diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f228418 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Report a bug or unexpected behaviour in SelectSim +title: "[BUG] " +labels: bug +assignees: '' +--- + +## Describe the bug + +A clear and concise description of what the bug is. + +## Minimal reproducible example + +```python +import selectsim as ss +# paste the smallest code that reproduces the issue +``` + +## Expected behaviour + +What you expected to happen. + +## Actual behaviour + +What actually happened. Include the full error message / traceback or +unexpected output. + +## Environment + +- selectsim version: +- Python version: +- Installation method: +- OS: +- Optional extras installed: + +## Additional context + +Any other context that may be relevant (e.g. dataset size, number of cores +used, `n_permut`, `backend`/`store` options passed to `null_model_parallel`). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..9d86aa4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest a new feature or improvement for SelectSim +title: "[FEATURE] " +labels: enhancement +assignees: '' +--- + +## Is your feature request related to a problem? + +A clear and concise description of the problem or limitation you are experiencing. + +## Proposed solution + +Describe the feature or change you would like to see, including any API +design ideas if relevant (e.g. new function signature, new parameter on +`selectX()`). + +## Alternatives considered + +Any alternative approaches you have considered, and why you prefer the +proposed solution. + +## Additional context + +Any other context, references, or examples that support the request. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..4479e2e --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: docs + +on: + push: + branches: [main, master] + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: docs-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + python-version: "3.12" + + - name: Install dependencies (including docs extra) + run: uv sync --all-extras + + - name: Build Sphinx docs + run: uv run sphinx-build -b html docs/source docs/build + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: docs/build + publish_branch: gh-pages diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4e6e5c0 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,32 @@ +name: tests + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +permissions: read-all + +jobs: + pytest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12"] + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: uv sync --all-extras + + - name: Run tests + run: uv run pytest tests/ -q diff --git a/.gitignore b/.gitignore index eed5663..a154190 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ .idea/* .python-version .vscode/* +.venv/ # Sphinx documentation docs/build/ +docs/jupyter_execute/ +docs/.jupyter_cache/ +docs/source/generated/ diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..392a683 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,51 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it using the metadata below, and cite the accompanying article as the primary reference (see 'preferred-citation')." +type: software +title: "SelectSim (Python)" +abstract: >- + A Python implementation of the SelectSim methodology for inferring + evolutionary dependencies -- co-mutations and mutual exclusivities -- + between functional alterations across cancer genomes. Estimates expected + co-mutation frequencies from individual gene mutation rates and per-sample + tumor mutation burden (TMB), then evaluates significance against a + permutation-based null model. +version: 0.2.0 +date-released: "2026-07-12" +license: MIT +repository-code: "https://github.com/CSOgroup/SelectSim_py" +url: "https://csogroup.github.io/SelectSim/" +authors: + - given-names: Arvind + family-names: Iyer + email: ayalurarvind@gmail.com + orcid: "https://orcid.org/0000-0002-8247-700X" + - given-names: Marco + family-names: Mina + email: marco.mina.85@gmail.com + - given-names: Miljan + family-names: Petrovic + email: miljanpet93@gmail.com + - given-names: Giovanni + family-names: Ciriello + email: giovanni.ciriello@unil.ch + orcid: "https://orcid.org/0000-0003-2021-8683" +preferred-citation: + type: article + title: "Evolving patterns of co-mutations from tumor initiation to metastatic progression" + journal: "Nature Genetics" + year: 2026 + doi: "10.1038/s41588-026-02661-4" + url: "https://doi.org/10.1038/s41588-026-02661-4" + authors: + - given-names: Arvind + family-names: Iyer + - given-names: Miljan + family-names: Petrovic + - given-names: Debora + family-names: Sesia + - given-names: Luca + family-names: Nanni + - given-names: Marco + family-names: Mina + - given-names: Giovanni + family-names: Ciriello diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c23bc86 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 SelectSim authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..59fafd6 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,104 @@ +# SelectSim (Python) 0.1.0 + +Initial feature-complete Python port of the [SelectSim R package](https://github.com/CSOgroup/SelectSim). + +* Ported the core algorithm: `selectX()` end-to-end pipeline, `AlterationLandscape`, + template-matrix generation (`template.py`), TMB-based weighting (`weights.py`), + the permutation null model (`null_model.py`), and all overlap / effect-size / + FDR statistics (`stats.py`). +* Added an optional accelerated backend for `null_model_parallel` + (`backend="jax"`, via the `selectsim[fast]` extra) alongside the default + NumPy backend, and an optional on-disk Zarr store for large null-model runs + (`store="zarr"`, via the `selectsim[storage]` extra) so simulations no + longer need to be held entirely in memory. +* Added `selectsim.gam`: a full port of `R/gam_utils.r`'s MAF-ingestion + pipeline (`filter_maf_*`, `stat_maf_*`, `maf_to_gam`, `TCGA_maf_schema`, + `GENIE_maf_schema`, `mutation_type`) for building Gene Alteration Matrices + (GAMs) directly from raw MAF files. +* Added `selectsim.plotting`: matplotlib/seaborn ports of the R package's + visualization helpers -- `theme_publication`, `obs_exp_scatter`, + `overlap_pair_extract`, `ridge_plot_ed`, `ridge_plot_ed_compare`. +* Added `selectsim.io`: Parquet read/write helpers and Zarr null-model store + utilities. +* Bundled real TCGA LUAD example data (`tests/data/*.parquet`, exported from + the R package's `luad_run_data`/`luad_result`/`luad_maf` datasets) and + added an R-parity test suite (`TestSelectXWithRData` in + `tests/test_selectsim.py`) validating the Python port's output against a + real `SelectSim::selectX()` R reference run. +* 126 tests passing across algorithm, statistics, GAM ingestion, plotting, + and I/O modules. +* Added Sphinx documentation (`docs/`) with a full API reference, an + "Overview" quick-start, and two tutorial notebooks (`introduction`, + `data_processing`) mirroring the R package's vignettes, plus standard + open-source project scaffolding (`LICENSE`, `CITATION.cff`, CI workflows, + issue templates). +* Supports Python 3.10-3.12 (`requires-python = ">=3.10,<3.13"`). The upper + bound is intentional, not a typo: numpy/scipy/jax/zarr don't all publish + prebuilt wheels for 3.13+ yet, and installing on an unsupported interpreter + can silently trigger a slow (or failing) from-source build of numpy. + 3.10/3.11/3.12 were each verified to resolve prebuilt wheels and pass the + full test suite. + +# SelectSim (Python) 0.2.0 + +* Fixed several correctness bugs found in code review: `filter_maf_column` + computed a union instead of an intersection when excluding rows matching + several substring values (`inclusive=False, fixed=False`); `store="zarr"` + fully materialized all permutations in memory before writing them to disk, + defeating its own purpose (now streams in bounded batches, ~5x lower peak + memory); the jax backend recompiled from scratch on every call instead of + reusing JAX's compilation cache; `n_cores` was silently ignored with + `backend="jax"` (now warns); `ridge_plot_ed` crashed on an empty null + model; and `retrieve_outliers`'s `n_sim` parameter was dead code and its + 90th-percentile threshold was off-by-one relative to the R original. +* This round of work (the fixes above, plus the earlier module ports, + documentation, and uv migration) was developed with the assistance of + Claude Code (Anthropic), directed and verified by Arvind Iyer throughout; + see `git log` for detailed commit history. + +# SelectSim (Python) 0.3.0 + +* **Performance**: the default numpy null-model backend + (`null_model_parallel`, `store="memory"` or `"zarr"`) no longer loops + over genes in Python to pick each row's top-k residual columns; + it now uses the same vectorized double-argsort rank trick the (now + removed) jax backend used, with no per-gene Python loop. Benchmarked + as bit-identical to the old implementation (full R-parity test suite + still passes) and consistently faster, especially at higher `n_cores`. +* **Performance**: `store="zarr"` null-model runs now chunk the on-disk + Zarr array to match the internal write-batch size instead of + defaulting to one chunk per permutation; writing in batches of 200 + (the default) previously forced 200 separate compress/write calls per + batch. Benchmarked at ~2.5x faster zarr writes in isolation. +* **Performance**: `estimate_p_val`'s `gene_names: List[str]` parameter + is now `gene_index: Dict[str, int]` -- callers looking up many pairs + (as `estimate_pairwise_p` does, used when `estimate_pairwise=True`) + now build the name-to-index mapping once instead of doing an O(n_genes) + list scan per gene per pair. +* **Removed**: the jax null-model backend (`backend="jax"` on + `null_model_parallel`, the `selectsim[fast]` extra). Benchmarked + against the numpy backend on real LUAD-sized data on CPU (no GPU): + jax was consistently 5-10x *slower*, dominated by per-call JIT + dispatch overhead that this workload's array sizes never amortize. + If GPU-backed acceleration is needed in the future, it should be + re-evaluated against the current (vectorized) numpy backend on + representative GPU hardware, not reintroduced by default. +* **Fixed**: `significance_heatmap` was implemented and tested but never + exported from the top-level `selectsim` package (only reachable via + `selectsim.plotting.significance_heatmap`); it's now `ss.significance_heatmap`. +* Added `null_model_parallel`/`selectX` `store="auto"`: estimates the + null model's memory footprint from cohort size and `n_permut` and + picks `"memory"` or `"zarr"` automatically against a detected (or + configured, `memory_budget_gb`) RAM budget, so one call scales safely + across machines without the caller needing to know cohort dimensions + up front. +* Added `oncoprint` and `oncoprint_pair`: new (not present in the R + package) multi-gene and two-gene co-mutation visualizations comparing + the observed alteration pattern to a retained null-model simulation, + with a per-sample TMB track. +* Extended the `introduction` tutorial notebook with `significance_heatmap`, + `oncoprint`, `oncoprint_pair`, and `ridge_plot_ed_compare` (previously + only `obs_exp_scatter` and `ridge_plot_ed` were demonstrated). +* This round of work was developed with the assistance of Claude Code + (Anthropic), directed and verified by Arvind Iyer throughout; see + `git log` for detailed commit history. diff --git a/README.md b/README.md index 63134d7..6712225 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,60 @@ # SelectSim Python -The goal of `selectsim` package is to implement the methodology to infer -inter-dependencies between functional alterations in cancer. SelectSim -estimates the expected number of mutations in a given gene and a given -sample from the mutation frequency of the gene, f(g), and the tumor -mutation burden (TMB) of the sample, $\mu$(t). These values can be -estimated within specific mutation and tumor subsets, to account for -heterogeneous tumor types, tissue specificities, and distinct mutational -processes. \ No newline at end of file +SelectSim infers evolutionary dependencies — co-mutations and mutual +exclusivities — between functional alterations across cancer genomes. It +estimates the expected co-mutation frequency for each gene pair from +individual mutation frequencies and per-sample tumor mutation burden (TMB), +then evaluates significance against a simulation-based null model. + +![SelectSim Method](docs/source/_static/SelectSim_method.png) + +This package accompanies the manuscript: + +> Iyer A, Petrovic M, Sesia D, Nanni L, Mina M, Ciriello G (2026). Evolving +> patterns of co-mutations from tumor initiation to metastatic progression. +> *Nature Genetics*. DOI: [10.1038/s41588-026-02661-4](https://doi.org/10.1038/s41588-026-02661-4) + +## Installation + +```bash +cd SelectSim_py +uv sync # recommended; or: pip install -e . +``` + +Optional extras: `uv sync --extra storage` (Zarr on-disk storage), `--extra docs` (build docs locally), `--all-extras`. Supports Python 3.10-3.12. + +## Quick start + +```python +import selectsim as ss + +# M, sample_class, alteration_class: see the Introduction tutorial for how +# to build these from a MAF, or load the bundled TCGA LUAD example data. +result = ss.selectX( + M, sample_class, alteration_class, + n_cores=1, min_freq=10, n_permut=1000, +) + +# Significant evolutionary dependencies +significant = result['result'][result['result']['FDR']] +``` + +See the [Introduction tutorial](docs/source/tutorials/introduction.ipynb) for a full walkthrough on real TCGA LUAD data, and [`docs/`](docs/) for the full API reference (`selectsim.gam` for MAF ingestion, `selectsim.plotting` for visualization, `selectsim.io` for Parquet/Zarr storage). + +## Citation + +If you use SelectSim in your research, please cite: + +> Iyer A, Petrovic M, Sesia D, Nanni L, Mina M, Ciriello G (2026). Evolving +> patterns of co-mutations from tumor initiation to metastatic progression. +> *Nature Genetics*. DOI: [10.1038/s41588-026-02661-4](https://doi.org/10.1038/s41588-026-02661-4) + +Citation metadata is also available in [`CITATION.cff`](CITATION.cff). + +## License + +MIT License. See [LICENSE](LICENSE). + +## Contact + +For bugs or feature requests, use the [issue tracker](https://github.com/CSOgroup/SelectSim_py/issues). This is the Python implementation of the [SelectSim R package](https://github.com/CSOgroup/SelectSim). diff --git a/docs/source/_static/SelectSim_method.png b/docs/source/_static/SelectSim_method.png new file mode 100644 index 0000000..36958ad Binary files /dev/null and b/docs/source/_static/SelectSim_method.png differ diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css new file mode 100644 index 0000000..79789fd --- /dev/null +++ b/docs/source/_static/css/custom.css @@ -0,0 +1,15 @@ +/* Wide tables (e.g. autosummary tables, wide code/dataframe output in + notebooks) scroll horizontally within their own box instead of + overflowing the page or getting clipped by the theme's content column. */ +table.docutils, +.output_area table, +div.jp-RenderedHTMLCommon table { + display: block; + overflow-x: auto; + white-space: nowrap; +} + +table.docutils td, +table.docutils th { + white-space: normal; +} diff --git a/docs/source/_static/favicon-32x32.png b/docs/source/_static/favicon-32x32.png new file mode 100644 index 0000000..5515d63 Binary files /dev/null and b/docs/source/_static/favicon-32x32.png differ diff --git a/docs/source/_static/favicon.ico b/docs/source/_static/favicon.ico new file mode 100644 index 0000000..cfcc850 Binary files /dev/null and b/docs/source/_static/favicon.ico differ diff --git a/docs/source/_static/logo.png b/docs/source/_static/logo.png new file mode 100644 index 0000000..35fbc7a Binary files /dev/null and b/docs/source/_static/logo.png differ diff --git a/docs/source/_static/logo.svg b/docs/source/_static/logo.svg new file mode 100644 index 0000000..47c2e59 --- /dev/null +++ b/docs/source/_static/logo.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/docs/source/conf.py b/docs/source/conf.py index c0cfc54..e845bce 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,33 +11,82 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'SelectSim' -copyright = '2024, Arvind Iyer' -author = 'Arvind Iyer' -release = '0.0.1' +copyright = '2026, Arvind Iyer, Marco Mina, Miljan Petrovic, Giovanni Ciriello' +author = 'Arvind Iyer, Marco Mina, Miljan Petrovic, Giovanni Ciriello' +release = '0.3.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', 'sphinx.ext.napoleon', - 'sphinx.ext.todo', 'sphinx.ext.viewcode', - 'sphinx.ext.autodoc', - 'sphinx_rtd_theme', - + 'sphinx.ext.intersphinx', + 'sphinx_autodoc_typehints', + 'sphinx_copybutton', + 'myst_nb', ] templates_path = ['_templates'] -exclude_patterns = [] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'generated/*.ipynb'] + +# -- Napoleon (NumPy/Google docstring) settings ------------------------------- +napoleon_google_docstring = False +napoleon_numpy_docstring = True +napoleon_use_param = True +napoleon_use_rtype = False +# -- Autosummary / autodoc ---------------------------------------------------- +# The reference page uses `.. autosummary:: :toctree: generated` blocks, which +# auto-generate one short stub page per function/class listed (rather than one +# giant automodule dump per module) -- this is the same pattern used by +# sibling scverse-ecosystem packages (e.g. CellCharter, squidpy). + +autosummary_generate = True +autodoc_typehints = 'description' +autodoc_default_options = { + 'members': True, + # Module-level constants such as `selectsim.gam.TCGA_maf_schema` are + # sizeable nested dicts; showing their full literal value inline makes + # the generated page an unreadable wall of text, so only their docstring + # is rendered. + 'no-value': True, +} + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable/', None), + 'pandas': ('https://pandas.pydata.org/docs/', None), +} + +# -- MyST-NB (Jupyter notebook tutorials) settings ---------------------------- +# The tutorial notebooks under docs/source/tutorials/ are pre-executed and +# saved with their outputs (see docs/source/tutorials/*.ipynb); "off" tells +# myst-nb to render the stored outputs as-is rather than re-executing every +# notebook on every docs build. +nb_execution_mode = "off" +myst_enable_extensions = ["colon_fence", "dollarmath"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_rtd_theme' +html_theme = 'sphinx_book_theme' html_static_path = ['_static'] +html_css_files = ['css/custom.css'] master_doc = 'index' - +html_title = 'SelectSim' +html_logo = '_static/logo.png' +html_favicon = '_static/favicon.ico' +html_theme_options = { + 'repository_url': 'https://github.com/CSOgroup/SelectSim_py', + 'use_repository_button': True, + 'use_issues_button': True, + 'use_download_button': False, + 'use_edit_page_button': False, + 'path_to_docs': 'docs/source', + 'show_navbar_depth': 1, + 'navigation_with_keys': False, +} diff --git a/docs/source/index.rst b/docs/source/index.rst index 6583bd9..a8724f6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -4,13 +4,15 @@ contain the root `toctree` directive. Welcome to SelectSim documentation -======================= +=================================== .. toctree:: :maxdepth: 2 :caption: Contents: - + overview.rst + tutorials/introduction.ipynb + tutorials/data_processing.ipynb reference.rst Indices and tables diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 6507591..96bcf5e 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -10,11 +10,111 @@ estimated within specific mutation and tumor subsets, to account for heterogeneous tumor types, tissue specificities, and distinct mutational processes. -Example usage: +This package accompanies the manuscript: Iyer A, Petrovic M, Sesia D, Nanni L, +Mina M, Ciriello G (2026). *Evolving patterns of co-mutations from tumor +initiation to metastatic progression.* Nature Genetics. +doi: `10.1038/s41588-026-02661-4 `_. + +.. _installation: + +Installation +------------ + +.. code:: bash + + # Using uv (recommended) + git clone https://github.com/CSOgroup/SelectSim_py + cd SelectSim_py + uv sync + + # Using pip + pip install -e . + +Optional extras: + +.. code:: bash + + uv sync --extra storage # Zarr-backed null model storage + +Supports Python 3.10-3.12. + +Example usage +------------- + +The snippet below builds a small synthetic dataset with the same shape +``selectX()`` expects -- a genes x samples binary mutation matrix (a "Gene +Alteration Matrix", or GAM) per mutation type, together with a matching +per-sample tumor-mutation-burden (TMB) table, sample covariates, and gene +covariates -- and runs the full pipeline. For a walkthrough using real +TCGA LUAD data instead of synthetic data, see the +:doc:`Introduction tutorial `. .. code:: python - import selectsim as ss - import pandas as pd import numpy as np + import pandas as pd + import selectsim as ss + + np.random.seed(0) + n_genes, n_samples = 15, 40 + genes = [f"gene{i}" for i in range(n_genes)] + samples = [f"sample{j}" for j in range(n_samples)] + + # Two mutation-type GAMs (genes x samples binary matrices) + missense = pd.DataFrame( + np.random.binomial(1, 0.2, size=(n_genes, n_samples)), + index=genes, columns=samples, + ) + truncating = pd.DataFrame( + np.random.binomial(1, 0.1, size=(n_genes, n_samples)), + index=genes, columns=samples, + ) + + # Matching per-sample tumor mutation burden (TMB) tables + tmb_missense = pd.DataFrame({ + "sample": samples, + "mutation": np.random.randint(10, 100, n_samples), + }) + tmb_truncating = pd.DataFrame({ + "sample": samples, + "mutation": np.random.randint(5, 50, n_samples), + }) + + M = { + "M": {"missense": missense, "truncating": truncating}, + "tmb": {"missense": tmb_missense, "truncating": tmb_truncating}, + } + + # Sample and gene (alteration) covariates + sample_class = {s: ("TypeA" if i < 20 else "TypeB") for i, s in enumerate(samples)} + alteration_class = {g: "MUT" for g in genes} + + # Run the full SelectSim pipeline + result = ss.selectX( + M, + sample_class, + alteration_class, + n_cores=1, + min_freq=2, + n_permut=1000, + verbose=True, + seed=42, + ) + + # `result['result']` is the interaction table: one row per gene pair + interaction_table = result["result"] + print(interaction_table[["SFE_1", "SFE_2", "nES", "type", "FDR"]].head()) + + # Significant evolutionary dependencies at the chosen FDR threshold + significant = interaction_table[interaction_table["FDR"]] + +The ``obj`` half of the return value (``result['obj']``) holds the +intermediate ``AlterationLandscape``, weight matrix, template matrices, and +retained null-model permutations, and is what the plotting helpers in +:mod:`selectsim.plotting` (e.g. :func:`selectsim.obs_exp_scatter`, +:func:`selectsim.ridge_plot_ed`) expect as input. + +See :doc:`reference` for the full API, and the :doc:`tutorials/introduction` +and :doc:`tutorials/data_processing` tutorials for end-to-end walkthroughs on +real TCGA LUAD data. diff --git a/docs/source/reference.rst b/docs/source/reference.rst index 16c453f..c1d71d7 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -1,6 +1,157 @@ API reference -================================= +============= -.. automodule:: selectsim.selectsim - :members: +This page documents the full public API of ``selectsim``, organized into the +same categories used by the R package's reference index (see +`SelectSim's pkgdown reference `_). +Every name below is importable directly as ``selectsim.``. +Main interface +-------------- + +End-to-end analysis: run the full SelectSim pipeline in one call. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.selectX + +Core workflow +------------- + +Step-by-step building blocks called internally by :func:`~selectsim.selectX`. +Exposed directly for custom pipelines or debugging. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.AlterationLandscape + selectsim.generate_s + selectsim.template_obj_gen + selectsim.generate_w_mean_tmb + selectsim.generate_w_block + selectsim.null_model_parallel + selectsim.retrieve_outliers + +Statistics +---------- + +Overlap computation, effect sizes, FDR estimation, and results-table +assembly. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.am_stats + selectsim.al_stats + selectsim.am_pairwise_alteration_overlap + selectsim.am_weight_pairwise_alteration_overlap + selectsim.r_am_pairwise_alteration_overlap + selectsim.w_r_am_pairwise_alteration_overlap + selectsim.effect_size + selectsim.estimate_fdr2 + selectsim.binary_yule + selectsim.interaction_table + selectsim.estimate_p_val + selectsim.estimate_pairwise_p + +Data processing +---------------- + +MAF file filtering utilities and Gene Alteration Matrix (GAM) construction, +for going from a raw MAF dataframe to the ``M`` / ``tmb`` structure consumed +by :func:`~selectsim.selectX`. See also the +:doc:`Data processing tutorial `. + +.. py:data:: selectsim.mutation_type + :type: dict[str, list[str]] + :no-index: + + Mapping from mutation-type bucket name (``"truncating"``, ``"missense"``, + ``"ignore"``) to the ``Variant_Classification`` values that fall into it. + Mirrors R's ``mutation_type`` list object. + +.. py:data:: selectsim.TCGA_maf_schema + :type: dict + :no-index: + + Schema describing a TCGA-style MAF: which columns hold gene/sample/ + mutation information, and which ``Variant_Classification`` values count + as truncating, missense, or ignorable. Mirrors R's ``TCGA_maf_schema`` + list object. + +.. py:data:: selectsim.GENIE_maf_schema + :type: dict + :no-index: + + Schema describing a GENIE-style MAF, in the same shape as + :data:`~selectsim.TCGA_maf_schema`. Mirrors R's ``GENIE_maf_schema`` + list object. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.filter_maf_column + selectsim.filter_maf_complex + selectsim.filter_maf_sample + selectsim.filter_maf_gene_name + selectsim.filter_maf_mutation_type + selectsim.filter_maf_mutations + selectsim.filter_maf_schema + selectsim.filter_maf_truncating + selectsim.filter_maf_missense + selectsim.filter_maf_ignore + selectsim.stat_maf_column + selectsim.stat_maf_sample + selectsim.stat_maf_gene + selectsim.maf_to_gam + +Visualization +-------------- + +Plot helpers for inspecting results and null-model distributions. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.theme_publication + selectsim.obs_exp_scatter + selectsim.overlap_pair_extract + selectsim.ridge_plot_ed + selectsim.ridge_plot_ed_compare + selectsim.oncoprint_pair + selectsim.oncoprint + +I/O +--- + +Parquet read/write helpers and Zarr-backed null-model storage utilities +(used by ``null_model_parallel(..., store="zarr")``). + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.read_parquet + selectsim.write_parquet + selectsim.create_zarr_null_store + selectsim.open_zarr_store + +Utilities +--------- + +Low-level array/indexing helpers shared across the package. + +.. autosummary:: + :toctree: generated + :nosignatures: + + selectsim.add + selectsim.pairwise_indices + selectsim.matrix_to_pairwise_vector + selectsim.create_pair_template diff --git a/docs/source/tutorials/data_processing.ipynb b/docs/source/tutorials/data_processing.ipynb new file mode 100644 index 0000000..63def2b --- /dev/null +++ b/docs/source/tutorials/data_processing.ipynb @@ -0,0 +1,922 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "8aff7690", + "metadata": {}, + "source": [ + "# Data Processing with SelectSim\n", + "\n", + "`selectsim` implements the SelectSim methodology to infer inter-dependencies\n", + "between functional alterations in cancer. The `selectsim.gam` module\n", + "provides functions to turn a raw MAF (Mutation Annotation Format) dataframe\n", + "into the Gene Alteration Matrices (GAMs) and tumor-mutation-burden (TMB)\n", + "tables that `selectX()` expects.\n", + "\n", + "## Example\n", + "\n", + "We will process the LUAD MAF dataset from TCGA bundled with this package\n", + "(`tests/data/luad_maf.parquet`), together with the bundled OncoKB v3.9\n", + "cancer gene list and hotspot mutation catalogue, to build the GAM used in\n", + "the [Introduction tutorial](introduction.ipynb).\n", + "\n", + "**Note:** this is an illustrative example of the MAF -> GAM pipeline. It is\n", + "not necessarily the exact production pipeline used to generate the\n", + "package's real published data; see the\n", + "[SelectSim_analysis](https://github.com/CSOgroup/SelectSim_analysis)\n", + "repository for that." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6016e184", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:40.334654Z", + "iopub.status.busy": "2026-07-12T00:40:40.334355Z", + "iopub.status.idle": "2026-07-12T00:40:41.884875Z", + "shell.execute_reply": "2026-07-12T00:40:41.883815Z" + } + }, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "import selectsim as ss\n", + "from selectsim.gam import (\n", + " filter_maf_gene_name,\n", + " filter_maf_truncating,\n", + " filter_maf_mutation_type,\n", + " filter_maf_schema,\n", + " filter_maf_mutations,\n", + " maf_to_gam,\n", + ")\n", + "\n", + "DATA_PATH = Path(\"../../../tests/data\")\n", + "assert DATA_PATH.exists(), f\"Expected bundled test data at {DATA_PATH.resolve()}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0da7dfff", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:41.888331Z", + "iopub.status.busy": "2026-07-12T00:40:41.887928Z", + "iopub.status.idle": "2026-07-12T00:40:42.105866Z", + "shell.execute_reply": "2026-07-12T00:40:42.104273Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "##### Number of lines #### -> 220734\n", + "##### Number of genes #### -> 396\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
ChromosomeStart_PositionEnd_PositionHugo_SymbolVariant_ClassificationTumor_Sample_BarcodesampleHGVSp_Short
010101814119101814119CPN1Missense_MutationTCGA-05-4244-01A-01D-1105-08TCGA-05-4244-01p.H366D
110129902901129902901MKI67SilentTCGA-05-4244-01A-01D-1105-08TCGA-05-4244-01p.N2401N
2102110460121104606NEBLIn_Frame_DelTCGA-05-4244-01A-01D-1105-08TCGA-05-4244-01p.S730_V731del
3104565251845652518RP11-445N18.7RNATCGA-05-4244-01A-01D-1105-08TCGA-05-4244-01.
4105066720050667200ERCC6SilentTCGA-05-4244-01A-01D-1105-08TCGA-05-4244-01p.S1381S
\n", + "
" + ], + "text/plain": [ + " Chromosome Start_Position End_Position Hugo_Symbol \\\n", + "0 10 101814119 101814119 CPN1 \n", + "1 10 129902901 129902901 MKI67 \n", + "2 10 21104601 21104606 NEBL \n", + "3 10 45652518 45652518 RP11-445N18.7 \n", + "4 10 50667200 50667200 ERCC6 \n", + "\n", + " Variant_Classification Tumor_Sample_Barcode sample \\\n", + "0 Missense_Mutation TCGA-05-4244-01A-01D-1105-08 TCGA-05-4244-01 \n", + "1 Silent TCGA-05-4244-01A-01D-1105-08 TCGA-05-4244-01 \n", + "2 In_Frame_Del TCGA-05-4244-01A-01D-1105-08 TCGA-05-4244-01 \n", + "3 RNA TCGA-05-4244-01A-01D-1105-08 TCGA-05-4244-01 \n", + "4 Silent TCGA-05-4244-01A-01D-1105-08 TCGA-05-4244-01 \n", + "\n", + " HGVSp_Short \n", + "0 p.H366D \n", + "1 p.N2401N \n", + "2 p.S730_V731del \n", + "3 . \n", + "4 p.S1381S " + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "input_maf = pd.read_parquet(DATA_PATH / \"luad_maf.parquet\")\n", + "oncokb_genes = pd.read_parquet(DATA_PATH / \"oncokb_genes.parquet\")[\"gene\"].tolist()\n", + "oncokb_truncating_genes = pd.read_parquet(DATA_PATH / \"oncokb_truncating_genes.parquet\")[\"gene\"].tolist()\n", + "variant_catalogue = pd.read_parquet(DATA_PATH / \"variant_catalogue.parquet\")\n", + "\n", + "print(f\"##### Number of lines #### -> {len(input_maf)}\")\n", + "print(f\"##### Number of genes #### -> {len(oncokb_genes)}\")\n", + "input_maf.head()" + ] + }, + { + "cell_type": "markdown", + "id": "4d8b0056", + "metadata": {}, + "source": [ + "### Defining a MAF schema\n", + "\n", + "A \"schema\" tells the `filter_maf_*` / `maf_to_gam` functions which columns\n", + "hold gene, sample and mutation-type information, and which\n", + "`Variant_Classification` values count as `truncating`, `missense`, or\n", + "`ignore` (non-functional). `selectsim.gam` ships `TCGA_maf_schema` and\n", + "`GENIE_maf_schema` for the two most common MAF conventions; here we define a\n", + "small custom schema matching the bundled LUAD MAF's columns (it uses a\n", + "short `sample` column rather than the full `Tumor_Sample_Barcode`)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9a3bfc1b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:42.109304Z", + "iopub.status.busy": "2026-07-12T00:40:42.108871Z", + "iopub.status.idle": "2026-07-12T00:40:42.140227Z", + "shell.execute_reply": "2026-07-12T00:40:42.138816Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "##### Number of samples #### -> 502\n" + ] + } + ], + "source": [ + "mutation_type = {\n", + " \"ignore\": [\"Silent\", \"Intron\", \"RNA\", \"3'UTR\", \"5'UTR\", \"5'Flank\", \"3'Flank\", \"IGR\"],\n", + " \"truncating\": [\n", + " \"Frame_Shift_Del\", \"Frame_Shift_Ins\", \"In_Frame_Del\", \"In_Frame_Ins\",\n", + " \"Nonsense_Mutation\", \"Nonstop_Mutation\", \"Splice_Region\", \"Splice_Site\",\n", + " \"Translation_Start_Site\",\n", + " ],\n", + " \"missense\": [\"Missense_Mutation\"],\n", + "}\n", + "\n", + "custom_maf_schema = {\n", + " \"name\": \"custom_maf\",\n", + " \"column\": {\n", + " \"gene\": \"Hugo_Symbol\",\n", + " \"gene.name\": \"Hugo_Symbol\",\n", + " \"sample\": \"sample\",\n", + " \"sample.name\": \"sample\",\n", + " \"mutation.type\": \"Variant_Classification\",\n", + " \"mutation\": \"HGVSp_Short\",\n", + " },\n", + " \"mutation.type\": mutation_type,\n", + "}\n", + "\n", + "sample_col = custom_maf_schema[\"column\"][\"sample\"]\n", + "gene_col = custom_maf_schema[\"column\"][\"gene\"]\n", + "mutation_col = custom_maf_schema[\"column\"][\"mutation\"]\n", + "\n", + "mut_samples = input_maf[sample_col].unique().tolist()\n", + "print(f\"##### Number of samples #### -> {len(mut_samples)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "46bbab7f", + "metadata": {}, + "source": [ + "### Restricting to OncoKB cancer genes" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "e69536ba", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:42.143831Z", + "iopub.status.busy": "2026-07-12T00:40:42.143401Z", + "iopub.status.idle": "2026-07-12T00:40:42.176321Z", + "shell.execute_reply": "2026-07-12T00:40:42.175268Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "##### Number of lines #### -> 6708\n" + ] + } + ], + "source": [ + "maf_genes = filter_maf_gene_name(input_maf, genes=oncokb_genes, gene_col=gene_col)\n", + "print(f\"##### Number of lines #### -> {len(maf_genes)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "181299d5", + "metadata": {}, + "source": [ + "### Generating the GAMs\n", + "\n", + "#### Truncating GAM\n", + "\n", + "We build the GAM restricted to truncating mutations in OncoKB truncating\n", + "genes, and a matching TMB table counting *all* truncating mutations (in any\n", + "gene) per sample." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5f3461a0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:42.179831Z", + "iopub.status.busy": "2026-07-12T00:40:42.179537Z", + "iopub.status.idle": "2026-07-12T00:40:42.330571Z", + "shell.execute_reply": "2026-07-12T00:40:42.328641Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "truncating GAM shape (genes x samples): (396, 502)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
samplemutation
0TCGA-05-4244-0124
1TCGA-05-4249-0145
2TCGA-05-4250-0140
3TCGA-05-4382-01206
4TCGA-05-4384-0117
\n", + "
" + ], + "text/plain": [ + " sample mutation\n", + "0 TCGA-05-4244-01 24\n", + "1 TCGA-05-4249-01 45\n", + "2 TCGA-05-4250-01 40\n", + "3 TCGA-05-4382-01 206\n", + "4 TCGA-05-4384-01 17" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "maf_trunc = filter_maf_truncating(maf_genes, schema=custom_maf_schema)\n", + "input_maf_trunc = filter_maf_truncating(input_maf, schema=custom_maf_schema)\n", + "\n", + "truncating_tmb = pd.DataFrame({\"sample\": mut_samples})\n", + "trunc_counts = input_maf_trunc.groupby(sample_col).size()\n", + "truncating_tmb[\"mutation\"] = truncating_tmb[\"sample\"].map(trunc_counts).fillna(0).astype(int)\n", + "\n", + "truncating_gam = maf_to_gam(\n", + " maf_trunc,\n", + " sample_col=sample_col,\n", + " gene_col=gene_col,\n", + " value_var=\"Variant_Classification\",\n", + " samples=mut_samples,\n", + " genes=oncokb_genes,\n", + " fun_aggregate=len,\n", + " binarize=True,\n", + " fill=0,\n", + ").fillna(0).astype(int)\n", + "\n", + "truncating_data = {\"gam\": truncating_gam, \"tmb\": truncating_tmb}\n", + "print(\"truncating GAM shape (genes x samples):\", truncating_gam.shape)\n", + "truncating_tmb.head()" + ] + }, + { + "cell_type": "markdown", + "id": "2273d926", + "metadata": {}, + "source": [ + "#### Missense (hotspot) GAM\n", + "\n", + "For missense mutations we additionally restrict to known hotspot positions\n", + "from the OncoKB `variant_catalogue`, matching on (gene, stripped HGVSp\n", + "position)." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b9126d34", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:42.334079Z", + "iopub.status.busy": "2026-07-12T00:40:42.333780Z", + "iopub.status.idle": "2026-07-12T00:40:43.012826Z", + "shell.execute_reply": "2026-07-12T00:40:43.011440Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "missense GAM shape (genes x samples): (396, 502)\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
samplemutation
0TCGA-05-4244-01163
1TCGA-05-4249-01253
2TCGA-05-4250-01270
3TCGA-05-4382-011328
4TCGA-05-4384-01100
\n", + "
" + ], + "text/plain": [ + " sample mutation\n", + "0 TCGA-05-4244-01 163\n", + "1 TCGA-05-4249-01 253\n", + "2 TCGA-05-4250-01 270\n", + "3 TCGA-05-4382-01 1328\n", + "4 TCGA-05-4384-01 100" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# All non-\"ignore\" mutations (missense + truncating + other), all genes\n", + "maf_valid = filter_maf_schema(\n", + " input_maf,\n", + " schema=custom_maf_schema,\n", + " column=\"mutation.type\",\n", + " values=custom_maf_schema[\"mutation.type\"][\"ignore\"],\n", + " inclusive=False,\n", + ").copy()\n", + "\n", + "missense_maf = filter_maf_mutation_type(\n", + " input_maf, variants=\"Missense_Mutation\", variant_col=custom_maf_schema[\"column\"][\"mutation.type\"]\n", + ")\n", + "missense_tmb = pd.DataFrame({\"sample\": mut_samples})\n", + "missense_counts = missense_maf.groupby(sample_col).size()\n", + "missense_tmb[\"mutation\"] = missense_tmb[\"sample\"].map(missense_counts).fillna(0).astype(int)\n", + "\n", + "# Strip the leading \"p.\" and trailing ref/alt amino acid letters,\n", + "# e.g. \"p.R175H\" -> \"R175\", to match the variant_catalogue's \"mut\" column.\n", + "stripped = maf_valid[mutation_col].str.slice(2)\n", + "maf_valid[\"HGVSp_Short_fixed\"] = stripped.str.replace(r\"[A-Z]*$\", \"\", regex=True)\n", + "\n", + "maf_hotspot = filter_maf_mutations(\n", + " maf_valid,\n", + " variant_catalogue,\n", + " maf_col=[gene_col, \"HGVSp_Short_fixed\"],\n", + " values_col=[\"gene\", \"mut\"],\n", + ")\n", + "\n", + "missense_gam = maf_to_gam(\n", + " maf_hotspot,\n", + " sample_col=sample_col,\n", + " gene_col=gene_col,\n", + " value_var=\"Variant_Classification\",\n", + " samples=mut_samples,\n", + " genes=oncokb_genes,\n", + " fun_aggregate=len,\n", + " binarize=True,\n", + " fill=0,\n", + ").fillna(0).astype(int)\n", + "\n", + "missense_data = {\"gam\": missense_gam, \"tmb\": missense_tmb}\n", + "print(\"missense GAM shape (genes x samples):\", missense_gam.shape)\n", + "missense_tmb.head()" + ] + }, + { + "cell_type": "markdown", + "id": "1a2c34f0", + "metadata": {}, + "source": [ + "### Comparing against the pre-built bundled GAMs\n", + "\n", + "The package also bundles the ground-truth GAMs produced by this same\n", + "pipeline (`luad_missense.parquet` / `luad_truncating.parquet`, used\n", + "directly in the [Introduction tutorial](introduction.ipynb)). Let's confirm\n", + "our from-scratch GAMs match." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "edb77ac5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:43.015918Z", + "iopub.status.busy": "2026-07-12T00:40:43.015530Z", + "iopub.status.idle": "2026-07-12T00:40:43.105308Z", + "shell.execute_reply": "2026-07-12T00:40:43.103965Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "truncating GAM mismatches vs. bundled ground truth: 0 / 198792 cells\n", + "missense GAM mismatches vs. bundled ground truth: 0 / 198792 cells\n" + ] + } + ], + "source": [ + "ground_truth_trunc = pd.read_parquet(DATA_PATH / \"luad_truncating.parquet\")\n", + "aligned_trunc = truncating_gam.loc[ground_truth_trunc.index, ground_truth_trunc.columns]\n", + "n_mismatch_trunc = int((aligned_trunc.values != ground_truth_trunc.values).sum())\n", + "print(f\"truncating GAM mismatches vs. bundled ground truth: {n_mismatch_trunc} / {aligned_trunc.size} cells\")\n", + "\n", + "ground_truth_missense = pd.read_parquet(DATA_PATH / \"luad_missense.parquet\")\n", + "aligned_missense = missense_gam.loc[ground_truth_missense.index, ground_truth_missense.columns]\n", + "n_mismatch_missense = int((aligned_missense.values != ground_truth_missense.values).sum())\n", + "print(f\"missense GAM mismatches vs. bundled ground truth: {n_mismatch_missense} / {aligned_missense.size} cells\")" + ] + }, + { + "cell_type": "markdown", + "id": "47524023", + "metadata": {}, + "source": [ + "### Assembling the run object\n", + "\n", + "Finally, we assemble the `M` / `tmb` / `sample_class` / `alteration_class`\n", + "structure that `selectX()` expects -- exactly the structure loaded directly\n", + "from bundled Parquet files in the\n", + "[Introduction tutorial](introduction.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "88aced4b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:43.108421Z", + "iopub.status.busy": "2026-07-12T00:40:43.108115Z", + "iopub.status.idle": "2026-07-12T00:40:43.122782Z", + "shell.execute_reply": "2026-07-12T00:40:43.121712Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "M['missense'] shape: (396, 502)\n", + "M['truncating'] shape: (396, 502)\n", + "n sample_class: 502\n", + "n alteration_class: 396\n" + ] + } + ], + "source": [ + "genes_to_take = missense_data[\"gam\"].index.tolist()\n", + "order = missense_data[\"gam\"].columns.tolist()\n", + "\n", + "M = {\n", + " \"M\": {\n", + " \"missense\": missense_data[\"gam\"].loc[genes_to_take, order],\n", + " \"truncating\": truncating_data[\"gam\"].loc[genes_to_take, order],\n", + " },\n", + " \"tmb\": {\n", + " \"missense\": missense_data[\"tmb\"].set_index(\"sample\").loc[order].reset_index(),\n", + " \"truncating\": truncating_data[\"tmb\"].set_index(\"sample\").loc[order].reset_index(),\n", + " },\n", + "}\n", + "\n", + "alteration_class = {gene: \"MUT\" for gene in genes_to_take}\n", + "sample_class = {sample: \"LUAD\" for sample in order}\n", + "\n", + "run_data = {\n", + " \"M\": M,\n", + " \"sample_class\": sample_class,\n", + " \"alteration_class\": alteration_class,\n", + "}\n", + "\n", + "print(\"M['missense'] shape:\", run_data[\"M\"][\"M\"][\"missense\"].shape)\n", + "print(\"M['truncating'] shape:\", run_data[\"M\"][\"M\"][\"truncating\"].shape)\n", + "print(\"n sample_class:\", len(run_data[\"sample_class\"]))\n", + "print(\"n alteration_class:\", len(run_data[\"alteration_class\"]))" + ] + }, + { + "cell_type": "markdown", + "id": "55bbae28", + "metadata": {}, + "source": [ + "### Running SelectSim on the freshly-built GAMs\n", + "\n", + "With `run_data` assembled, we can feed it straight to `selectX()` -- using\n", + "a smaller `n_permut` here just to keep this notebook fast; see the\n", + "[Introduction tutorial](introduction.ipynb) for a full run with\n", + "`n_permut=1000` (and results validated against the R package's reference\n", + "output)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "32c46843", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:43.126318Z", + "iopub.status.busy": "2026-07-12T00:40:43.126031Z", + "iopub.status.idle": "2026-07-12T00:40:43.510523Z", + "shell.execute_reply": "2026-07-12T00:40:43.509354Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "253 gene pairs tested\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SFE_1SFE_2nEStypeFDR
0KRASTP53-13.912824METrue
1EGFRKRAS-10.657126METrue
2STK11TP53-7.731611METrue
3BRAFKRAS-5.536524METrue
4KRASSTK114.877474COTrue
\n", + "
" + ], + "text/plain": [ + " SFE_1 SFE_2 nES type FDR\n", + "0 KRAS TP53 -13.912824 ME True\n", + "1 EGFR KRAS -10.657126 ME True\n", + "2 STK11 TP53 -7.731611 ME True\n", + "3 BRAF KRAS -5.536524 ME True\n", + "4 KRAS STK11 4.877474 CO True" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_obj = ss.selectX(\n", + " M=run_data[\"M\"],\n", + " sample_class=run_data[\"sample_class\"],\n", + " alteration_class=run_data[\"alteration_class\"],\n", + " n_cores=1,\n", + " min_freq=10,\n", + " n_permut=200,\n", + " lambda_var=0.3,\n", + " tao=1,\n", + " verbose=False,\n", + " max_fdr=0.25,\n", + " seed=42,\n", + ")\n", + "\n", + "result_df = result_obj[\"result\"]\n", + "print(f\"{len(result_df)} gene pairs tested\")\n", + "result_df[[\"SFE_1\", \"SFE_2\", \"nES\", \"type\", \"FDR\"]].head()" + ] + }, + { + "cell_type": "markdown", + "id": "de08b2a1", + "metadata": {}, + "source": [ + "Save `run_data` (e.g. via `selectsim.io.write_parquet` per-table, or\n", + "`pickle`) and see the [Introduction tutorial](introduction.ipynb) for how to\n", + "run `selectX()` on it and interpret the results in more detail." + ] + }, + { + "cell_type": "markdown", + "id": "b0b1ba0a", + "metadata": {}, + "source": [ + "### Session info" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "20df33ff", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-12T00:40:43.513453Z", + "iopub.status.busy": "2026-07-12T00:40:43.513166Z", + "iopub.status.idle": "2026-07-12T00:40:43.518074Z", + "shell.execute_reply": "2026-07-12T00:40:43.516847Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python: 3.12.13 | packaged by conda-forge | (main, Mar 5 2026, 16:50:00) [GCC 14.3.0]\n", + "selectsim: 0.1.0\n", + "numpy: 2.1.3\n", + "pandas: 2.2.3\n" + ] + } + ], + "source": [ + "import sys\n", + "import numpy, pandas\n", + "import selectsim\n", + "\n", + "print(\"python:\", sys.version)\n", + "print(\"selectsim:\", selectsim.__version__)\n", + "print(\"numpy:\", numpy.__version__)\n", + "print(\"pandas:\", pandas.__version__)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/source/tutorials/introduction.ipynb b/docs/source/tutorials/introduction.ipynb new file mode 100644 index 0000000..ccbfd0b --- /dev/null +++ b/docs/source/tutorials/introduction.ipynb @@ -0,0 +1,891 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c5509c02", + "metadata": {}, + "source": [ + "# Introduction to SelectSim\n", + "\n", + "SelectSim infers evolutionary dependencies -- co-mutations and mutual\n", + "exclusivities -- between functional alterations across cancer genomes. It\n", + "estimates expected co-mutation frequencies from individual gene mutation\n", + "rates and per-sample tumor mutation burden (TMB), then evaluates\n", + "significance against a permutation null model.\n", + "\n", + "![SelectSim Method](../_static/SelectSim_method.png)\n", + "\n", + "This package accompanies the manuscript: Iyer A, Petrovic M, Sesia D, Nanni L,\n", + "Mina M, Ciriello G (2026). *Evolving patterns of co-mutations from tumor\n", + "initiation to metastatic progression.* Nature Genetics.\n", + "doi: [10.1038/s41588-026-02661-4](https://doi.org/10.1038/s41588-026-02661-4).\n", + "\n", + "See the [installation instructions](../overview.rst) for setup.\n", + "\n", + "## Example\n", + "\n", + "We will run the SelectSim algorithm on a processed LUAD (lung\n", + "adenocarcinoma) dataset from TCGA, bundled with this package as small\n", + "Parquet files under `tests/data/`.\n", + "\n", + "**Note:** this is an example of running SelectSim on *already processed*\n", + "data (a GAM + TMB + covariates, ready to feed to `selectX()`). See the\n", + "[Data processing tutorial](data_processing.ipynb) to build this input\n", + "structure from a raw MAF file." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "d465b22a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:47.821334Z", + "iopub.status.busy": "2026-07-14T19:10:47.820804Z", + "iopub.status.idle": "2026-07-14T19:10:49.335540Z", + "shell.execute_reply": "2026-07-14T19:10:49.334213Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import numpy as np\n", + "import pandas as pd\n", + "from pathlib import Path\n", + "\n", + "import selectsim as ss\n", + "\n", + "# The bundled LUAD example data lives in tests/data/ at the repository root.\n", + "# This notebook lives at docs/source/tutorials/, three levels below the\n", + "# repository root.\n", + "DATA_PATH = Path(\"../../../tests/data\")\n", + "assert DATA_PATH.exists(), f\"Expected bundled test data at {DATA_PATH.resolve()}\"" + ] + }, + { + "cell_type": "markdown", + "id": "b1f3aac8", + "metadata": {}, + "source": [ + "### Data description & format\n", + "\n", + "The bundled LUAD data is a set of Parquet exports of the R package's\n", + "`luad_run_data` object, which is conceptually a list consisting of:\n", + "\n", + "- `M`: a dict of GAMs (genes x samples presence/absence matrices), one per\n", + " mutation type (`missense`, `truncating`)\n", + "- `tmb`: a dict of tumor mutation burden tables, one per mutation type, each\n", + " a `DataFrame` with `sample` and `mutation` columns\n", + "- `sample_class`: a mapping of sample -> sample covariate (here, all `LUAD`)\n", + "- `alteration_class`: a mapping of gene -> alteration covariate (here, all `MUT`)\n", + "\n", + "We assemble this same structure here from the bundled Parquet files." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "b116ad1e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:49.339285Z", + "iopub.status.busy": "2026-07-14T19:10:49.338828Z", + "iopub.status.idle": "2026-07-14T19:10:49.449444Z", + "shell.execute_reply": "2026-07-14T19:10:49.448430Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "missense GAM: (396, 502)\n", + "truncating GAM: (396, 502)\n", + "n samples with covariates: 502\n", + "n genes with covariates: 396\n" + ] + } + ], + "source": [ + "missense = pd.read_parquet(DATA_PATH / \"luad_missense.parquet\")\n", + "truncating = pd.read_parquet(DATA_PATH / \"luad_truncating.parquet\")\n", + "tmb_missense = pd.read_parquet(DATA_PATH / \"luad_tmb_missense.parquet\")\n", + "tmb_truncating = pd.read_parquet(DATA_PATH / \"luad_tmb_truncating.parquet\")\n", + "sample_class_df = pd.read_parquet(DATA_PATH / \"luad_sample_class.parquet\")\n", + "alteration_class_df = pd.read_parquet(DATA_PATH / \"luad_alteration_class.parquet\")\n", + "\n", + "M = {\n", + " \"M\": {\"missense\": missense, \"truncating\": truncating},\n", + " \"tmb\": {\"missense\": tmb_missense, \"truncating\": tmb_truncating},\n", + "}\n", + "sample_class = dict(zip(sample_class_df[\"sample\"], sample_class_df[\"sample_class\"]))\n", + "alteration_class = dict(zip(alteration_class_df[\"gene\"], alteration_class_df[\"alteration_class\"]))\n", + "\n", + "print(\"missense GAM:\", missense.shape)\n", + "print(\"truncating GAM:\", truncating.shape)\n", + "print(\"n samples with covariates:\", len(sample_class))\n", + "print(\"n genes with covariates:\", len(alteration_class))" + ] + }, + { + "cell_type": "markdown", + "id": "e322670e", + "metadata": {}, + "source": [ + "### Running SelectSim\n", + "\n", + "We use `selectX()`, which builds the background (null) model and computes\n", + "the results table in one call.\n", + "\n", + "| Parameter | Description |\n", + "|-----------|-------------|\n", + "| `M` | Dict of GAMs and TMB tables |\n", + "| `sample_class` | Dict mapping sample -> sample covariate |\n", + "| `alteration_class` | Dict mapping gene -> alteration covariate |\n", + "| `min_freq` | Minimum number of samples a gene must be mutated in |\n", + "| `n_permut` | Number of permutations for the null model |\n", + "| `lambda_var` | Penalty factor for the weight computation |\n", + "| `tao` | Fold-change threshold for the weight computation |\n", + "| `max_fdr` | FDR cutoff for calling significant results |\n", + "\n", + "The function returns a dict with `'obj'` (the background model and\n", + "intermediate computations) and `'result'` (the interaction table)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4e61ac58", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:49.452228Z", + "iopub.status.busy": "2026-07-14T19:10:49.451965Z", + "iopub.status.idle": "2026-07-14T19:10:51.127557Z", + "shell.execute_reply": "2026-07-14T19:10:51.126450Z" + } + }, + "outputs": [], + "source": [ + "result_obj = ss.selectX(\n", + " M=M,\n", + " sample_class=sample_class,\n", + " alteration_class=alteration_class,\n", + " n_cores=1,\n", + " min_freq=10,\n", + " n_permut=1000,\n", + " lambda_var=0.3,\n", + " tao=1,\n", + " save_object=False,\n", + " verbose=False,\n", + " estimate_pairwise=False,\n", + " max_fdr=0.25,\n", + " seed=42,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "409bd0f1", + "metadata": {}, + "source": [ + "### Interpreting the results\n", + "\n", + "`result_obj['result']` is a DataFrame with one row per gene pair. Some of\n", + "the key columns:\n", + "\n", + "- **`SFE_1`, `SFE_2`**: the two genes/alterations making up the pair.\n", + "- **`overlap` / `w_overlap`**: observed (raw / TMB-weighted) co-occurrence\n", + " count for the pair.\n", + "- **`r_overlap` / `w_r_overlap`**: mean (raw / TMB-weighted) co-occurrence\n", + " count expected under the null model.\n", + "- **`nES`**: the normalized effect size -- how far the observed weighted\n", + " overlap deviates from the null-model background, in units of the\n", + " null-model's standard deviation. Positive values indicate co-occurrence\n", + " more frequent than expected (co-mutation); negative values indicate\n", + " co-occurrence less frequent than expected (mutual exclusivity).\n", + "- **`type`**: `'CO'` (co-mutation) if `nES > 0`, `'ME'` (mutual exclusivity)\n", + " if `nES < 0`.\n", + "- **`nFDR` / `nFDR2`**: FDR-corrected significance estimates for `nES`.\n", + "- **`FDR`**: boolean, `True` if the pair is significant at the `max_fdr`\n", + " threshold passed to `selectX()`.\n", + "\n", + "Let's look at the results, sorted by absolute effect size (the default\n", + "sort order)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0de9e002", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:51.130731Z", + "iopub.status.busy": "2026-07-14T19:10:51.130441Z", + "iopub.status.idle": "2026-07-14T19:10:51.148445Z", + "shell.execute_reply": "2026-07-14T19:10:51.147133Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SFE_1SFE_2namesupport_1support_2freq_1freq_2overlapw_overlapmax_overlap
0KRASTP53KRAS - TP53154.0221.00.3067730.44023949.035.876017154.0
1EGFRKRASEGFR - KRAS57.0154.00.1135460.3067730.00.00000057.0
2STK11TP53STK11 - TP5359.0221.00.1175300.44023913.09.54563959.0
3BRAFKRASBRAF - KRAS35.0154.00.0697210.3067732.00.98214335.0
4KRASSTK11KRAS - STK11154.059.00.3067730.11753028.025.92307759.0
\n", + "
" + ], + "text/plain": [ + " SFE_1 SFE_2 name support_1 support_2 freq_1 freq_2 \\\n", + "0 KRAS TP53 KRAS - TP53 154.0 221.0 0.306773 0.440239 \n", + "1 EGFR KRAS EGFR - KRAS 57.0 154.0 0.113546 0.306773 \n", + "2 STK11 TP53 STK11 - TP53 59.0 221.0 0.117530 0.440239 \n", + "3 BRAF KRAS BRAF - KRAS 35.0 154.0 0.069721 0.306773 \n", + "4 KRAS STK11 KRAS - STK11 154.0 59.0 0.306773 0.117530 \n", + "\n", + " overlap w_overlap max_overlap \n", + "0 49.0 35.876017 154.0 \n", + "1 0.0 0.000000 57.0 \n", + "2 13.0 9.545639 59.0 \n", + "3 2.0 0.982143 35.0 \n", + "4 28.0 25.923077 59.0 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_obj[\"result\"].iloc[:5, :10]" + ] + }, + { + "cell_type": "markdown", + "id": "964e3cc9", + "metadata": {}, + "source": [ + "#### Filtering significant hits" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "72275c7f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:51.151166Z", + "iopub.status.busy": "2026-07-14T19:10:51.150892Z", + "iopub.status.idle": "2026-07-14T19:10:51.174015Z", + "shell.execute_reply": "2026-07-14T19:10:51.172684Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "43 significant evolutionary dependencies at FDR <= 0.25\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
SFE_1SFE_2namesupport_1support_2freq_1freq_2overlapw_overlapmax_overlap...w_r_overlapwESwFDRnESmean_r_nESnFDRcum_freqnFDR2typeFDR
0KRASTP53KRAS - TP53154.0221.00.3067730.44023949.035.876017154.0...58.380021-15.9127340.0-13.928458-1.9842750.0375.00.0METrue
1EGFRKRASEGFR - KRAS57.0154.00.1135460.3067730.00.00000057.0...16.917421-11.9624230.0-10.720814-1.2416090.0211.00.0METrue
\n", + "

2 rows × 22 columns

\n", + "
" + ], + "text/plain": [ + " SFE_1 SFE_2 name support_1 support_2 freq_1 freq_2 overlap \\\n", + "0 KRAS TP53 KRAS - TP53 154.0 221.0 0.306773 0.440239 49.0 \n", + "1 EGFR KRAS EGFR - KRAS 57.0 154.0 0.113546 0.306773 0.0 \n", + "\n", + " w_overlap max_overlap ... w_r_overlap wES wFDR nES \\\n", + "0 35.876017 154.0 ... 58.380021 -15.912734 0.0 -13.928458 \n", + "1 0.000000 57.0 ... 16.917421 -11.962423 0.0 -10.720814 \n", + "\n", + " mean_r_nES nFDR cum_freq nFDR2 type FDR \n", + "0 -1.984275 0.0 375.0 0.0 ME True \n", + "1 -1.241609 0.0 211.0 0.0 ME True \n", + "\n", + "[2 rows x 22 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_df = result_obj[\"result\"]\n", + "\n", + "significant = result_df[result_df[\"nFDR2\"] <= 0.25]\n", + "print(f\"{len(significant)} significant evolutionary dependencies at FDR <= 0.25\")\n", + "significant.head(2)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "054959bf", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:51.176540Z", + "iopub.status.busy": "2026-07-14T19:10:51.176217Z", + "iopub.status.idle": "2026-07-14T19:10:51.183444Z", + "shell.execute_reply": "2026-07-14T19:10:51.181999Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "type\n", + "ME 30\n", + "CO 13\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "significant[\"type\"].value_counts()" + ] + }, + { + "cell_type": "markdown", + "id": "db2838c5", + "metadata": {}, + "source": [ + "#### Plotting a scatter plot of observed vs. expected co-mutation" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "591930bf", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:51.186313Z", + "iopub.status.busy": "2026-07-14T19:10:51.186040Z", + "iopub.status.idle": "2026-07-14T19:10:51.593042Z", + "shell.execute_reply": "2026-07-14T19:10:51.591820Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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GhrqzE+BCjiRLjfNoCYiIiKifI9zQImIiIjIqZiAEhEREZFTMQElIiIiIqdiAkpERERETsUElIiIiIicitswEZFTREdH48cff7S5HplMhq+//jrLe65fv45z587h+fPniI2NhVQqhY+PDypXroxGjRrBz8+6/Tfv37+PM2fO4MmTJ4iNjYVGo4FCoYCfnx/KlSuHunXrolixYhbXp9VqMXXqVGRkZBhdGz16NCpVqmRVfObMnTvXaEP4sWPHonz58hY9//PPP+PBgwcGZSNGjDA4YOB1s2bNQnx8vEHZhAkTULJkySzb+eWXX3D37l2j8qCgIAQEBGQb59atW/Hnn38alIlEIkilUri4uMDT0xM+Pj4oV64catasiRIlSmRbJxE5mI6IyAn++ecfHQCb/7i7u5us//nz57ovvvhC5+fnl20dderU0S1btizLeOPi4nTfffedrnz58hbFValSJd2HH36oS0xMzLYvDhw4YLaeSZMm5ah/TalZs6ZR/SdOnLD4+caNGxs9v2/fPrP3lypVyuj+f/75J8s2YmNjdTKZzGRfNGvWzKI433vvPau+hqpXr65bsGCBLiEhweK+ICL74it4Isrzdu/ejapVq2LGjBmIiYnJ9v7Lly9j+fLlZq8fOHAAVatWxeTJk3H//n2LYrhz5w7mz59vUfvh4eFmr61duxZardaiNvODTZs2QalUmrx2+vRp3Llzx+5tXr9+HePHj0e1atVw8uRJu9dPRNnjK3gicopSpUphzpw5Jq+tX78ef//9t0FZ165d0bJlS6N73zyJ5rfffsOIESOge+NMDXd3dwQFBaFGjRpwdXXFkydPcOrUKVy+fNno3tetXLkSQ4cONbpHKpWiRYsWaNiwIXx8fJCUlIQHDx7g9OnTFiepAJCcnIzt27ebvf748WMcPnzYolfP+cHq1auzvB4eHo4vv/zS6nqDg4PRtm1bqNVqPH/+HH/99RdOnz4NjUajv+fJkydo27Yt1q5da/ZIUSJyEIFHYImIdAMGDDB6TTpv3rxsnzt27JhOKpUaPdutWzddTEyMyWeuXbum69evn65JkyYm63NxcTGqr127drr//vvPbBznz5/XjRw5Uufi4qK7fft2ljGvWrUq21fEYWFh2X7ulsjtr+Dv3Llj0dSG7Jh6Bf/tt98a3Xfr1i1dmzZtjO51c3PTnT9/3qI+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", + "text/plain": [ + "
" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fig = ss.obs_exp_scatter(result=result_obj[\"result\"], title=\"TCGA LUAD\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "586f1b33", + "metadata": {}, + "source": [ + "#### Ridge plot of the null-model background for the top hits\n", + "\n", + "For a handful of the most significant pairs, `ridge_plot_ed` shows the\n", + "null-model background distribution (grey ridge), the observed weighted\n", + "overlap (red line), and the null-model mean (blue line)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a8fa7702", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:51.596645Z", + "iopub.status.busy": "2026-07-14T19:10:51.596329Z", + "iopub.status.idle": "2026-07-14T19:10:52.049139Z", + "shell.execute_reply": "2026-07-14T19:10:52.047728Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwkAAAKcCAYAAACjR/bIAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAwwxJREFUeJzs3XdYU2fDBvA7CXuDOBEBrYqCE1BxgAgooiJOXDjqrnWPVm2tVq1b694DB4ji3rhb9x51K+6tiMiGJN8ffuQ1ggIhyWHcv+vyepvk5Hnuc16F3DlLJJfL5SAiIiIiIvp/YqEDEBERERFR3sKSQERERERESlgSiIiIiIhICUsCEREREREpYUkgIiIiIiIlLAlERERERKSEJYGIiIiIiJSwJBARERERkRIdoQOQsN69e4cDBw7A3t4ehoaGQschIiIiIg1KTEzEo0eP0KRJE1hbW39zOZaEQu7AgQPo0qWL0DGIiIiISIvWr1+Pzp07f/N1loRCzt7eHsDnvyiVKlXSypxSqRQJCQkwMjKCRCLRypwFBbed6rjtVMdtpxpuN9Vx26mO2051hWXb3bp1C126dFF8BvwWloRCLv0Qo0qVKqFmzZpamVMqlSIuLg4mJiYF+h+hJnDbqY7bTnXcdqrhdlMdt53quO1UV9i2XVaHmfPEZSIiIiIiUsKSQERERERESlgSiIiIiIhICUsCEREREREpYUkgIiIiIiIlLAlERERERKSEJYGIiIiIiJSwJBARERERkRKWBCIiIiIiUsKSQERERERESlgSiIiIiIhICUsCEREREWVLUlISmjdvjvDwcKGjkIaxJBARERGpyYgRI9C9e3ehY2hMWloa9uzZg3v37gkdhTRMR+gARERERAXFiRMn8OzZM6FjEOUa9yQQEREREZES7kkgIvpCQkICDAwMIBbzOxQi0pyzZ88iPDwcjx8/hrm5Ofz9/dGmTRuIRCLFMmlpaQgJCcGhQ4egp6eH9u3bw9vbG23btkVwcDCCgoIyjBkWFoaoqCgUKVIEzZo1Uxrzzp07GD58OH777TdYW1tjwYIFePbsGZydnTFkyBBYWFgojSeVShESEoKDBw8q5vf09NT4tqG8gb8FiYgA7N27F02bNoW5uTnMzUtBJAJEIuDt2wSho2lHfDwUKx0fL3QaogJt3LhxqFu3LuLj49GmTRs4OjqiT58+6NKli2IZmUyGVq1aYeDAgXB0dETjxo2xceNGzJ07N9NzAtLHTEhIQEBAACpWrJhhzA8fPmDPnj04dOgQBgwYAGdnZzRq1AiLFy+Gr68v5HK5Ylm5XI42bdpgwIABqFChAho3bowNGzZg4cKFmt9AlCdwTwIRFXo7duxAp06d0KtXL/z888+IiUlF+u/V8uXLY9Cgnhg8eDCKFCkibFAiyvciIyMxceJEzJ07F4MGDVI8X69ePdSvXx+BgYFo164dNmzYgN27d2PLli1o3bo1AKBz587o16/fd8ccMGAA4uLiYGJiggYNGiiNmS4iIgLnzp2Dnp4eAKB06dJo2bIl9u/fj6ZNmwIAQkNDsWPHDmzevBlt27ZVzN+zZ0+NbRvKW7gngYgKtfPnz6Nz586YNGkSevfuDTs7O5QvX17x+pQpU3D48GHY2dlhzJgx+PDhg4BpiSi/W716NQwMDNC/f3+l5+vVq4fy5ctj27ZtAIAtW7agWLFiaNWqldJyPXr0UHnMdJ06dVIUBADw8vICAFy7dk3xXEREBIoWLYo2bdoovbdXr17ZXVXK57gngYgKrbS0NHTs2BG9e/eGr69vpsu4uLigQYOluHLlChYtWoTFixdjxYoVGX5xEhFlx71796Cjo6P4Zl8ulyv+vH//Hk+fPgUAPHnyBPb29krnKACAg4PDd8eUy+VITU2FRCIBAKUxvzWGqakp9PT08Pr1a8Vzjx8/hoODQ4b5y5Urp+KaU37DkkBEhdamTZuQnJyMrl27Zrls9erVsWzZMkRGRuLHH3/E0aNHMW/ePJ7gTEQ5oqenBwsLi0y/ke/bty8sLS0BAPr6+vj06VOGZeIzOWfoyzGlUimSkpJgYGAAiUSiNGY6XV3dDGOIRCLIZDLFY319fcTGxmZYLi4uLuuVpAKBJYGICiWZTIZJkyahR48emf7C/JbGjRvDyckJvXr1QqlSpTBmzBgNpiSigsbd3R3nzp1D7dq1UbRo0W8uV7NmTaxatQofPnxQ+pB/4cKF745pZWWlOCchfW+CKlxcXLBixQpER0fDyspK8fzZs2dVHpPyF34FRkSF0rZt2xATE4PAwMAcv9fGxgazZ8/G5MmTcfDgQfWHI6ICa9iwYbC0tERwcDDevXun9NrBgwdx+PBhAED//v0hlUoxcuRISKVSAMDr16+xceNGlcfMif79+0Mmk2H48OGK+V+8eJHp/FQwcU8CERVKs2fPRnBwsNLJezlRqVIl/Prrr+jcuTMePXoEIyMjNSckovzq/fv3aN68eYbnzc3NsWHDBhw/fhy9e/eGvb09nJycoKenh3v37qFGjRqYMWMGAMDZ2Rnr169Hnz59cODAATg4OCA2NhZLly7F1q1blfYS2NjYKMYsV64cHB0dYWBggPv37yuNmRNOTk7YsGEDevfujUOHDsHBwQEfPnzAqlWrsHPnTtU3DuUbLAlEVOg8fPgQ58+fx9SpU3M1TmBgIDZu3IgVK1YoXcqQiAqvWbNmffMqaOlfSlSuXBknT57EkydPcPfuXRgZGaFChQqwtrZWWr59+/bw9/fHhQsXoKenBzc3N0RFRQFAhvMM0sd8+PAhrl27BisrK1SqVElpTEdHR+zatQtubm4Zsm3btg12dnZKz7Vr1w7+/v44f/68Yn6xWIxdu3bB0dEx5xuH8hWWBCIqdDZs2IB69epl+CWbUyKRCD/++CNmzJiBfv36qbxXgogKjnr16mV72TJlyqBMmTLfXcbExAQNGzZUPD59+jSAz+cMfGtMKyurTM9JsLCwyHQPBwDF/RG+ZmxsrDQ/gG+OQQULz0kgokJFLpdj3bp18Pf3V8t4Pj4+0NXVRWhoqFrGIyJKFxERoXSFoXv37uHPP/+Ei4tLpnsDiNSJJYGICpXbt2/j+fPnGb4ZU5VEIkH37t0xffp0yOVytYxJRAR8vtxppUqVUKdOHbi6uqJKlSooXrw4tmzZInQ0KgR4uBERFSp79+6Ft7c3DA0N1Tamn58fpkyZghs3bsDZ2Vlt4xJR4datWzcEBQXh1q1bePfuHezt7ZXuCE+kSSwJRFSoHDlyBAEBAWod08jICJ6enti0aRNLAhGplYGBAWrUqCF0DCqEeLgRERUqMTExqF27ttrH9fHxwaZNm3jIERERFQgsCURUqNSoUQP6+vpqH9fDwwOPHz/GjRs31D42ERGRtrEkEFGh8q3LBubWl4ccERER5XcsCURUKDx//hwANHpsr4+PD686QkREBQJLAhEVCsePHwfw+cZEmuLu7o47d+7gxYsXGpuDiIhIG1gSiKhQOHz4sMbnMDc3R5UqVRAZGanxuYiIiDSJJYGICry3b9/i2rVrWpmrTp06OHDggFbmIipI7ty5Az8/P/Tq1UvoKFnau3cv/Pz8kJqaKnSUbMuPmUlYLAlEVODt3r0b5cqV08pcdevWxcGDByGTybQyH1FBsXr1ahw4cAArV65U+Sph27dvh5+fn8b//T158gQHDhyAVCrV6DzqlB8zk7BYEoiowNu6davGrmr0tSpVqiAlJQVXrlzRynxEBYFMJsP69evRunVrGBkZYd26dSqN8+jRIxw4cIAlnUgNWBKIqECLj4/HoUOHUKtWLa3Mp6Ojg9q1a/OQI6IcOHToEJ4/f46ePXuiWbNmCA0NzfQb79TUVKxduxZdu3ZF27ZtMWXKFMTGxgIAwsLCsHTpUgBAs2bN4Ofnp7i7+tKlS9GtW7cM402ePBkjRoxQPD59+jT8/Pzg5+cHf39/9OrVC6tWrUJaWppK6xUbG4sZM2agbdu2aNeuHWbPno34+HjF62fOnIGfnx/u3r2b4b0LFy5Eu3btFIUnNTUVq1evRpcuXRAYGIixY8fi1atXSu/ZtGkTWrRoAQBYu3YtgoKCMHXq1EyzZXddN23ahGbNmkEul2P58uVo27YtfvzxR5w+fVqlbUL5h47QAYiINCkyMhIlS5ZEqVKltDanu7s79u/fj9GjR2ttTqL8LCQkBJaWlvD19UViYiI2b96MY8eOoWXLlopl4uPj4evri/v372PEiBFwcHDAlStX4O/vjxMnTqB27drw9PTE7du3MXjwYIjFYkgkEgDArVu3FFc4+9Lly5eVPmiXK1cOQ4YMAQBIpVJERUVh0qRJ2LRpE/bv35+jdXr37h3q1q0LiUSC4cOHQyqVYtq0aVi9ejVOnDgBc3NzVK1aFSdPnsTatWsxadIkxXtlMhmmTp2KRo0aQSwWIzExET4+Pnj06BFGjRqF4sWLY926dahRowbOnDkDOzs7AEBUVBQiIyMxadIkpKSkoFOnTrC3t880X3bXNSoqCgcOHMBvv/2GxMREtGvXDnv37kWDBg2wc+dO+Pv752i7UP7BkkBEBdqmTZvg5eUFkUiktTlr166NadOmISEhAUZGRlqblwoxuRxISBA6BWBkBOTw31psbCy2bduGjh07QldXF82aNYOpqSk2btyoVBImTJiAixcv4ubNm4pzjNq1a4eYmBgAQNmyZeHo6AgAaNy4MXR0cv4Rp1ixYvDz81N6ztvbG05OTjh58iTq1auX7bHGjRuHFy9eICoqCsWKFQMA+Pn5oXz58pg4cSJmzpwJIyMjtG7dGuvXr8fEiRMVP6eOHTuGZ8+eITg4GAAwadIkXL58GTdu3ICDgwMAICgoCC4uLhg1ahTCw8MV88pkMojFYsyePVtRknK7rjKZDBKJBLNnz1bMnZCQgKFDh6q8rSnv4/+rRFRgJSYmYteuXVi1apVW5y1Tpgysra1x8uRJ+Pr6anVuKqQSEgAN3gMk2+LiAGPjHL1l8+bNSExMRFBQEADAwMAALVq0wLZt2xAbGwtLS0sAwMaNGxEQEJDhIgQWFhZqiZ7u5s2bCAkJwaNHj/Dx40fF87du3cpRSdixYwcCAgIUBQEA7Ozs0KRJE2zfvh0zZ84EAAQHB2Pt2rX4999/4eHhAQBYt24dbGxs0KhRIwBAaGgomjZtqigIACASidC+fXtMnjwZcrlcUTDkcjnat2+v1nWVy+Xo2rWr0nt//PFH+Pv74+bNm6hatWq2twvlHzwngYgKrH379sHa2hqVKlXS6rwikQi1atXCoUOHtDovUX4UEhICa2trxQdi4PMegsTERERERAD4fDjMs2fPUL58eY1m2blzJ5ydnREdHY3AwEAMHjwYgwYNAgDExcVlexypVIoXL14ofahP5+DggKdPnyoeN2rUCDY2Nli/fj2Az19ubN26FZ07d4ZYLIZMJsPjx49x6dIlxTkE6X82bNiAuLg4pQ/4AGBra6v2dU0/pCld+mFMUVFRWc5F+RP3JBBRgRUeHo7GjRtr9VCjdLVq1VJ8wCHSOCOjz9/iCy2Hh9dFRUXhxIkTKFKkCJo3b654PiUlBcDnb9R79+4NiUQCXV1dfPr0SaVYBgYGijG/9OHDB6XHkyZNQqtWrbB8+XLFc8+ePcvxfOl5vzxJOV1cXBz09fUVj8ViMTp37oxly5Zh/vz52LFjB2JjYxWHGonFYujq6sLV1RU9e/bMdD7jL/beSCQSpfG/JafrGh8fr7TXJr1ImJmZZTkX5U8sCURUIMXHx2P37t1Yu3atIPPXrl0bv/32G6Kjo2FlZSVIBipERKIcH+aTF6T/+1y4cKHSh02pVIpy5cph5cqVePjwIRwcHFCrVi2cOHHiu+Pp6uoCQIZLoNrY2ODt27dISkqCgYEBgM9F5L///lPaO/HkyRN4e3srvffo0aMqrVv16tVx7ty5DM+fP38eNWrUUHouODgY06dPx65du7Bu3TpUr14dzs7Oitdr1aqFV69eZTiHIDdyuq7nz59XOnzywoULEIvFivNAqODh4UZEVCDt2bMHJUqUQIUKFQSZv1ixYihbtiyOHTsmyPxEeZ1cLse6devg7u6O9u3bZziUJv3Ql/QiMWbMGFy5cgUTJ06EXC4H8PmD/pffhJcuXRoA8PDhQ6W5mjRpAplMhhUrViiemzhxYobzGapVq4Z9+/YhMTERAPD48WNs3LhRpfUbOHAgTp8+jQ0bNiieW7hwIf777z8MHjxYaVlnZ2fUqFEDf//9NyIjIzMc///777/j5MmTmDFjhlIBioqKUlr/nMjJuorFYqxZs0Zxudnnz59j+vTp6Nixo1avHEfaxZJARAXS2rVr4efnJ8ihRul4XgLRt/3777+IiopC586dM329TJkyqFu3ruLGak2bNkVISAjmzp2L0qVLo379+rCzs1O6hKmfnx+cnZ3h4eEBX19fxX0SKlSogDFjxmDIkCFwc3ODo6MjSpYsiSpVqijNOWfOHHz8+BFly5ZF3bp10bhxY/z1118qrV9wcDAmTZqE3r17o0qVKnBycsLIkSMxa9YstG7dOtPlT548Cblcjo4dOyq91rhxY4SHh+Pvv/+Gra0tPD098cMPP8Df31/lE7dzsq4ikQgDBw6Eq6sr6tatC0dHR5QoUQJz5sxRaW7KH3i4EREVOK9fv0ZkZCR27dolaA53d3f+EiX6htKlS2Pfvn3fvWLQ8uXL8fjxYyQnJ0NfXx9du3ZFhw4dcPXqVaSkpMDZ2Rnm5uaK5fX19XHlyhVcuXIF79+/V/qSYOLEiejVqxeioqJQqVIllChRAnXr1kVycrJimcqVK+P27du4fv06pFIpatasCV1dXezbt0/pAgjNmjXDvn37oKen9911HDt2LAYOHIirV69CJBKhWrVqMDU1zXTZH3/8EZUqVYKxsTFKlCiR4fV27dqhdevW+O+//xAdHQ07OzvY29tDLP7f971BQUGoVq1apuN/nTm765quTp06uHz5Mi5dugQzM7NvzkMFB0sCERU4GzZsQM2aNWFjYyNojlq1auHZs2e4f/8+fvjhB0GzEOU1ZcuWRdmyZb+7TIUKFTJ8YNXT04Obm9s33yORSODi4pLpa3Z2dkpX6alevXqGZfT19eHq6qr03NfnAtja2mbrCkLA5xN7GzRokOVy5ubmWZ5zIJFIvvvh3MHBAWXKlMn06kSZZc7Oun7J2Ng4W+tCBQMPNyKiAkUul2PlypWKwwyEZGRkBBcXlxzfqZWIiEhoLAlEVKBcvnwZjx8/ho+Pj9BRAAB169bF3r17hY5BRKSSoKAg/gwrpFgSiKhAWb58OZo0aQKjHF6vXVPq1auH48ePIykpSegoREQ55uDggMaNGwsdgwTAkkBEBcanT5+wbt06tGvXTugoCj/88APMzMyyvL47ERFRXsKSQEQFRlhYGOzt7eHk5CR0FAWRSIR69epxdz0REeUrLAlEVCDI5XIsWrQIbdu2FfTeCJlp2LAhtm7dqrgBFBERUV7HkkBEBcK5c+fw4MED+Pv7Cx0lA3d3d7x//x6XLl0SOgoREVG2sCQQUYGwcOFCtGjRIs+csPwlfX19NGzYEJs2bRI6ChERUbawJBBRvvfu3Tts3rwZQUFBQkf5Jh8fH2zevJmHHBERUb7AkkBE+d6KFStQrVo1lCtXTugo31SvXj28efMGV65cEToKUZ6RlpaGCxcu4MKFC/j48WOmy9y6dQsXLlzAkydPtJwue75chwsXLuDixYuIiopCSkqKRudNSEjAhQsX8OnTJ43Ok9fcvXsX9+/fFzpGjrx8+RKXL18WOkaOsSQQUb4mlUqxePHiPL0XAQAMDAzg6emJjRs3Ch2FKM+IiYmBm5sb3NzcMHPmzAyvf/r0CS4uLnBzc8Nff/0lQMKspa9DQEAA+vXrh759+6Jhw4awsrLC77//rrF5b968CTc3N1y8eFFjc+RFP/30E4YMGSJ0jBxZuXIlGjRoIHSMHGNJIKJ8be/evUhJSYGXl5fQUbLUokULrF27FqmpqUJHIcpTihYtitDQ0AzPb9u2Dbq6utDX1xcgVc706dNHsTfhyZMnWLRoESZNmoQVK1YIHY1IJSwJRJSvzZ8/H23atIGOjo7QUbLk7u4OiUSCPXv2CB2FKE9p164doqKicObMGaXnw8LC0KpVq+/++3779i2uXbuGd+/eZXhNJpMpHQb06NEjSKXSDMt9fejOs2fPcPv27VydQ9S1a1dYWlri6NGjOc7zpffv3+O///5DfHx8tuZ9+vQpLl++rHQYUlpaGm7evIlXr14B+Hwe14ULFyCTyQBkXP8XL17g6tWrSuOmpKTg5s2buHfvXqbb5eHDh7h161aG5+/cuYMHDx4oHj979kwxdlpaGm7fvo2XL19+c31iYmJw48aNbK9/djO/evUKFy5cyHRdnj59muFqdHK5HA8ePMCNGzeQnJyc4T1fr9edO3e+eYhc+va/cOECLl++jLdv32a63JdjpqSk4NatW3j9+nXWK64mLAlElG/dv38fx48fR5s2bYSOki0SiQStWrXCkiVLhI5ClKc4ODjA3d0dGzZsUDz35s0bHDlyBJ07d870PQ8ePICXlxfKlCmD9u3bw9bWFu3atUNcXJximeTkZPTr109xGJCHhwcsLS0xffp0pbHSD905efIk2rZtC09PT7i6usLR0RFRUVEqrVNCQgLi4+NhYWGR4zzA559vjRo1QokSJdC6dWvY2Nigb9++SEhI+OacBw4cQJUqVbB+/XoYGxsDACIjI2Fra4t69erB3d0dP/74I7Zs2QI3NzfFWOnr/88//6Bly5bw8PDA8OHDFePOmjUL1tbWaNy4MWrXrg1bW1vs3LlTae7ff/8dwcHBGTL17dtXaay///4bTZo0wbVr11ClShW0aNECtra26Nq1q6K0pBszZgyKFSuGZs2awdHREeHh4d/Z4sqyynzjxg24ubnh+PHjGd7bvn17jBgxQvE4PDwcZcqUQa1atdCiRQsUK1YMCxYsUHpP+nqdP38eTk5OaN++/Tfznjx5UvH3oEePHihfvjycnJxw8uTJTMc8e/YsKlWqhBYtWsDGxgadO3fOtKioG0sCEeVbixYtgre3N4oWLSp0lGwLDAzEkSNH8PjxY6GjUAEilwPx8cL/yc3Fuzp16oTw8HCkpaUBALZs2QJra2s0atQow7IxMTHw8vKCRCLB8+fPcfv2bcW3vL1791YsZ2hoqHRS8ZMnT7Bp0yb8/vvvme7RmzZtGoYOHYoHDx7gyZMnkMvl+PXXX7OV/8WLF4p59u3bhzZt2sDS0hKDBg3KcZ7o6Gh4enpCKpXi2bNnuHv3LqKjo+Hq6prpHhMAWL9+PVq0aIFhw4Zh1qxZEIvFeP/+Pdq2bYumTZvi3bt3ePjwIQICArB06dJMx5g6dSpGjRqF+/fv49ChQwA+H08/YsQILFiwAM+ePcP79+/RunVrtGnTRuXzIVJSUjBz5kycOnUK9+7dw759+7Bu3Tps375daX2mTZuGzZs349GjR4iKisKBAwfw9OnTLMfPTmYvLy+ULl0a69atU3rv/fv3cf78eXTt2hUAsHv3bnTo0AEDBw7Eu3fvEBUVhdDQUAwePBg7duzIsF6zZ8/G2bNncfXqVYwcOTLTfC1btlT8Hbhy5QrevHmDRo0aoW3btkolN33MWbNm4eLFi7h//z7Onj2LPXv2YOLEiVluh9xiSSCifCkhIQGrVq3K8ycsf6148eJo0KAB9yaQWiUkACYmwv/5zpfcWQoKCsKHDx9w8OBBAFBc1lgikWRYdsWKFXj27BnWrFkDKysrAECpUqUwYcIEhIeH4/nz50rLS6VSPH36FBcvXoS1tTXKly+PvXv3ZhjX1dUV9erVAwBYWVnhxx9/xM6dO7N12NHu3bsV3w4PHDgQx48fx88//4wffvghw7JZ5Vm6dClev36NNWvWoHjx4gAAsViM3r17o0yZMhnGmzlzJnr06IH58+dj3LhxiueXLFkCqVSK2bNnK7ZjYGAgSpcunek61KhRQ7H+6WbMmAEfHx/Fh2aRSIQZM2bAysoKs2bNynK7ZObDhw8YPHgwLC0tAQC+vr6oVq2a0ofuadOmISAgAC1btgQA6OrqYtq0adn6giU7mcViMTp37oyIiAgkJiYq3rtx40YYGhoq9lCPHz8e9evXx6hRoyASiQAAzZo1Q2BgIGbPnp1hvQYOHKi09+h7EhIScOfOHVy7dg2+vr549epVhqsgffjwAUOHDlWM6eLiggEDBuDvv/9Wyq0Jef8gXiKiTISFhaFEiRKoWbOm0FFyrEuXLhg6dCjGjBkDU1NToeMQ5QlFixaFj48PQkNDUbZsWVy6dCnDIR3p/v33X5QqVQqvXr1SHGcPfD6kTy6X4+rVq7CxsQEA/PXXX5gxYwYMDAxQokQJSCQSPHnyJNNvpN3d3ZUe29vbIzk5GW/fvkWxYsW+m79Pnz4YP3684vH169fRoEEDPH36VOmb++zkOXXqFMqWLQsHB4fvzgl8/hB79uxZbNq0Ca1atVI6x+HatWtwdHTM8KG1Tp062LVrV4ax6tatq/Q4Li4Od+7cQceOHZWe19fXh5ubG86fP59lvszo6ell+Nltb2+v2AZpaWm4detWhsOXihYtivLly3937JxkDg4OxrRp07Bz504EBQVBLpdj8+bNaNmyJUxNTZGQkIBLly6hR48euHDhQoYs6YU2nUQiQe3atbNc/zdv3qB///7YuXMn7O3tYWZmpvj/7eu/lzo6OqhVq5bSc3Xr1sVff/2FqKgoODk5ZTmfqlgSiCjfkcvlmD9/Ptq3b6/4Zic/cXV1hYODA5YuXap03CuRqoyMgK+OUhAsR2507twZ/fv3R/HixVG2bNkMH47SxcbG4uPHj+jXr1+G11xcXKCrqwvg87fC48aNw/bt29G8eXPFMnXq1Mlw/DsAxV6JdAYGBgDw3fMAvqVKlSro1KkTli5diunTp8Pc3DzbeeLi4hTfsmclOjoaIpEIJiYmGV778OEDzMzMMjyf2XMAMhSh9JOFzc3NMyxrYWGhdDLxt34Wpx8+9iVLS8sMyxsYGCgKX2xsLKRSaabzZvacqpmdnJxQs2ZNrF+/HkFBQTh58iQeP36MhQsXAvh8CV65XI5Dhw5lOJEbACpWrKj02MrKKtM9X1/r06eP4sTmkiVLAgAePXoEBweHDH8vjY2NM4yZvm7R0dFZzpUbLAlElO+cOXMGUVFRSr9k8xORSIQePXpg6tSpGDhwYL64vCPlbSIR8P/nqeZrgYGB6Nu3L+bOnYthw4Z9c7nSpUsjKioqw7e7Xzt06BCcnZ2VflbIZDJERUXB2tpabbm/xdraGjKZDC9fvoS5uXm289ja2uLAgQPZmuPvv//GmjVrEBAQgB07dsDb21vxmp2dHY4dO5bhPV8fjpVOLFY+Ct3Kygr6+vqZ7nV58uQJSpUqpXhcpEgRxMTEZFju6dOnOd7WVlZWMDU1xbNnzzId73uH8+QkM/B5b8LIkSPx9u1bbNiwAcWKFYOvr69inQwNDdGhQwdMmzYty9xfb7/MyOVyHD58GGPHjlUUBAC4d+9epst//PgRnz59UtrrnL5u9vb2Wc6XGzwngYjynfnz56NFixYwyu3XlgJq2LAhjIyMsHbtWqGjEOUZJiYm6N+/P6pVq4b27dt/c7l27drhyZMniIyMzPDal9/66+npITY2Vumcgq1bt2r8G9h058+fh4GBAWxtbXOUp02bNnjz5g22bNmi9LxMJstwJ2exWIw1a9YgKCgILVu2VCoXfn5+uH//vtJx7jKZDNu2bctWfl1dXfj6+mLz5s1K8z548ACnTp1Cs2bNFM85Ojri8ePHSpfoPHXq1DcLSVb8/PywefNmpW/WszNeTjIDUByWFBISgoiICLRt21bxzb2Ojg4CAwOxcePGTO9srcoeJpFIBF1d3Qx3GP/W/TTEYjHCwsKUngsLC4Ozs/M3zy1RF+5JIKJ85c2bN9i6dSs2bdokdJRcEYvF6NOnDyZOnIiuXbtybwLR/5s5cyakUmmGq7x8KSAgAH369EHbtm3xyy+/wN3dHfHx8Th//jzWrVuHhw8fAvh8/s+SJUswYMAAtG/fHteuXcPWrVtRv359tedOv7oR8Pnb3x07dmD//v3466+/FJcjzW6eli1bomvXrggODsbt27dRt25dPHv2DIsWLUJoaGiGcxXEYjFWrVoFsViM1q1bY/369WjVqhVat26N+vXrIzAwEFOmTIGVlRVWrFiBhg0b4s6dO9k6XHP69OmoV68e/P39MXToUMTHx2PMmDGoUKGC0qVNO3fujN9++w3BwcEYNWoUnj59il27dmU4zyG7/vzzT7i5uSEwMBA//fST4pv+7Px/l93MwOeLSTRu3Bh//PEHEhIS0KFDB6XX58yZgwYNGqBevXoYOXIk7O3t8eTJE+zbtw/GxsbfvFLU93Tt2hULFy5EuXLlYG9vj9DQUEWR/FrRokVx6tQpJCYmwtnZGREREdizZw/27t2r8cNtuSeBiPKVxYsXw8XFBWXLlhU6Sq75+fnByMgIy5YtEzoKkSB0dXXh4uKCEiVKfHe5mjVrws7OTum5pUuXYt26dbhx4wZ+//13rFy5Evr6+kqHINWtWxcHDx7E69evMW7cODx58gTbtm2Dk5OT0gmwxsbGcHFxyXAhAUtLS7i4uHy3xKevw6VLlxRXN5o0aRLi4+Nx4sQJjB49Osd5AGDNmjVYuXIlLl26hN9//x1Hjx7FggULFAXh68xisRgrV65E3759MWvWLNy+fRsikQh79+5FcHAwFi9ejCVLlmDw4MEoX748xGIxDA0Nv7v+AFCpUiVcunQJTk5OmDp1KpYsWYLg4GCcPn1a6dwGU1NTHDt2DEWKFMH48eNx/fp1rF69GpUqVVK6wpOtrS2qV6+eYZ5y5cqhUqVKiseOjo44deoUzMzMMGnSJJw6dQohISGZbitVM6fr168fKlWqhICAAFStWlXpteLFi+PixYvo2rUrwsLCMGbMGOzfvx/NmzfHokWLslwv4POVt748UXvGjBkYN24cwsPDMWPGDNSvXx+//fYbXFxcUKRIkQzvX7x4Md68eYMJEybg1atXOHjwIJo0afLdbaAOInlubidI+d6lS5fg4uKCixcvau0qMenfEJmYmGTrBB/6n8K+7ZKTk1GmTBmMHz8eDRo0yNF7dXV1UaJECbx69QqpqanfXTYhQYTatT9fMeLs2RswMtLcj8kjR45g8uTJiIqKUnzbKIj4+M/XsAQ+nwH7RZbC/vdOVdxuquO2U93X2y45OTlDyfHz88ObN28y3FW4sMtrf+9GjBiB9evXK13BSx2y+9mPhxsRUb6xadMmmJiYZLiOd37m5eWFlStXYu7cuRgzZozQcYiogOnXrx/KlSsHd3d3JCYmIiwsDAcPHsTWrVuFjkZ5HA83IqJ8QS6XY9asWejUqVO2riCRX4hEIgwZMgRTp0795p1UiYhU9ffff0Mmk2HmzJmYOnUq9PX1ceLECcVNyijv+t4hTNrAPQlElC8cO3YMjx49QosWLYSOonZubm6oUaMGJk2ahL///lvoOERUgJibmyvdhZnyj8GDB2Pw4MGCzV9wvo4jogJt0qRJ6NSpU76+7On3DB48GEuXLlVclYWIiEhILAlElOedO3cOZ86cQadOnYSOojEVKlRAkyZNeF4CERHlCSwJRJTnTZ48Ge3atfvuXTYLggEDBmD79u284ggREQmOJYGI8rTLly8jMjISXbt2FTqKxpUsWRIdO3bEyJEjhY5CRESFHEsCEeVpv/zyC4KCglCsWDGho2hFz549cfHiRURGRgodhYiICjGWBCLKs44ePYozZ86gV69eQkfRGnNzc/Ts2ROjRo2CTCYTOg4RERVSLAlElCfJ5XL88ssv6N69e4E/F+FrHTt2xJs3bxAeHi50FCIiKqRYEogoT1q7di2ePn2KLl26CB1F6wwMDPDTTz9h7NixSElJEToOEREVQiwJRJTnfPjwASNGjMCoUaMK7H0RstKiRQvo6upi0aJFQkchIqJCiCWBiPKcsWPHwtHRET4+PkJHEYxEIsHQoUPx559/4sOHD0LHISKiQoYlgYjylCNHjiAkJAS//vorRCKR0HEEVa9ePVSqVAkTJ04UOgoRERUyLAlElGdER0cjODgYw4cPh52dndBxBCcSiTBs2DAsXrwY9+7dEzoOEREVIiwJRJQnyOVy9O7dGxUrVkS7du2EjpNnVKxYEYGBgejXrx/kcrnQcYiIqJBgSSCiPOG3337DuXPnMH78+EJ/mNHXBg0ahOvXryM0NFToKEREVEiwJBCR4JYuXYqFCxdi4cKFsLKyEjpOnmNqaopffvkFQ4YMwZs3b4SOQ0REhQBLAhEJRi6XY8aMGRgxYgTmzp2LsmXLCh0pz2rcuDFq166N9u3bIy0tTeg4RERUwLEkEJEgEhIS0K9fP0yfPh0rVqyAi4uL0JHyNJFIhHHjxuHly5cYNWqU0HGIiKiA0xE6ABEVPqdPn0a3bt1gYmKC9evXw8bGRuhI+YKRkRFmz56N4OBg6OjoYOrUqRCL+V0PERGpH3+7EJHWvHjxAl27doW3tzeaN2+O1atXsyDkkJ2dHdauXYvw8HAEBQXxHAUiItIIje1JSE5LxrA9w775ei+3XqhRqobSc7ff3sah+4fwOOYxRBChpGlJVC5eGY3KNoKuRDfLcV1Lu6KHS48My4hFYlgaWqKWbS34V/SHWKSebrTi/ApcfnE5y+V+8fwFxU2KK2WSiCUoaVoStW1ro1G5RkrLj40ci5jEmAzj9KvdD1VKVFE8TpWmYv+9/bj84jLiU+JRxqIMWlVuhVJmpVRfKSINiI+Px4wZMzBz5kw0aNAAO3bsQMmSJYWOlW/Z29tj7dq1+PPPP1GuXDkMHz4cffr0QalS/LdPRETqobGSkCZLw/57++H7gy9aVmqZ4fWSpv/7gJCclozfD/6OXbd3oYVjC7jauEJPoofbb2/j94O/YxzGYXvwdpQ0LfndcUualcx07jR5Gm68voGR+0biwN0DmB8wXy3r6GLjAhuz/30LuuPWDhy8fxAzms6AoY6h4nlzA/MMmVJlqbj68ir67+gP/4r+mNNsjmL5Y1HH4FTcCa0qt1Kar4RpCaVt1iykGaqXrI4apWpALBIj8l4k5p+aj7nN56JJhSZqWUei3JDJZFi7di3GjBmD4sWLY8mSJahevbrQsQqEIkWKYO7cubh48SKWLVuGyZMno1GjRujWrRsCAwNhZGQkdEQiIsrHNH5OQlmrsmhasel3lxkbORaR9yIR3jEcVUtUVXptQJ0BGLV/FBJSEnI87pfLtHBsATN9M8w6MQvdnnWDa2lXFdZG2dd7Qq6/vg4A8CnnAzMDM6XX4lPiM2QKqBQAEz0TLDizAJ2rdVbKZG9p/9310xHrYGOHjbA2tlY816FqB7QNbYspx6ewJJDgIiMjMXLkSLx//x7Dhg1DkyZNeP8DDXBxccHSpUvx6tUr7N69G3/88Qf69euHbt26YcCAAXB0dBQ6IhER5UOCn5Nw881NbLu5Df3r9M9QEADAzMAMCwIWqOUQmlqlawEAbr29leux1KW+fX0An7dDTkjEEqWCAHy++kkF6wp4n/BebfmIckIul+Pw4cPw9PRE+/bt4evrix07dsDPz48FQcNKlCiBXr16YevWrVi8eDEeP36M6tWro0mTJtizZw8vm0pERDki+NWNIu9FAgD8K/p/cxkdsQ50xLmPGp/6+dt8iUiS67HUJX0Pg7GesdLz55+dx/C9wyEWieFU3AmtK7fOsHfia+8T3uPfR//C5wcfjeUlysyjR4+wdetWrFy5Ei9evEDnzp0xZcoUmJl9/+8sqZ9IJEK1atVQrVo1DBs2DJs2bUKvXr0gl8vRtm1beHt7o1atWihVqhSLGxERfZPGS8LB+wfx+MPjDM9PazoNJnomePD+AXTEOrCzsMv1uOO8x6G4SfFMl0+TpSH0aih0xDpoYN8gR3NpSqo0Fesur4O+jr5iLwcAmOiZoFqJanAu7oy3CW+x/NxyLDu3DOvarUO5IuWUxjj5+CRCr4QiLiUOV19dRRunNhjl8e1rqCcnJyM5OVnxOC4uTv0rRgVKcnIyPn78iA8fPiA6Ohrv3r3Dq1ev8PTpU9y8eRMXL17Es2fP4OLigk6dOsHX15fHw+cR1tbW+Omnn9C3b1+cPXsWx44dwy+//IL79+/D0NAQpUuXRtGiRVGsWDGUtrTEvP9/X2RkJEqVL4+SJUvyDthERIWUxktCOatyaO7YPMPzehI9AEBSWhL0JfoZXv/6ykEtK7dE4/KNvzvu19/GpxeJNHka7ry9gxRpCua3mA9bC9tcrVNufJnp1ptbSExNxNQmU5UyhXUIg5XR/34xt6/SHn6r/TAmcgzCO4YrjWdnYYfmjs3xIfEDJGIJIv6LQG3b2krb6ktTpkzBhAkTNLNyOeTi4oKrV68KHSNfqVatGo4fP57ntl2NGjWgq6uL/fv3Y//+/ULHyVS5cuUwd+5cTJkyBQ8ePMhy+fqfjwTE8OEaDqZlxYsXh4WFBZ48eYK7d+/i7t27itcUl3Ro3VrpPXn1711ex+2mOm471XHbqU7IbSeXy7U6X3YIfuKyhaEFElITkCJNURQH4H9XDopNjsWYyDFKl/7MzrjA/4pEsjQZxrrG2Hd3H9JkWR+X++eRP/H602vF4+AawahTpk6W78uO9EwSsQQl6pRA+SLlYahrqLTMlwUBAIoYFUFg5UCsurgKH5M+wtzAXPFaafPSKG1eGgDQqXon/LzzZ4zcNxJ1bOtkenjS6NGjMWzY/y7FeuXKFXh6eqpl3XKqSpUq/MY5h8qV+7wnKbfbLv0wE5FIBJFIBB0dHUgkEujr60NfXx/GxsYwNjaGubk5zM3NYWFhAX39jGU+P7GwsAAAtGjRAjExMYJmyS/i4uLw+vVrGBp+/hnl4eGBYsWKIS0tDWlpaZDL5ZDJZADy5i84oanr32thxG2nOm471Qm17apUqZL1QgIQ/JyEaiWqYeuNrbj+6jpcbFwUz6dfOeht/FuVx/6ySARWDoRIJMKofaNQtURVxQfrzHiV9UJc8v8Ow7E1V9+eh+yUm8xYG30+STk2KVapJHytcfnG2Hd3H+68uwO30m4ZXk//EJjOxMQkx1nUZc2aNZBI8s75IfmBVCpFXFwct50K0rddYGAgt10OpW+7OXPmcNvlAP+9qo7bTnXcdqrjtlMm+NWNmjs2h7GuMdZfWa/xuX71/BVyyDHn5JzvLtfAvgGaVmyq+GNjLvwdYW++vQl9HX0UMyn23eWiE6MBAGb6PGGUiIiIiFQjeEmwMLTAn75/Yvft3ZhzYg6S05KVXn8R+0JtcxU1LooOVTtg161dePThkdrGVadLLy7h6kvl4+AO3j+IvXf2oq1TW+jrfN4LcPH5RVx/dV1puScxT7Di/Ao4FXNCBesKWstMRERERAWLYFc3+vJE5MDKgTA3MMfUY1Ox7so6OBd3hp5ED68+vcKD6AdoXL4xPBw81JKnb62+CLsWhkVnFmF60+lqGVOdTPVM8cehP/A6/jXsLOzwJu4N7r2/h7bObTHWa6xiORM9E0w4MgFv4t6gjEUZxCbH4sbrG2hUthF+b/Q7L21IRERERCrTWEkw0DHAghYLvvn6D0V+UHrsVdYLXmW98PDDQzyJeQK5XI4SpiVgY2YDU33TDOOWtSqb5dyZLVPMpBjWtFmDj8kfVVir72tZqSWqFK+S4UTkrDJ9qbx1eYR2CMWzj89w//196Ih1ULlY5QwnM1csWhGhQaF4/vE57kffh7GuMews7VDUuKha14mIiIiICh+NlQSJWKLSCboOlg5wsHTI1bhZLVPLttY3X8uNikUromLRiipl+tqXVy36HhtzmzxxzgQRERERFRyCn5NARERERER5C0sCEeUJcrkct27dQkJCgtBRiIiICj2WBCIS3Lp161C1alVUrVoV1tZ2EIkAkQiIjxc6mcDi48GNQUREQmBJICJB/fnnnxg8eDDatWuHkydPYvny5YrXEhMTBUxGRERUeLEkEJFg/vzzT8yfPx8rVqxA69atYWRkhPLlyyte79mzJ2QymYAJiYiICieWBCISxPHjxzFt2jQsW7YMFSpkfvO/s2fPIiwsTMvJiIiIiCWBiLTu06dP6NatGwYNGqS05+Br/fr1w+jRo5GUlKTFdERERMSSQERa9+uvv6JEiRLo2LHjd5fz8/ODiYkJ5s2bp6VkREREBLAkEJGWPXz4ECtXrsTYsWMhFn//R5BYLMawYcMwefJkxMTEaCcgERERsSQQkXZNmjQJTZo0gYPDt++s/qU6derA0dERCxYs0HAyIiIiSseSQERaExUVhQ0bNqB37945et+PP/6Iv//+mzdaIyIi0hKWBCLSmilTpqBJkyawt7fP0fvq1q2LEiVKYMWKFZoJRkREREpYEohIK2JjYxEaGorg4OAcv1ckEuHHH3/E9OnTkZycrIF0RERE9CWWBCLSio0bN6Js2bJwdHRU6f0+Pj4wMjLCqlWr1JyMiIiIvsaSQERasWzZMrRq1Url94vFYvTr1w+TJk3ifROIiIg0jCWBiDTu6tWruHnzJvz9/XM1jo+PD8zMzLB8+XI1JSMiIqLMsCQQkcatXLkSjRs3homJSa7GEYvF6N+/PyZNmsT7JhAREWkQSwIRaZRMJsPmzZtzvRchnZeXFypUqICRI0eqZTwiIiLKiCWBiDTq9OnTSEpKgpubm1rGE4lE+P333xEWFoajR4+qZUwiIiJSxpJARBoVERGBRo0aQVdXV21jlipVCgMHDkSPHj3w7t07tY1LREREn7EkEJHGyOVyREREwMfHR+1jd+zYEY6OjggICODVjoiIiNSMJYGINOb8+fOIjY1FnTp11D62WCzGpEmTkJCQgO7du0Mmk6l9DiIiosKKJYGINCYiIgINGzZU66FGXzIwMMDcuXNx+vRp/PrrrxqZg4iIqDBiSSAijdm1axc8PT01OkeRIkWwcOFCrFixAgsXLtToXERERIUFSwIRaURUVBQePHiAunXranwue3t7/P333xg5ciRu3ryp8fmIiIgKOpYEItKIPXv2wNXVNdc3UMuumjVrokOHDujRowekUqlW5iQiIiqoWBKISCN27tyJBg0aaHXOn376CW/evMHcuXO1Oi8REVFBw5JARGoXFxeHf/75Bx4eHlqd18DAAKNHj8akSZMQHx+v1bmJiIgKEpYEIlK7Q4cOwcbGBnZ2dlqf293dHaVLl8aKFSu0PjcREVFBwZJARGq3e/durR9qlE4kEqF79+6YOXMmUlNTBclARESU37EkEJFayeVy7Nu3D/Xr1xcsg7e3N3R0dBAWFiZYBiIiovyMJYGI1Or69euIiYmBi4uLYBkkEgm6dOmCefPmCZaBiIgoP2NJICK12rdvH+rUqQM9PT1BczRt2hQ3btzArVu3BM1BRESUH7EkEJFa7d69G/Xq1RM6BszMzODl5YU1a9YIHYWIiCjfYUkgIrX5+PEjzpw5I+j5CF9q0aIF1q1bx5urERER5RBLAhGpzcGDB+Hg4IBSpUoJHQXA58uhSqVSHDp0SOgoRERE+QpLAhGpTV451Cidjo4OmjVrhpCQEKGjEBER5SssCUSkFjKZDHv27IGnp6fQUZQ0adIEu3btQlJSktBRiIiI8g2WBCJSi3PnziE1NRXVq1cXOoqSypUrw8rKCpGRkUJHISIiyjdYEohILXbt2oX69etDR0dH6ChKRCIRfH19ER4eLnQUIiKifIMlgYjUYteuXfDw8BA6RqaaNGmC3bt3IzExUegoRERE+QJLAhHl2pMnT3Dz5s08ddLylxwdHWFtbc2rHBEREWUTSwIR5dquXbtQs2ZNmJubCx0lU+mHHG3btk3oKERERPkCSwIR5drmzZvh7e0tdIzvaty4MQ4cOMBDjoiIiLKBJYGIcuXNmzc4ceJEni8Jjo6OKFKkCK9yRERElA0sCUSUK9u3b0fVqlVRokQJoaN8l0gkgo+PDyIiIoSOQkRElOexJBBRrmzatCnP70VI17hxY95YjYiIKBtYEohIZe/fv8fx48fh4+MjdJRsqVy5MiwtLXnIERERURZYEohIZVu3bkWlSpVgY2MjdJRsST/kaOPGjUJHISIiytNYEohIZatXr0azZs2EjpEj/v7+2LlzJxISEoSOQkRElGexJBCRSu7fv48LFy7A399f6Cg5UrFiRRQvXhx79uwROgoREVGexZJARCpZu3YtPDw8YGlpKXSUHBGJRPDz88OGDRuEjkJERJRnsSQQUY7JZDKsXbsWLVq0EDqKSvz8/LB//358/PhR6ChERER5EksCEeXY8ePHERsbCw8PD6GjqMTBwQHlypXDtm3bhI5CRESUJ7EkEFGOLVy4EIGBgdDV1RU6isqaNWuGVatWCR2DiIgoT2JJIKIcef78OXbt2oX27dsLHSVXmjdvjrNnz+LBgwdCRyEiIspzWBKIKEeWLl0Kd3d3lC5dWugouWJlZQVPT0+sXr1a6ChERER5DksCEWVbamoqli1bhqCgIKGjqEXLli2xZs0aSKVSoaMQERHlKSwJRJRtmzdvhoGBAerVqyd0FLWoV68eUlJScPDgQaGjEBER5SksCUSULXK5HNOnT0eXLl0gFheMHx06Ojpo06YN5syZI3QUIiKiPKVg/KYnIo07duwYHj9+jJYtWwodRa2CgoLwzz//4MaNG0JHISIiyjNYEogoW2bMmIF27drB0NBQ6ChqZW1tjWbNmmH27NlCRyEiIsozWBKIKEs3btzAkSNH0KlTJ6GjaESXLl0QGhqK169fCx2FiIgoT2BJIKIsTZs2DS1atIC1tbXQUTTihx9+gLu7OyZNmiR0FCIiojyBJYGIvuvx48cIDw9H9+7dhY6iUYMHD8aKFStw9+5doaMQEREJjiWBiL5r1qxZ8PLygp2dndBRNKpcuXJo2bIlRowYIXQUIiIiwbEkENE3vX37FitXrkSPHj2EjqIV/fv3x9GjR7F//36hoxAREQmKJYGIvmnWrFlwcXGBk5OT0FG0okiRIhg5ciS6d++Od+/eCR2HiIhIMCwJRJSp6OhoLFiwAH369BE6ila1atUKVatWRc+ePSGXy4WOQ0REJAiWBCLK1Jw5c1CtWjVUr15d6ChaJRKJMG7cOJw7dw6zZs0SOg4REZEgWBKIKIPo6GjMmzcPvXv3FjqKICwsLDBnzhz88ccfOHDggNBxiIiItI4lgYgymDx5MqpUqQJXV1ehowjG2dkZv//+O4KCgnDnzh2h4xAREWmVjtABiChvefz4MRYvXoy1a9cKHUVwzZs3x/379xEQEICzZ8/CwsJC6EhERERawT0JRKTk999/h4+PDxwdHYWOkicMHDgQJUuWRIcOHSCVSoWOQ0REpBUsCUSkcPLkSURERGDAgAFCR8kzJBIJpkyZgnv37mHcuHFCxyEiItIKlgQiAgAkJyejV69e6N+/P2xsbISOk6eYmppi9uzZmDdvHnbu3Cl0HCIiIo1jSSAiAMCUKVMgFosRHBwsdJQ8qVy5cvjjjz8QHByM+/fvCx2HiIhIo3jiMhHh33//xfTp07FmzRro6PDHwrf4+fnh+vXraN26Nc6cOQMjIyOhIxEREWkE9yQQFXLPnj1DmzZtMGLECFSuXFnoOHnekCFDoK+vjz59+vCOzEREVGCxJBAVYjExMWjZsiUaNGiAdu3aCR0nX9DV1cWMGTNw+PBhTJs2Teg4REREGsGSQFRIRUdHw9vbG6amphg7dixEIpHQkfINa2trzJs3D5MnT0ZERITQcYiIiNQuRwcf7769G0vOLsGSwCUobV5a8fzb+LcYtmcYxCIxZvnPgrWxtWLZdHoSPdiY2SCgcgB8f/DNdPz77+9jyO4hKGJUBCHtQjJdJjktGZuub8LpJ6fxJv4NSpqUhFtpN7R2bg0TPZOcrM53ZTXPlGNTcPLxySzHmdNsDmzNbdE2tC3aVmmL7jW7K70+89+ZOP7wOAbXHQyfH3wAAKnSVPzz6B/suLkDUdFR6FStEzpV75Rh7OwuR/S127dvo23btihRogRmzJgBXV1doSPlO5UqVcKUKVPQrVs3mJiYwM/PT+hIREREapOjkhCdEI1bb28hOS1Z8dzLTy8RvCkY8SnxWNd+HayNrZWWXdxyMUqbl8an5E/YfXs3+m3vh0m+k9CxWscM42/5bwtuv70NOeS48PwCXG1clV5PTE1E29C2iEmMwQD3AahoXRFPYp5g8dnFWHJuCU71O6XKNsggO/ME1whGy8otFe8JuRSCiP8isLHDRhjrGSuetzW3hVQuxa23t/Am7o3ieblcjvGHx2P9lfX4zes3RUEAgPZh7VHEqAh8fvDBnjt78Db+baY5s7scUbq0tDSsXLkSw4cPR4cOHTBgwAAWhFxo2LAhxo8fjzZt2mDz5s3w9/cXOhIREZFa5OoyJs8+PkOXTV0glUkR1iEM9pb2GZYpZ1UO5YqUAwDUtq2N/17/h3mn5mUoCTK5DDtu7UBzx+Y48fgEtt7YmqEkbLu5Dbff3sbGDhvhVtoNAOBi4wK/Cn4YfWB0blYlx/N8uScFAIoYFQEAVLSuCDMDM6XX4lPiM6zr2Mix2Hx9Myb6TMzw7f/69uthrGeM2KRYjI0c+82c2V2OKCkpCdu2bcOff/6JhIQEzJo1C/Xq1RM6VoHQtGlTiEQitG3bFhMmTMCIESN46BYREeV7KpeERx8eIXhTMHTEOtjYYSNszLN386XKxSrj2qtr+JT8Cab6pornTzw6gddxr9GycksY6Rlh7529+KPRH9DX0Vcs8/zjcwCAY1FHpTENdQ0x03+mqquSgSbnkcqkGLV/FHbe2olpftPQxrlNhmW+3BPxPdldjgofuVyOp0+f4tixYzh06BB27NgBKysrdOrUCW3btuXeAzXz8/ND6dKlMXz4cBw9ehSzZ8+Go6Nj1m8kIiLKo1Q6cflB9AN0Cu8EA10DhHUIy3ZBAID3Ce8hEUmUPvwDwNYbW2FuYI76dvXRrGIzfEr+hIP3Dyotk75HYu+dvRnG1RGr79rumponTZaGIXuGYPft3ZjdbHamBYFIFcnJyThz5gxmz56NNm3awMbGBmXLlsWsWbOgr6+PRYsWYefOnejYsSMLgoY4OzsjPDwcZmZmqF69Ojp16oSdO3ciISFB6GhEREQ5ptIn3qF7hsLayBphQWGKcxCy48KzCzgWdQx+FfygJ9FTPJ9eCAIqBUBXoos6tnVgbWSNrTe2orljc8VyAZUCsPv2boyJHIPN/21GvTL1UK1kNbiXcYehrqEqq5IpTc2z4coGJKUlYV7zeWjm2ExteXMiOTkZycn/O6ckLi5OkByUc3K5HJ8+fcLDhw/x/v173L9/H9evX8eFCxdw9epVGBkZoVq1aqhWrRqaN28OJycn3uxLyywsLPDbb78hODgYmzdvxs8//4znz5/DwcEBZcuWRdGiRWFlZQVzc3OYmZnBzMwM5ubmsLCwgKWlpeI1c3Nz6OvrgwctERGRUFQqCbbmtoiKjsK5Z+fgX/H7J+r139EfehI9fEr+hOexz9HMsRn+avyX0jJ77+xFUlqSYiyJWIKmFZoi9Goo3sa/RVHjop/DinWwqs0qnHpyCkcfHMWZp2ew9NxS6Ep0MbLBSHSt2VWV1clAU/OUNiuNB9EPsOfOHjSp0EStez+ya8qUKZgwYYLW5/2ahYWF0BEKHGtra9y/fx/379/Hli1bhI6TK6X//5SfNmrc2fbljc+0dc6AXC6HSCTCgwcP8ODBg9wNZqK+q7cREVHekhdvzqnSp9RZ/rMw+ehkDNszDHoSPaUr83xtRIMRKG1eGu8T3mPJ2SU49fgUXse9Rlmrsopltt7YCitDK7iXcVc819yxOdZdWYcdN3egl1svpTHrlqmLumXqAgBiEmMwNnIsJhyZAHtLe3g4eGSao9/2fnj28Zni8ZB6Q76bW9V5vsf7B28Emwbjj8N/YMTeEZjdbDbEIu3eqmL06NEYNmyY4vGVK1fg6emp1QwAsH37dty4cQNiMW/V8bX0D7BisRhisRi6urrQ09ODqakpjI2Noa+vDyMjI0gkEoGT5i9SqRSJiYkwNDTM09suOTkZsbGxSEhIQEJCAlJSUpCSkgKZTAapVAq5XK71XyYymQwpKSnQ09Pjv9kc4HZTHbed6rjtVCfUtsur57CpVBIMdAywvPVydI/ojoG7BmJJ4BJ4OmT+QfPLqxu52riiyeomGL53OLZ23gqRSIQnMU9w4fkFGOsaI3BdoOJ9cnz+Jbj1xtYMJeFLFoYWmOAzAfvv7cexh8e++eF9ZIORSJb+7zCbUqalcrTO2Z0nK11qdEGyNBl/HfsLehI9TPObptUroejr60Nf/3/ng5gI9O1kw4YN0bx58zz9YS0vkkqliIuLg4mJCbddDnHbqY7bTjXcbqrjtlMdt53quO2UqXy8i7GeMVa1WYXgTcH4acdPWN56ueJb928x1DXE0HpDMWLfCOy/ux9NKzbF1htbIYIIiwMXw9LQUmn5ww8O4++Tf+PG6xtwKu6EIw+OwNXGNcMlRmVyWZZ504tKduRmnuzo6doTyWnJmHViFvR09DDJd5JaxiUiIiIiUodc7Usx1TdFSLsQOFg6oO+2vrjw7EKW7wmoFAB7S3ssOLMAMpkM229uR02bmqhnVw+Vi1VW+tOlehfoinWx7eY2AMDll5cRuD4Q/zz8RzFebFIsJh6dCBFEWZ4fkV3amOenOj/h5zo/I+xqGCYemZjr8YiIiIiI1CXXZ86aG5gjpF0IOod3Rs+tPbG23VpUK1ntm8tLxBL8VPsnjNo/CgvPLsTTj0/R2613pstaGlqigX0D7Ly1E796/orWlVvjY+JHDN0zFGKRGGYGZngR+wKlzUtjYcuFGW6+piptzTO0/lAkS5Ox/Pxy6OvoY5THKADAzH9n4ljUMUjlUgBA6NVQxeVgQ4NCFXs4srscEREREVFOiOQ5OAMuOiEar+JeoZxVuQz3OYhNisWz2Gcw1jOGnYXdd5eVyqS48+6O4sofZS3LwkDXINM538W/w5v4NxnGeRv/Fh8SP6CIURHF3Y41IbvzvIl7g3cJ71DRuiIkYuXj2GRyGW6/vQ1rI2sUMymW4b133t6BVC5F+SLloSvRxYvYF4hJisl0ni/Hz+5y33Pp0iW4uLjg4sWLqFmzZpbLqwOP+VMdt53quO1Ux22nGm431XHbqY7bTnWFZdtl97NfjvYkWBlZwcrIKtPXzAzMUNmgcraWlYglqFyscqavfc3a2DrTezEUNS6quDSqJmV3nmImxTItAAAgFom/u74Vi1ZUelzKrBRKmWV9YnV2lyMiIiIiygleG4uIiIiIiJRo/25eRKQWUqkUFy9exNOnT2FjY4M6deoIHYmIiIgKCO5JoLwlPh4QiT7/iY8XOk2elZiYCH//dqhduxbatm2DRo1aYNGiRULHIiIiogKCJYEon4mLi0PTpk3x+vVrxXPz5s3D2LFjMXXqVAGTERERUUHBkkCUz4wfPx6JiYmYN2+e4rmqVatiyZIl+PPPP/H48WMB0xEREVFBwJJAlI+cOXMGGzZswIQJE2BgoHzZYCcnJ/j6+uK3334TKB0REREVFCwJRPlEamoq+vXrh169esHOzi7TZQYMGICIiAhcvnxZy+mIiIioIGFJIMon1q5di/j4ePTo0eOby5QqVQpBQUH4888/tZiMiIiIChqWBKJ8QCqVYurUqejZsyd0dXW/u2xQUBD27NmjdGIzERERUU6wJBDlA1u2bEFCQgL8/f2zXNbW1hY1a9bEunXrtJCMiIiICiKWBKI8Ti6XY9KkSejevXuWexHSBQYGYvny5ZDL5RpOR0RERAURSwJRHnf48GG8ePECrVq1yvZ7fHx88OrVK5w+fVqDyYiIiKigYkkgyuPmzZuH1q1bZ7jk6fcYGBigadOmCAkJ0WAyIiIiKqhYEojysEePHuHAgQNo3759jt/r4+ODHTt2QCaTaSAZERERFWQsCUR52OLFi+Hp6YkSJUrk+L0uLi5ISkrC+fPnNZCMiIiICjKWBKI8KikpCStWrECHDh1Uer+uri4aNGiAbdu2qTkZERERFXQsCUR51KZNm2BlZQU3NzeVx2jYsCG2b9+uvlBERERUKLAkEOVRCxcuRNu2bSESiVQeo379+nj48CHu3r2rxmRERERU0LEkEOVBV65cwfXr1xEQEJCrcYyNjVGnTh3s3LlTTcmIiIioMGBJIMqDFi1aBH9/f5iamuZ6rLp162L//v1qSEVERESFBUsCUR4TGxuL0NBQlS57mhl3d3ecPHkSSUlJahmPiIiICj6WBKI8ZtOmTbC3t0flypXVMp6DgwPMzc1x6tQptYxHREREBR9LAlEes3z5cgQGBqptPJFIhDp16iAyMlJtYxIREVHBxpJAlIfcvHkT165dg7+/v1rHrV27Ng4ePKjWMYmIiKjgYkkgykNWrlwJb29vmJmZqXVcd3d3XLlyBdHR0Wodl4iIiAomlgSiPCItLQ1r165V66FG6aytrfHDDz/gyJEjah+biIiICh6WBKI84vjx4xCJRKhVq5ZGxq9VqxYOHz6skbGJiIioYGFJIMojtmzZgkaNGkEs1sw/S5YEIiIiyi6WBKI8QCaTYdu2bfDx8dHYHC4uLnjw4AFevHihsTmIiIioYGBJIMoDTp8+jaSkJLi4uGhsDjMzMzg7O+Po0aMam4OIiIgKBpYEojxgy5Yt8PLygq6urkbncXFx4SFHRERElCWWBCKByeVybN26Fd7e3hqfq3bt2iwJRERElCWWBCKB3bt3D69evULt2rU1PleNGjXw4sULPHz4UONzERERUf7FkkAksEOHDqFmzZowMDDQ+FxGRkaoXr069yYQERHRd7EkEAksMjJSY/dGyEytWrVw4MABrc1HRERE+Q9LApGA0tLScPToUbi7u2ttzrp16+LQoUOQSqVam5OIiIjyF5YEIgFdvHgRYrEYjo6OWpvT2dkZMpkMFy5c0NqcRERElL+wJBAJ6NChQ6hduzYkEonW5pRIJHB3d+chR0RERPRNLAlEAjpw4IBWrmr0NXd3d+zfv1/r8xIREVH+wJJAJJDExEScPXtWkJJQt25dnDt3Dh8/ftT63ERERJT3sSQQCeTs2bOwtLSEra2t1ucuWbIk7O3teSlUIiIiyhRLApFAjh07BldXV4hEIkHmb9CgAXbs2CHI3ERERJS3sSQQCeTYsWOoWbOmYPN7eXlh9+7dSEtLEywDERER5U0sCUQCSE5OxtmzZ+Hq6ipYhmrVqgEATp8+LVgGIiIiyptYEogEcO7cOZiamsLBwUGwDBKJBB4eHti+fbtgGYiIiChvYkkgEsDx48fh4uIi2PkI6Ro2bIgdO3ZALpcLmoOIiIjyFpYEIgEcPXpU0PMR0rm7u+P58+e4deuW0FGIiIgoD2FJINKylJQUnD59WtDzEdIZGRmhXr16iIiIEDoKERER5SEsCURadvbsWRgbG+OHH34QOgoAwNfXF+Hh4ULHICIiojyEJYFIy44cOQI3NzfBz0dI17BhQzx48ICHHBEREZECSwKRlh0+fDhPHGqUztjYGA0aNMCmTZuEjkJERER5BEsCkRYlJibi7NmzqF27ttBRlPCQIyIiIvoSSwKRFp06dQpWVlYoU6aM0FGUeHp64uHDh7hx44bQUYiIiCgPYEkg0qIjR46gVq1aeeZ8hHTGxsbw9PTEhg0bhI5CREREeQBLApEWHTp0KE+dj/Cl5s2bY8OGDZDJZEJHISIiIoGxJBBpyfv373Hx4kXUq1dP6CiZqlevHj59+oSTJ08KHYWIiIgExpJApCWRkZGoUKECihUrJnSUTOnq6qJJkyZYt26d0FGIiIhIYCwJRFqyb98+1K1bV+gY39WsWTNs2rQJycnJQkchIiIiAbEkEGmBTCbD/v37Ub9+faGjfFe1atVgYWGBHTt2CB2FiIiIBMSSQKQFly9fRlJSEqpVqyZ0lO8SiURo1aoVli5dKnQUIiIiEhBLApEW7N27F3Xq1IGurq7QUbLUsmVLnDhxAlFRUUJHISIiIoGwJBBpwa5du/L8oUbprK2t4enpiRUrVggdhYiIiATCkkCkYU+fPsXly5fh5eUldJRsa926NVatWoXU1FShoxAREZEAWBKINGzr1q1wdXWFpaWl0FGyzd3dHfr6+oiIiBA6ChEREQmAJYFIwzZv3gxvb2+hY+SIRCJB586dMX36dMjlcqHjEBERkZaxJBBp0KtXr3DmzJl8VxIAIDAwEA8fPsTx48eFjkJERERaxpJApEHbt29H9erVUbRoUaGj5JiRkRHat2+PGTNmCB2FiIiItIwlgUiDNmzYAB8fH6FjqKxjx444cuQILl++LHQUIiIi0iKWBCINuX//Ps6dOwd/f3+ho6isaNGiCAoKwpgxY4SOQkRERFrEkkCkIatXr4anpyesrKyEjpIrPXv2xIkTJ/DPP/8IHYWIiIi0hCWBSAOkUilCQkIQGBgodJRcs7S0RLdu3fDLL7/wSkdERESFBEsCkQYcOnQIqampqFu3rtBR1KJr16549OgRQkJChI5CREREWsCSQKQBCxcuRIsWLaCjoyN0FLUwMjLCr7/+iuHDh+Pdu3dCxyEiIiINY0kgUrNbt24hMjISnTp1EjqKWnl7e6NGjRoYOnSo0FGIiIhIw1gSiNRs6tSpaN68OYoVKyZ0FLX79ddfsXPnTmzevFnoKERERKRBLAlEavTkyRNs3LgR3bt3FzqKRpQoUQLjx49H79698ejRI6HjEBERkYawJBCp0cSJE+Hl5QV7e3uho2iMr68vmjRpgqCgICQnJwsdh4iIiDSAJYFITU6ePIkNGzZg8ODBQkfRuJEjRyIxMRF9+vThZVGJiIgKIJYEIjVISUlB79690bdvX9ja2godR+MMDAzw999/IzIyEjNmzBA6DhEREakZSwJRLsnlcowaNQoymQxdu3YVOo7WFCtWDH///TcmTpyI9evXCx2HiIiI1KhgXMSdSEC///47wsLCsGrVKujq6godR6ucnJwwe/Zs9OnTBxYWFmjevLnQkYiIiEgNNFISVtVbhRcXXyge6xrpwsLOAlWDq6L2oNoQ64gzXVYkEkHPVA8la5ZE/V/rw76hfabj7xmwB5dXXkbtwbXhO803w+sXllzA/iH7M31vx10dUc63XC7WTnmO/tf6o0iFIornX19/jQ1NN8C4mDG67O8C42LGGfLo6OvA3M4cVTpXQd3hdZW2R7pnZ55hTcM1MC5mjMEPB0MsybjMp5ef8O9f/+LRkUf49PITzEqbwc7DDrV+rgVrR+tcryN9X3R0NEaOHIkdO3Zg1apVsLOzEzqSINzd3TFp0iQEBQUhPDycRYGIiKgA0EhJSEtOQ4lqJdD9eHcAQEpcCu7uuYtdvXYh7lUcfKf7ZrqsXC5HzKMYHBhyAOv91qPv5b4oWqmo8thJafgv9D9ADlxbew3ef3ln+AAtS5NBmixFv6v9lD7AA4BET6KWdUyfQy7730mbLy+9xLrG62BW2gxdDnSBcVHjTPMkfUzCf2H/4cDQA0iNT4XXn14Zxr+y5gogB2KfxuLBgQco719e6fX4t/FY5rIMJsVN4L/IH8WrFEf0g2gc/f0oVrqvxC8fflHLelJGMTExWL58OaZNmwZnZ2esX78epUuXFjqWoBo3bgyRSIT27dsjJCQE7dq1EzoSERER5YLmzkkQAToGOtAx0IGRtRGqd6uO8s3K43ro9W8uq2uoi6KViqLFihaQJktxcdnFDIve3nEbSTFJaPhnQ8S9isODyAffjCDRkygypP8RiUXqXEuFZ2efYa33Wlg6WKLb0W6KgpBZHpPiJqgzpA4cGjng3PxzGZZLS07DjfAbqNm7JsxszXA15GqGZa6tu4a4l3EIXBsIe097GFoZwsbNBp33dYbrT64aWcfC7v79+/j5559RunRphIeHY8KECZg/f36hLwjpfH19MXPmTPTo0QPjx4+HTCYTOhIRERGpSKsnLot1xJClZv3BwdzWHBJ9CWKiYjK8djXkKizLWaLuiLowLmac6QdobXty4gnW+a5D0cpF0fVwVxhaGmbrfVblrZAUk4TED4lKz9/ZcQdJMUlw7uAMp/ZOuLPz8+MvJbxPAPB5W31JJBLBe7J3LtaGviSVSnHgwAH4+/vDyckJjx8/xooVK7BmzRp4enpCJNJM6cyvPDw8sHbtWqxatQp+fn64d++e0JGIiIhIBVopCdIUKe7suoO7u++ieo/qWS7/6cUnSJOlMCpmpPR8+p4Dp/ZOEEvEqNS20ucP1B+TvjGS5j088hDr/dajlGspdInsAn0z/Wy/90PUB+gY6GR4z9WQqzC1MYVtPVs4BTkhLSkNNzbdUFrGppYNAODEtBOQSfmNrTp9+PABO3fuxIABA2BjY4Pg4GCUKVMGe/fuxZQpU+Ds7Cx0xDytQoUKCAsLg5WVFapVq4Z+/frh5MmTvJ8CERFRPqKxqxs9P/cckwwmAfhcEiAHqnSqAq+JGY+//9Knl5+w56c9kOhJ4NLHRem1a+uvQS6VwynICQDgHOSMC4su4MamG3Dp7ZJhrCXVlgBffNFbunZpxXkS6rL3572wLGuJTns6Qdcwe1e2SU1MxX9h/yHqUBRqD66tdE5F3OvPRcjtZzeIRCLYuNnAsqwlroZcVdoeji0dUWdoHZyafgqXV16GfUN72NSyQYXmFb570nJycrLSXXLj4uJUWOvcS0lJQWxsLCSSr84RiY+H2f//Z2xsLCCV5nouuVyu+COVSpGWloaEhAR8+vQJr1+/xosXL3D37l3cvHkTly5dwtOnT2FrawtXV1eMGzcOrq6u0NHRUeTOK1JTxV/8dypSUvJOWTQ0NMSoUaPQsmVLhIeHw9fXFzKZDE5OTihXrhyKFy8Oa2trWFpawsTEBIaGhhCLxZBKpUhOTkZSUhJSU1MhlUqhp6cHQ0NDmJmZwdzcHObm5jAzM4OhoSH09fWho6MDsVgMkUik+AMgw14eqVSKuLg4yGSyjH/v6Lu47VTD7aY6bjvVcdupTqhtp6+vD3397H/JrC0aKwk2bjaKD+RpSWl4ceEFdvbaiU1tNqHjzo5KyyoKhfxzodAx1EHHXR1h42ajtNzVtVdRpGIRlKhWAgBQpkEZmNqYfv4AnUlJ6H2+t9KJy5o4H6GsT1lEHYzC2blnUf/X+t9dNr20pJemmn1qovHMxkrLXFt/DbI0GZyD/vdttVOQE05MOYHo+9Gw+sFK8XyT2U1QZ2gd3N93H8/PP8e5+edwcNRBuP3kBv8F/plmmDJlCiZMmJCLNVaPYsWKZb1QqVKaD/INT58+xdOnT7Ft2zbBMuRE/e//1cszLl26hEuXLgkdg4iIKE/Ji3vbNXefhP8/GRn4/L9lfcrC608vbO+2HVGHo1DWu6xi0fRCIZPK8Praa2wL3obDYw7DzsNOMcbLSy/x5vobeIzz+N8UIhEqt6uMs3+fRfSDaFiVs1KKkH6icHY8P/ccqz1WKz33W9JvWb7Pb64fTk49icOjD0PHQAd1htT55rLppSX+TTyOjT+GqyFXUbVLVdg1+N+lM6+tvQZzO3PY1P5fQUovCVfXXs1wJSRzW3O49HGBSx8XyGVyHBx1EKdnnYZtXVtU6VQlQ4bRo0dj2LBhisdXrlyBp6dnluupblFRUYiLi9NaU0//hlkikUBHRweGhob58hsWqVSK+Ph4GBsb58v86pSamoqkpCRIpVJIpVLF3qJvkUqlSEhIgJGRUaHfdjnFbacabjfVcdupjttOdUJtuyJFimS9kAC0ejM1q/KfP8S/+e+NUkn4slDYutuibXhbLHdbjn8m/4NGExsBAK6EXAEAnJhyAiennVS8VS79/KHg6tqr8Jrw/UOZvqeUWyn8GvNrjt8nEokQsCoA0hQpDgw9AIm+BG793TJdNr20mJcxR8CKALy6/Ao7euzAgJsDINGT4OXll3h97TVEYhEmG07O8P6ra6+i4YSG3zxZViQWweN3D5yedRpPTjzJtCR8vUvLxMQkx+usDlZWVihTpgx/gOWQVCqFoaEhTExMuO1yKH03MrddznHbqYbbTXXcdqrjtlMdt50yrZaEmEcxAJDp5UG/VMqlFJzaOeHMnDOoM7gO9M318V/Yf6jQogLabcp4/fWwgDBcW3cNDcd/+wN0VkQiUbb3OnxNLBGj1bpWSEtKw94Be6FjoIMaPWp8fz6xCI3+aoRQ/1BcXnUZrv1ccTXkKsQ6Ygx+NBhGRZRP2r6x+Qa2d92Ox8cfw76hPa6suQI7DztYlrVUWi7+TTwAQM9UT6V1ISIiIiLSytWNZFIZXlx4gePjj8PM1gwVAypm+R6P3z2QmpCK07NP497ee0h4m4CqXapmuO+BjoEOqnSugpiHMXjy7xMtrE3mxDpitA1vi/JNy2NXr12Z3w/iK+WblodNLRucmHoCqYmpuB56HWV9y8LMxizDOlZuUxm6xrq4uvbzJV+j70djac2lODP3DBLeJ0CWJsPLyy+xo/sO6BjqoHr36hpeYyIiIiIqqLRydSO5TA6T4iYo16QcGo5vCD2TrL/lLuZcDJXbVMa5Bedgd80OeqZ6qNCiQqbLVmpdCXv678GVkM/frgtFoidB+y3tEdYiDNu6boNEX4LKbSp/9z0e4zwQ1jwMB0ceRMLbBFTpnPEQIQDQNdKFY6AjbkbchP8Cf7gPd4dxMWP8t/E/HB9/HMmxyTCwMECZBmXw44kfM9ypmoiIiIgou0RyDZxOLU2RQi7737BiHTHEOpnvtJCmfL7EpUQv47FfMqlMcfM1kViU6TKZjZP+Pom+RGM3u/reHHKZHNIUqSJzVnnSktIghxwiiL6b+XvjyKQypUupZtelS5fg4uKCixcvombNmjl+vyp4zJ/quO1Ux22nOm471XC7qY7bTnXcdqorLNsuu5/9NLIn4Xsf5nOyrFgizvYH3y/Hycn7VPW9OURi5fMbssqT3XMhvjeOpteXiIiIiAoPfrIkIiIiIiIlLAlERDkgl8tx4sQJTJ8+HU+eCHexBCIiIk1iSSDKq+LjAZHo85/4eKHTEIDnz5+jcmU3NGhQH7/8Mgrly1dH3759kZaWJnQ0IiIitWJJICLKBplMhuDgYNjZ/e8KaqGhoTh+/Dj69+//3bs9ExER5TcsCURE2TBz5kw8ePAAv/76vzuz29raYsGCBdi9ezemTp0qYDoiIiL1YkkgIsrCs2fP8Mcff2DKlCkwMlK+G3qJEiUwf/58/Pnnn7hx44ZACYmIiNSLJYGIKAtLliyBu7s7qlatmunrjo6OaN++PQYOHMjDjoiIqEBgSSAi+o7k5GQsW7YMHTp0+O5y/fr1w/Xr17Ft2zYtJSMiItIclgQiou/YvHkzTE1NUadOne8uZ2pqip9//hmjRo2CVCrVUjoiIiLNYEkgIvqOefPmoX379hCLs/5xGRAQgISEBOzcuVMLyYiIiDSHJYGI6Btu376Na9euISAgIFvL6+rqonPnzpg2bZqGkxEREWkWSwIR0Tds2rQJ9evXh6mpabbf06ZNG9y8eROnTp3SYDIiIiLNYkkgIvqG8PBwNG7cOEfvMTExQdu2bTFjxgwNpSIiItI8lgQiokzcvHkTUVFR8PT0zPF727dvjz179uD169caSEZERKR5LAlERJkIDw+Hh4cHjI2Nc/ze0qVLw9XVFSEhIRpIRkREpHksCUREmQgPD4evr6/K7w8MDMSKFSt4czUiIsqXWBKIiL5y48YNPH78GB4eHiqP4e3tjTdv3uDkyZNqTEZERKQdLAlERF/ZvHkzGjRoACMjI5XH0NfXR7NmzbBixQo1JiMiItIOlgQioq9s3rwZPj4+uR4nICAAW7ZsQWJiohpSERERaQ9LAhHRF27fvo0HDx7k6lCjdJUrV4a1tTV2796thmRERETaw5JARPSFiIgI1KtXDyYmJrkeSyQSwd/fH+vWrVNDMiIiIu1hSSAi+sKmTZvg7e2ttvH8/f1x4MABfPjwQW1jEhERaRpLAhHR/7t27Rru3buHRo0aqW1MOzs7ODo6IiIiQm1jEhERaRpLAhHR/1uzZg18fHzUcqjRl5o2bYoNGzaodUwiIiJNYkkgIgKQlpaGDRs2oEWLFmofu3Hjxjh58iRevXql9rGJiIg0gSWBiAhAZGQkxGIxateurfaxixUrhho1avCQIyIiyjdYEoiI8PlQo2bNmkEikWhk/MaNGyM0NFQjYxMREakbSwIRFXpPnz7Fjh070Lp1a43N4ePjg/Pnz+Pp06cam4OIiEhdWBKIqNCbO3cuPD09UaZMGY3NYW1tDTc3N2zevFljcxAREakLSwIRFWofP37E0qVL0bVrV43P1bhxY2zcuFHj8xAREeUWSwIRFWrLly9HhQoVUL16dY3P5ePjg8uXL+PRo0can4uIiCg3WBKIqNCKj4/HzJkz0a1bN63MZ2FhgTp16vCQIyIiyvNYEoio0Jo1axZKliwJLy8vrc3JQ46IiCg/YEkgokLp7du3mDFjBgYPHgyRSKS1eRs1aoTr168jKipKa3MSERHlFEsCERVKEyZMgKurK1xdXbU6r7m5OerWrYtNmzZpdV4iIqKcYEkgokLn5s2bWLlyJYYMGSLI/E2aNEFYWJggcxMREWUHSwIRFSpyuRyDBg1C27ZtUa5cOUEyeHl54c6dO7h9+7Yg8xMREWWFJYGICpXdu3fj0qVL6Nevn2AZTExM0KBBA57ATEREeRZLAhEVGikpKRgxYgR+/vlnmJubC5ol/ZAjuVwuaA4iIqLMsCQQUaGxatUqiEQitG7dWugo8PDwwLNnz3Dt2jWhoxAREWXAkkBEhcKHDx8wdepUDBs2DDo6OkLHgZGREby8vLBhwwahoxAREWXAkkBEhcKUKVNQuXJl1K9fX+goCv7+/tiwYQNkMpnQUYiIiJSwJBBRgffy5UssWbIEgwYN0uqN07JSt25dJCcn49ixY0JHISIiUsKSQEQF3l9//YW6devCyclJ6ChKdHR04Ofnh7Vr1wodhYiISAlLAhEVaE+fPsWKFSvQv39/oaNkqkWLFoiIiEBCQoLQUYiIiBRYEoioQPvrr7/g5eWFChUqCB0lU5UrV0bJkiWxfft2oaMQEREpsCQQUYH18uVLrFmzBr169RI6yjeJRCIEBARg2bJlQkchIiJSYEkgogJr9uzZcHd3z7N7EdIFBATg9OnTuHfvntBRiIiIALAkEFEBFR0djcWLF6Nnz55CR8lSkSJF4OXlheXLlwsdhYiICABLAhEVUAsXLoSTkxOqVasmdJRsadOmDdasWYOUlBShoxAREbEkEFHBk5SUhPnz56Nbt25CR8m22rVrw9DQkCcwExFRnsCSQEQFTmhoKMzMzPLU3ZWzIhaLERQUhNmzZwsdhYiIiCWBiAoWuVyOmTNnIjg4GGJx/voR17p1a9y4cQNnzpwROgoRERVy+es3KBFRFg4cOIC3b9+iefPmQkfJMRMTE7Ru3Zp7E4iISHAsCURUoMyePRvt27eHvr6+0FFU0qlTJ+zYsQOPHj0SOgoRERViLAlEVGDcvHkT//zzD9q1ayd0FJXZ2NjAx8cHU6dOFToKEREVYiwJRFRgzJ07F02bNoW1tbXQUXKlT58+CAkJwdOnT4WOQkREhRRLAhEVCNHR0Vi/fj06d+4sdJRcK1euHBo2bIgpU6YIHYWIiAoplgQiKhCWLl0KZ2dnODo6Ch1FLfr06YPVq1dzbwIREQmCJYGI8r2UlBTMnz8fXbp0ETqK2pQvXx4+Pj4YM2aM0FGIiKgQYkkgonxv8+bN0NPTg6enp9BR1GrQoEGIiIjAhQsXhI5CRESFDEsCEeVrcrkcs2bNQqdOnfLdzdOyUrJkSQQHB2PIkCGQy+VCxyEiokKkYP1GJaJC599//8WDBw8QGBgodBSN6NmzJ+7evYv169cLHYWIiAoRlgQiytcmT56MoKAgGBkZCR1FI4yNjTFmzBgMHjwYr1+/FjoOEREVEiwJRJRvXb58Gf/880+BuOzp9/j4+MDNzQ0DBgwQOgoRERUSLAlElG9NmTIFrVu3RpEiRYSOonGjR4/G0aNHsXr1aqGjEBFRIcCSQET50p07d7Bz505069ZN6ChaYW1tjalTp+Lnn3/G5cuXhY5DREQFHEsCEeVLo0ePRkBAAEqVKiV0FK1xd3dHr1690KpVK7x580boOEREVICxJBBRvnPq1CkcOHAAP/30k9BRtK5nz55wdnZGkyZNEBsbK3QcIiIqoFgSiChfkcvlGD58OLp16wZra2uh42idWCzGxIkTYWpqioCAACQkJAgdiYiICiCWBCLKV0JCQvDgwYNCcy5CZnR1dTFz5kzEx8ejWbNmiI+PFzoSEREVMCwJRJRvPH36FIMHD8Yff/wBY2NjoeMIysjICAsXLkRCQgKaNm2KuLg4oSMREVEBwpJARPmCTCZDjx494OPjA09PT6Hj5AlGRkZYsGAB0tLS4Ofnh0+fPgkdiYiICgiWBCLKF0aPHo179+5hxIgRQkfJUwwNDTFv3jwAQOPGjVkUiIhILVgSiCjPW7RoEZYtW4YFCxbA1NRU6Dh5joGBAebOnQuxWIwWLVrwZGYiIso1lgQiytOWLl2KkSNHYt68eXBwcBA6Tp6lr6+POXPmIC4uDq1bt0ZKSorQkYiIKB9jSSCiPEkul2PixIn45ZdfsHjxYtSoUUPoSHle+jkKT58+Rbdu3SCTyYSORERE+RRLAhHlOYmJiejSpQsWLVqElStXombNmkJHyjdMTEywcOFCnDp1CoMHD4ZcLhc6EhER5UM6QgcgIvrSw4cP0aZNG4hEIoSFhRXKG6bllrW1NZYsWYJu3bqhePHi+O2334SORERE+Qz3JBBRnrFz507UqFEDjo6OWLFiBQtCLtja2mLx4sWYMWMGFi9eLHQcIiLKZ3K9J0EmlSHxfSKMrI0gEosUz8e/jUdqfGqW7zctZQqxrhgfH3+Evrk+DC0NlV5PTUxF/Ot46BjqwKS4idJraUlpiHsVBwMLAxhYGHxzjuwul1OqrGM6sY4YJiVNIJZk7Gmxz2IhS8t4LLFxcWPoGupmeD4tKQ2pCakwtDLM8BpRfpCcnIyRI0di9erVGDduHJo2bSp0pAKhYsWKWLBgAX766Sfo6uqiV69eQkciIqJ8QuWS8OLiCxz+9TAe//MY+ub6SEtMg01tG9QeXBsVW1TEsT+O4d7ee4rlkz4kITk2GWa2ZkplouOujrB0sMRch7mo90s9+Ez1UbyW8D4B6xuvx4eoD+hyoIuiJDz+9zGurLmCuzvvIuFdAjz/8ETD8Q0zZMzucqpSZR31zfRhYGkAWaoM8W/jYVvXFk3mNEHJGiUVy6+oswIpn1JgYKlcaFquagmHRp+v7iKXy3Ft3TWcnXcW0fejATkAEVCzd014TfCCrlHGMkGUF128eBE9evSAXC5HeHg4ypQpI3SkAqVGjRqYP38+fv75Z0ilUvTt21foSERElA+oVBI+RH3AGo81sPO0w5DHQ2BSwgSJHxJx7I9j2N51O3758AuaLWqm9J5Dvx7CyWkn0f9a/wzf5qfEZbxUX/ybeKz1WYu4l3HoeqSr0ofoK6uvwLauLTzHeWKu/dxv5szucqpSZR1d+7sqitDHpx8R6h+KUP9Q/HTjJ6U9ATV710TjmY2/OXdaYhqenn6KlqtbophzMYhEIjw4+ADhgeGIexmH1utbq2s1iTQiOjoakydPxuLFi9GjRw/06tULurost5rg6uqKhQsXYvDgwbh//z6mTp0KiUQidCwiIsrDVCoJ/238D6kJqWi2qBlMSnz+dt/Q0hBN5zVFyZols3h31j69/IS13muR9CEJ3Y51QzGnYkqvt1zVEgCQFJP03XGyu5xQzG3N4THOAxHtIxB1KApO7Z2y/V5dI100X9xc6blyvuVQqXUl3Np6S91RidTm5cuXWLp0KebMmYMqVapgw4YNKF++vNCxCjwXFxesX78eAwcOxKVLl7BixQred4KIiL5JpROX05LTACDTQ1qqd6+eq0Afn37EGo81SPmUgu7Hu2coCAWNWWkzAMCnF58yvBb3Og4J73J259Tk2GQYFzdWSzYidZDL5bh37x6WLFkCf39/2Nvb48iRI5g1axaWLFnCgqBF9vb22LBhA4oUKYIqVapg3LhxeP36tdCxiIgoD1JpT0JZ77L4589/sG/QPvjN9ctwQrGqYh7FYI3HGgBA93+6w9LBUi3j5mWvLr8CAFiVt1J6/vTs07iy+gqSPibBtJQp6o+uD7f+bhnenxKXgoR3CUiOTcadXXfwIPIB2oS10Up2onSpqal49eoVnj9/jmfPnuHx48e4d+8ebt26hWvXriEhIQHVqlVD/fr1MWjQIJQuXVroyIWWmZkZxo0bh2bNmmH58uWYPn06mjRpgqZNm6JWrVqoWLEijI35RQMRUWGnUkmw87BDsyXNcGTMEcwqOQvFnIvBppYNKrSogIoBFSESibIeJBM3wm8AALod61ZgC0LSxyTEPIqBLE2G5+ee4+i4oyhRowTKNS6nWKZ6j+qo2bMmLOwtkJqYin8m/oO9P+1FWlIa3Ie6K413d89dHPrlEBLfJyIlPgX1RtVD+Wbf/mY2OTkZycnJisdxcXHqX8lsuHbtGh4+fAixmFfh/RZJUhI6/v9/h4WFQWpgAJlMhqSkJBgYGOR626XfZEsulyv+SKVSSKVSpKamIjU1FYmJiYiPj0dsbCw+fvyI6OhoREdH4+3bt3j37h0+fPiQYVxdXV3Y2NjAxsYGDg4O8Pb2RoUKFRTnG8THx+POnTu5yi6kpCQJgM+HBt67dw8GBlJhA6nIxMQEQ4cOxcOHD3HkyBGMHj0aMTExSssYGhrC2NgYEokEIpEIUqkUKSkpSEhIQGpq5ld209fXh7W1NYoWLYpixYqhaNGiKFKkCMzNzWFsbAwjIyPo6elBR0cHEolE8fc4/feGqr8/8iJ1/nstbLjtVMdtpzqhtl25cuXg7u6e9YJapvLVjVz7uqJmz5p4euopnp9/jvv77iM8MBxlfcqi095OkOjm/KS4ii0r4umpp9jebTt6/NMD5mXMVY2XK7HPYyFL/d8lSI2sjaBnoqeWsf8L/Q/3992HWEcMs9JmcPvJDe7D3ZW2V6OJjRT/rWuoC++/vPH83HP8O+lf1BlcR+nKSc5BznAOcgYAPDzyEJvabsK72+/QYXuHTOefMmUKJkyYoJZ1yQ0PDw+hI+QLndL/o3dvIWNki4mJCXR0Pv9IiYmJQUxMDG7cuCFwKs2wsOgBAPj5Z4GDqJm5uTk+fvzfpZoTExORmJiYozGSk5Px/PlzPH/+XN3xiIgKrPQv7vKSXN0nQawjhp2HHew87FB3eF2cX3QeewfsxaXll+D2U8ZDY7Ji7WgNrz+9EOIVgpBGIejxTw+YljLNTUSVbO+6HdEPohWPG89sjMptK6tl7C+vbpQT9g3t8fDwQ3x8+hEWdhaZLuPQyAH1fqmHw78exrvb72DtmPFGVKNHj8awYcMUj69cuQJPT88c58mtmJgYmJiY8AorOSSVShEXF8dtpwJuO9Vx26mG20113Haq47ZTHbedMpVKglwmV/o2O121rtWwd8BevLr6SuVAxasWR5fILljrvRZrvdei+/HuMC6m3eNjux7uqtX5siM14fOufYne9//SpheIuNdxmZYEfX196OvrKx6bmKjnfBIiIiIiKjhUOuDqzN9n8Pifxxmef3f7HQDAtGTuvv0v5VIKnfd1RuyzWKz1XouE9zm7wk9+JpNmvNOyNFWK29tvw6q8lWLbZrYc8PmQI7GOuMBfFYqIiIiINEelPQkpcSkI8QpBzT41UbVLVRhaGuLV1Vc4+vtRmJQwgUsfl1wHs3W3RcfdHRHqH4p1vuvQ7Ug3xQ3KEt4lICUuBcmxn0/ATYr5fDIwAJiXMVfs5cjucnnJ9Q3XcXf3XVTtUhVWP1jh04tPODHlBD48+IAOO/93nsHVtVcRdTAKzh2dYVXOCkkxSbix6QYurbiERpMawcjaSMC1ICIiIqL8TKWS4PG7B0rXKY3/Nv6HyOGRSHyfCONixqjctjLqDKmjuMHalwwsDWBul/kHc5FYBHM7cxhYKt+l2N7THkHbg7Cn3x7s7rcbrda1gkRXgn8m/4Pb224DAMztzHF7+23c3v75cd/LfWFo+fnOxdldTl1UWcevVQ2uCgNLA1wNuYp3t95BrCNGKbdSaDKnCYpXLa5Yrnr36jAuaoyrIVfx9tZb6JnooUiFIuh5qidK1+HlJYmIiIhIdSJ5XjydmrTm0qVLcHFxwcWLF1GzZk2tzMkTg1THbac6bjvVcduphttNddx2quO2U11h2XbZ/ezHC+gSEREREZESlgQiIiIiIlLCkkDaFR8PiY4OzC0sgPh4odOQwObPn4/27XtAJAJEIv6VICIiyitYEohIEHPnzsWECRNQtGhRxXNnz54VMBERERGlY0kgIq3buXMnxowZg3nz5qFbt26K51u2bIn//vtPwGREREQEsCQQkZbFx8ejd+/eGDduHKpWrar0WqtWrdCzZ09IpVKB0hERERHAkkBEWjZ37lyULFkS/v7+GV7r3bs3Xr16hQULFgiQjIiIiNKxJBCR1nz48AHTp0/HwIEDIRJlvOmgvr4+xo0bh7Fjx+LJkycCJCQiIiKAJYGItGjmzJmoXLkyateu/c1lateuDR8fHwwZMkR7wYiIiEgJSwIRacWnT5+wYMEC9O3bN8tlhw4disOHD2Pv3r1aSEZERERfY0kgIq1YuXIlHBwcvnsL+HRFihTB4MGDMWDAACQkJGghHREREX2JJYGINC4tLQ2zZ89GcHBwpuciZKZNmzawtLTEmDFjNJyOiIiIvsaSQEQaFxERAQDw9vbO9nskEgkmTJiAZcuW4Z9//tFUNCIiIsoESwIRaZRcLsfMmTPRuXNn6Ojo5Oi9Dg4OGDhwILp37464uDgNJSQiIqKvsSQQkUadOXMGd+/eRWBgoErv79y5M4oWLYqBAweqNxgRERF9E0sCEWnUnDlz0Lp1axgbG6v0frFYjMmTJ2Pbtm3YtGmTmtMRERFRZlgSiEhjnjx5gh07dqBDhw65GqdEiRIYP348+vTpg+fPn6spHREREX0LSwIRaczChQvh4eGB0qVL53osHx8feHh4oE+fPpDL5WpIR0RERN/CkkBEGpGcnIyVK1fmei/Cl0aNGoVz584hNDRUbWMSERFRRiwJRKQRERERMDc3R61atdQ2poWFBcaOHYtBgwYhJiZGbeMSERGRMpYEItKIBQsWoG3bttm+eVp2eXt7o0KFCpgyZYpaxyUiIqL/YUkgIrW7du0arly5goCAALWPLRKJMGTIEMyfPx9Pnz5V+/hERETEkkBEGrB8+XL4+fnB3NxcI+M7OTnBy8sL48aN08j4REREhR1LAhGpVWpqKsLCwjSyF+FLAwYMQGhoKJ48eaLReYiIiAojlgQiUqv9+/dDX18fLi4uGp2nTJky8PDwwPz58zU6DxERUWHEkkBEahUSEoJmzZpBLNb8j5cuXbpg2bJliIuL0/hcREREhQlLAhGpzYcPH7B79240b95cK/PVrFkTZcqUwZo1a7QyHxERUWHBkkBEarNlyxZUrFgRZcuW1cp8IpEInTt3xty5c3kXZiIiIjViSSAitQkPD0fjxo21Oqevry/evn2L06dPa3VeIiKigowlgYjU4v379zh27Bh8fX21Oq++vj78/PywevVqrc5LRERUkLEkEJFa7Ny5E5UrV0apUqW0PnfLli0RHh6OhIQErc9NRERUELEkEJFabNq0Cd7e3oLM7ezsjOLFi2P79u2CzE9ERFTQsCQQUa7FxMTg8OHDWj/UKJ1IJEKLFi2watUqQeYnIiIqaFgSiCjXdu3ahR9++AG2traCZfD398fx48fx5s0bwTIQEREVFCwJRJRrW7duRaNGjQTNUKJECVStWhVbtmwRNAcREVFBwJJARLmSmJiIyMhIwUsCADRu3BhhYWFCxyAiIsr3WBKIKFcOHToEa2trlC9fXugo8PX1xenTp/HixQuhoxAREeVrLAlElCvbtm2Dl5cXRCKR0FFQrFgx1KxZExEREUJHISIiytdYEohIZVKpFDt37oSXl5fQURR4yBEREVHusSQQkcpOnjwJuVyO6tWrCx1FwdvbGxcuXMDz58+FjkJERJRvsSQQkcq2bt0KLy8vSCQSoaMoWFtbw8XFhVc5IiIiygWWBCJSiVwux9atWwW7y/L3+Pj4IDw8XOgYRERE+RZLAhGp5OLFi4iJiUHt2rWFjpKBj48Pzp49y6scERERqYglgYhUsmXLFnh4eEBPT0/oKBnwkCMiIqLcYUkgohyTy+WIiIiAj4+P0FG+ydfXFxs3bhQ6BhERUb7EkkBEOXbjxg08e/YMdevWFTrKN/n6+uLcuXN48uSJ0FGIiIjyHZYEIsqxjRs3wsPDA0ZGRkJH+aYiRYqgTp06PIGZiIhIBSwJRJQjcrkcYWFhaNq0qdBRstSkSRNs2LBB6BhERET5DksCEeXI+fPn8fbtWzRo0EDoKFny9vbG7du3cefOHaGjEBER5SssCUSUI6GhofD29oa+vr7QUbJkamqK+vXrc28CERFRDrEkEFG2SaVShIeH54tDjdK1aNECISEhkMlkQkchIiLKN1gSiCjbjh49irS0NNSqVUvoKNnm4eGBxMREHDp0SOgoRERE+QZLAhFl24oVK9C8eXPo6OgIHSXbdHV1ERAQgJUrVwodhYiIKN9gSSCibImOjsb27dvRqlUroaPkWKtWrbBr1y68e/dO6ChERET5AksCEWXLhg0b4OTkhLJlywodJcfs7OxQvXp1hIWFCR2FiIgoX2BJIKIsyeVyLFu2DIGBgUJHUVmHDh2wePFipKSkCB2FiIgoz2NJIKIsnTt3Do8ePUKTJk2EjqKyRo0aQU9PDxs3bhQ6ChERUZ7HkkBEWZozZw4CAwNhZGQkdBSVSSQSdO/eHdOnT+flUImIiLLAkkBE3/Xs2TNs27YNnTp1EjpKrrVo0QLR0dHYtWuX0FGIiIjyNJYEIvquBQsWoEGDBrC1tRU6Sq7p6emhR48eGD16NNLS0oSOQ0RElGexJBDRN8XHx2PZsmXo3Lmz0FHUJigoCImJibxvAhER0XewJBDRNy1ZsgQ2NjZwdXUVOora6OrqYujQofjtt98QGxsrdBwiIqI8iSWBiDKVmJiI6dOno2/fvhCJRELHUSsvLy+ULVsWf/zxh9BRiIiI8iSWBCLK1IoVK1CkSBF4enoKHUXtRCIRxo4diyVLluD8+fNCxyEiIspzWBKIKIOkpCRMnToVffr0KXB7EdKVLVsWPXv2RM+ePZGamip0HCIiojyFJYGIMpg3bx4sLCzQqFEjoaNoVM+ePZGUlITp06cLHYWIiChPYUkgIiXR0dH466+/MGTIEIjFBftHhK6uLiZMmIDJkyfj5s2bQschIiLKMwr2JwAiyrG//voLzs7OcHd3FzqKVlSpUgUdOnRAjx49IJVKhY5DRESUJ7AkEJHC/fv3sWjRIgwZMkToKFr1008/4e3bt5gzZ47QUYiIiPIElgQiAgDI5XIMGjQIAQEBcHR0FDqOVhkYGGDcuHEYP348Hj58KHQcIiIiwbEkEBEAYNeuXTh9+jR+/vlnoaMIwtXVFf7+/ujTpw/kcrnQcYiIiATFkkBEiI+Px6BBgzBo0CBYWFgIHUcwQ4YMwbVr17B+/XqhoxAREQmKJYGI8Ouvv6Jo0aJo06aN0FEEZWZmhl9++QVDhw7F+/fvhY5DREQkGJYEokLu+PHjWLVqFSZMmFDgL3maHb6+vnB2dsaoUaOEjkJERCQYfiIgKsQ+fPiA7t27Y+DAgShTpozQcfIEkUiE0aNHY+PGjTh27JjQcYiIiATBkkBUSMlkMgQHB8Pe3h6dOnUSOk6eYmNjg59//hk9e/ZEQkKC0HGIiIi0jiWBqJCaMmUKrl69ikmTJvEwo0x06tQJ5ubm+P3334WOQkREpHX8ZEBUCIWEhGDq1KmYPXs2zM3NhY6TJ0kkEowfPx6LFi3CP//8I3QcIiIirWJJICpktm/fjv79++Pvv/9GpUqVhI6Tp5UtWxbDhg1Dhw4d8PbtW6HjEBERaQ1LAlEhsnLlSnTq1AlTp05F7dq1hY6TL3To0AFVq1ZFly5dIJVKhY5DRESkFSwJRIWAVCrF2LFjMXz4cCxcuBCNGjUSOlK+IRKJ8Mcff+DBgwfo168f78ZMRESFgo66B/z45CPe3sx8t7xYR4yyPmUzPC+XyfHu9jvEvY6DoaUhzGzNYFTEKNvjlnItBSNrowzLSPQlsLCzgGVZy1ysUebS5yrToAz0jPWUXnt+7jkSoxNRyq0UjIqolistOQ2Pjj6Cvrk+bN1tv7mcLE2GDw8/IO5lHMxKm8HC3gIisSj3K0gFxuvXr9GhQwc8fvwYa9aswQ8//CB0pHzH1NQUS5YsQbdu3TBq1ChMnz4dIhH/nRERUcGl9pJwZ+cd7Bu4D2Xql4Gusa7Sa7qGukolQSaV4eT0kzgz+wwkehJYlbdCSlwKXl97jZI1SqLOsDpwDnLOctxGkxvByNoowzLJH5Px8vJLlKxREu23tIdpKVO1r+eAWwNg7WiteP7cwnPYN3AfavaqiXKNy2WaPTu57uy4g4igCEj0JRj+cjgMLQ0zLHNzy00cHHEQqQmpKFKxCGIexUAkEqHWoFqoO7yu2taV8q+IiAj069cPderUQVhYGIyNjYWOlG+VKFECy5YtQ8+ePfHmzRssW7YM+vr6QsciIiLSCLWXhHQtlrdQ+vD8Nblcjs3tNuPR0UcIDAlExYCKitfi38bjyG9HcPbvs4qSkN1xv17m3e13WOa6DLv77UbHnR1zsUZZOz37NCKHR6LWwFrwm+uX4ZvGnOS6GnIVZrZmiH0aixvhN+Daz1Xp9ZeXXyKifQRq9qmJpvOaQqIrgVwmx4UlF3B8wnGWhELu2bNnGDJkCA4fPoyxY8fCz89P6EgFgp2dHUJDQzFkyBB4enpi3bp1KF++vNCxiIiI1E6wcxKurLmC29tuZygIAGBc1BgtlraA10SvXM9j7WiNCs0r4EHkA8ikslyP9y3//vUvIodHou6oumg6r2mWhyJ8L1fcqzjcP3Afbj+5waaWDa6uvZrh/be334ZcJkfDPxpCoisBAIjEIrj95IY2G9uob8UoX/n06RMmTZoER0dHpKSkYNu2bSwIalasWDGsWrUK5cqVQ/Xq1TF16lQkJSUJHYuIiEitBCsJl5ZdgmVZywwF4UuZnb+gCj1TPUiTpUj+mKyW8b529I+jODL2CDzGefxfe3ceH9O5/wH8M9k3iawaS7NaQ0JiF4Ig1E64lpDYq6haK3auVhe9LZe2aF0upeJaqq2UijWWqiBiJxVrLJEEkz0zz+8Pv5w6soiR5Ezk83695mXmOc+c8z3fieR855znOej4acfXjuvsD2chNAJe/b3gNcALt4/dxqMrj2R9DE2eFQa5mbn51uvWzk2HvaDy7NatW5g/fz7c3Nywbds2LF++HB9//DEcHIo+60a6MTMzQ3h4OL799lusW7cOnp6eWLlyJe/OTEREb4xSu9zoZvRNpCakytosnSzh7OsMrUaLuyfvol5wvdder1llM1RvXr3Q/kIrcPvYbVSqVgnmdvmv639d+2bvw8WtFxG4OBD+M/yL/b6i4opdF4uqjavC1t0WXv29sGfKHsT+NxbtF/09I03t7rVxcP5BbAvZhsCPA1GjVQ0YGHKyqoogNzcXCQkJuHTpEk6cOIE9e/YgJiYGrVq1wqJFi9CqVSsOqi0jjRo1wubNm7F79258+eWX+PDDDxESEoLevXujdevWMDExeflKiIiI9FCpFQmx62LzDTCu1qwanH2dkfU4C9pcLczt5QfHWo0Wf/3+l6zNrb2b9K15Qet1qOuQr0jIKyQyH2fi3KZzeHT5Efps7FNSuyZzcetF2HrYotn7L59zvjhxJZ5OxIO4B+j4+bMzEtbVrPG2/9s4u/4s2v2znXTwV8W7Cgb/NhhR4VFY23YtjMyM4OzrjFrda6HZhGYwtjDOt30AyMrKQlbW32cu1Gq1rrv+2kJDQ5FtXHCcbzohhOyh0WiQm5uLnJwcZGZmIj09HU+fPsXjx4+RkpKC7OzsAtdjZGSEZs2aYdKkSXByckJaWhr27NlTxnvzerKyjAB4AQD27t0LU9P8Z8f0nYGBAUaOHIkzZ85g/fr1WLFihbTMzMwM1apVg4ODAywtLWFmZgZjY2MYGhpCpVJJ/6f1ubATQiA3NxdGRkZ6Hae+Yd50x9zpjrnTnVK5q1+/PubOnVtm2ysuRQYuG5k922xulvxgQJujxfGvjgMAUq+n4tGVR5iSOAVWb1kVa7158gqJtAdpuHf6HhqPbQyvfl5FvifzcSZuH7sta/Ps/PKpItt/3B4H5h7Apu6bMOjXQdK+6RpX7LpYQAV49f+73esfXogcH4mEAwmyS4ncA93hfsId6vtq3D15F9cir+HAvAM4s+YMRv05CqbW+WdeWbx4MRYsWPDS/So1lpbQ/P9/QOzYoVwc5ViVKlVQtWpVGP9/gZWTk4MDBw4oG9Rratp0IwDgp59KbxtCCGi1WhgYGJTqL/86depAq9UiPj4eKSkpyMzMRHx8POLj40ttm0REVH5t2bKlYhUJRTG2MEalapWQfDVZHoyZEUJ+CwEARH8SjajwKJ3W/3whcXH7RUT0jYCth22RM/6oE9VSgZKnOEVC3d51Yetmi22Dt2Fz780Y8NMA2ZmPV4lLm6tF3MY4VHapjIcXHkr3VjCt9OxgP3ZdbIHjDayqWKFW11qo1bUWPII88GOPH/HnN3/C/8P8lz+Fh4dj8uTJ0uszZ84gICDgpftZ0lJTU2FlZQVDw4JzRQXTaDRQq9XMnQ6YO90xd7ph3nTH3OmOudMdcyenSJEAAHV61UHMqhio76llZwpKWt3eddFgUAMcmHsADQY1QCXngu+V4FDHQSpQXlX9AfWRm5WLn4b9hC39t6D///rDwKjo8QEFxXV111WkP0yHfU37fAWLfS17XNx6Ee+seAcmlibISc8p8JIit/bPiojka8n5lgGAqampbG53K6vSyz0RERERlU+KjXT1n+EPU2tT/Dz6Z2hyNKW6rbbz2yI3KxdHPj1SattoGNoQ3b7thss/XcbWQVuLNd3qi3HFrouFma0ZQveHIuS3ENmj+3fdka3OxsVtFwEAR5ccxanvT+Vb583omwAA+5r2Jbh3RERERFSRlOnsRsDfA5Gtq1tjcORgRPSNwLc+36LR8Eawq2mH3IxcPDj3AKfXnIalkyUMTV//dI+dpx0aDGyAmFUx8A/3h1WV0vn23G+0H3Izc/HbxN9gZGaEXmt7QWVQ+LXPz8fl964frvxyBT6hPgVervS2/9uwedsGseti4TPEB+Z25vhl9C+I2xCHBiENYG5rjnux93Di3ydQxbtKvpuvEREREREVV4kXCTYuNvAI8sCF/10ocHm1ZtVgbvJsVqNqTaphwpUJOB9xHjcO38DNwzdham2KStUqoes3XVGray3psp289ZpYFT6lYFF92sxpg7SHaUjYn4D6A+oX8G7d9vPFbeXNcnR111WcjziP+gPqFyuuuA1xcG3nCt+RvgVuT6VSoeX0lri26xqy1dloOr4panWvhfObz+Pm4ZvIeJQBCycLBP0rCPUH1oeRqWJXkhERERFROacSQgilgyDlnDp1Cn5+foiJiYGvb8EFSknjwCDdMXe6Y+50x9zphnnTHXOnO+ZOdxUld8U99uPdt4iIiIiISIZFAhERERERyfDCdSIievOkpQF5Uzyr1YClpbLxEBUhIyMDu3btQlRUFC5duoR79+4hO9sY8fGxAIDRoyejZUsftG3bFi4uLgpHSxUFzyQQERERKUCtVmPmzJlwdHTElClT8PTpU7Rp0wbjx4/HxIkTpX5PnjzBsmXL4OnpicaNG2P16tXIyspSMHKqCHgmgYiIiKiMHTp0CAMGDICzszNWrlwJb29vqFR/T5uenv7380mTJsHCQiA1NRV79+7FZ599hgULFuDTTz/FoEGDZO8jKik8k0BERERUhn788Ud06dIFI0aMwNq1a+Hj41OsA/3KlSsjODgYW7duxcSJE/HBBx+gT58+SEpKKoOoqaJhkUBERERURjZv3oyRI0diyZIl+Mc//qHTWQADAwN06dIF27Ztw5MnT9CiRQvEx8eXQrRUkbFIICIiIioDJ06cwPDhw/H555+jdevWr70+W1tbfPnll2jRogVatGiBs2fPlkCURM9wTAIRERFRKbt//z569OiBcePGlUiBkMfAwADTp0+HtbU1goKCEB0dDQcHhxJbP1VcPJNAREREVIqEEHj33XfRsGFDDBkypFS2MWbMGLRv3x6dO3dGcnJyqWyDKhYWCURERESlKCIiAocOHcLMmTNLbSYilUqFDz/8EK6urhg1ahS0Wm2pbIcqDhYJRERERKXk0aNHGDduHGbOnAk7O7tS3ZaBgQEWLlyIa9eu4aOPPirVbdGbj0UCERERUSlZsGAB6tWrh6CgoDLZXqVKlfDFF1/g888/x8GDB8tkm/RmYpFAREREVAouX76M1atXY+rUqWW63dq1a+P9999HWFgYnj59WqbbpjcHiwQiIiKiUjB58mT07t0b7u7uZb7tgQMHwsnJqcwLFHpzsEggIiIiKmGHDx/GoUOHMHbsWEW2nzc+YePGjdi3b58iMVD5xiKBiIiIqAQJITBz5kwMGTIEtra2isVRrVo1jB8/HmPGjEFmZqZicVD5xCKBiIiIqARFRUUhLi6u1O6J8CoGDBgAMzMzfPzxx0qHQuUMiwQiIiKiEiKEwKxZsxAaGopKlSopHQ4MDQ0xd+5cLFmyBJcvX1Y6HCpHWCQQERERlZDff/8dV69exaBBg5QORVK3bl307t0b48aNgxBC6XConGCRQERERFQChBCYP38+hgwZAktLS6XDkRk3bhxiY2Oxbds2pUOhcoJFAhEREVEJOHDgAC5cuICBAwcqHUo+1tbWmDhxIiZOnIi0tDSlw6FygEUCERERUQmYP38+Bg8eDCsrK6VDKVCPHj3g5OTEQcxULCwSiIiIiF5TdHQ0Tp06pVdjEV5kYGCADz/8EF9++SXi4+OVDof0HIsEIiIiote0cOFCDBw4EDY2NkqHUiQvLy907doVH3zwgdKhkJ5jkUBERET0Gk6cOIEjR44gJCRE6VCKZcKECTh06BAiIyOVDoX0GIsEIiIiotewcOFC9OvXD3Z2dkqHUix2dnYYP348JkyYgKysLKXDIT3FIoGIiIhIR6dPn0ZUVBTCwsKUDuWV9O/fHyYmJliyZInSoZCeYpFAREREpKP58+cjODgYDg4OSofySgwNDREeHo7Fixfjxo0bSodDeohFAhEREZEOYmNjsWfPHgwbNkzpUHTSqFEjBAUFYfz48UqHQnqIRQIRERGRDhYsWIC+ffvCyclJ6VB09sEHHyA6Oho//fST0qGQnmGRQERERPSKTp06hcjISAwfPlzpUF6Lra0tJk+ejPHjx+Pp06dKh0N6hEUCERER0SuaM2cO/vGPf5Trswh5evXqhapVq2LmzJlKh0J6hEUCERER0Ss4duwYDh48WO7PIuRRqVSYO3cuvv/+exw7dkzpcEhPsEggIiIiKiYhBGbOnIlBgwaVm/siFIeLiwtGjx6N4cOHIzMzU+lwSA+wSCAiIiIqpt9//x2nT58ud/dFKI6wsDAYGxtjzpw5SodCeoBFAhEREVExaLVaTJ8+HSNGjIC1tbXS4ZQ4IyMjLFq0CCtWrMCRI0eUDocUxiKBiIiIqBgiIiLw4MEDDBw4UOlQSo27uzvGjRuHoUOHcrajCo5FAhEREdFLZGVlITw8HGPHjoWZmZnS4ZSqIUOGwNHRER988IHSoZCCWCQQERERvcSyZctgZmaGHj16KB1KqTMwMMCiRYuwdetWbN++XelwSCEsEoiIiIiKkJSUhEWLFmHy5MkwNDRUOpwy8dZbb2H27NkYMWIE7ty5o3Q4pAAWCURERERFmDdvHho1aoQWLVooHUqZ6ty5M9q0aYOQkBBotVqlw6EyxiKBiIiIqBCxsbFYs2YNpk6dqnQoiggPD8dff/2Fzz//XOlQqIyxSCAiIiIqgBAC48aNw+DBg+Hq6qp0OIqwtLTE4sWLsWDBApw5c0bpcKgMsUggIiIiKsCmTZtw7do1jB49WulQFOXt7Y1hw4Zh0KBByMjIUDocKiMsEoiIiIhekJKSgkmTJmHKlCmwsLBQOhzFjRo1CiYmJggPD1c6FCojLBKIiIiIXjBjxgzUrl0bnTt3VjoUvWBkZIR//vOfWLVqFaKjo5UOh8oAiwQiIiKi5xw9ehQbNmzAzJkzoVKplA5Hb7i5uWHcuHEICwtDenq60uFQKWORQERERPT/MjIyMGzYMIwZMwbVq1dXOhy9ExISAmtra8yZM0fpUKiUsUggIiIi+n/z5s2DmZkZhg4dqnQoesnQ0BBz587F119/jdjYWKXDoVLEIoGIiIgIwB9//IHly5dj4cKFMDIyUjocveXp6YnBgwdjzJgxvMnaG4xFAhEREVV4arUagwcPxrvvvgsPDw+lw9F7Y8aMwd27d/Hdd98pHQqVEhYJREREVOFNmjQJdnZ2CA0NVTqUcsHc3BzTpk1DeHg4UlJSlA6HSgGLBCIiIqrQduzYgYiICHz00UcwNDRUOpxyo23btqhbty7mz5+vdChUClgkEBERUYV148YNDBs2DHPmzIGzs7PS4ZQrKpUK06dPx+rVq3HhwgWlw6ESxiKBiIiIKqScnBwMGDAAHTt25E3TdOTu7o4+ffpg2rRpSodCJYxFAhEREVVI4eHhSE5OxvTp05UOpVx79913ER0djaioKKVDoRLEIoGIiIgqnG3btmHVqlX44osvYGZmpnQ45VrlypUxatQoTJo0CRqNRulwqISwSCAiIqIK5fLlywgLC8PChQvh6uqqdDhvhIEDByIlJQUbNmxQOhQqISwSiIiIqMJ4/PgxevTogX79+qFDhw5Kh/PGMDU1xfjx4zFr1ixkZGQoHQ6VABYJREREVCFotVqEhITAyckJ77//vtLhvHG6dOkCW1tbLF26VOlQqASwSCAiIqIKYebMmYiLi8Mnn3zC+yGUAgMDA0yaNAkff/wxkpKSlA6HXhOLBCIiInrjrVu3Dt988w2WLVsGGxsbpcN5YzVt2hS+vr5YsGCB0qHQa2KRQERERG+0gwcPYuzYsViyZAnc3d2VDueNN2nSJHz33Xe4fPmy0qHQa2CRQERERG+sc+fOoWfPnpgxYwZatGihdDgVgoeHB3r27ImpU6cqHQq9BhYJRERE9Ea6efMmOnfujMGDB6NPnz5Kh1OhjB07FgcPHsTevXuVDoV0xCKBiIiI3jj3799HYGAg/P398e677yodToVjb2+P9957D+PGjUN2drbS4ZAOWCQQERHRG+XRo0fo2LEj6tSpg5kzZ0KlUikdUoU0YMAAqFQq/Otf/1I6FNIBiwQiIiJ6Yzx8+BDt2rWDs7MzFi5cCAMDHuooxcjICOHh4Vi0aBESEhKUDodeEf/nEBER0RshMTER7dq1Q9WqVfHJJ5/A2NhY6ZAqPD8/P7zzzjsYNmwYtFqt0uHQK2CRQEREROXeX3/9hVatWsHDw4MFgp6ZMmUKrl69im+//VbpUOgVsEggIiKicu3UqVNo2bIl/P398c9//hNGRkZKh0TPsbS0xPz58zF9+nRcunRJ6XComFgkEBERUbm1a9cuBAQEYPDgwZg2bRrHIOip5s2bY8CAAejVqxeePn2qdDhUDPyfREREROWOEAJfffUV+vXrh3nz5mHYsGGcxUjPTZgwAfb29ggLC+P4hHKARQIRERGVK9nZ2Rg9ejQ++ugjrF69Gp07d1Y6JCoGQ0NDfPLJJzh58iQmTJgAIYTSIVEReNEeERERlRv3799H79698fjxY2zcuBHOzs5Kh0SvwNbWFqtXr0ZYWBgsLS3x6aef8gyQnuKZBCIiIioX/vjjD/j6+sLOzg5r165lgVBOVa1aFatXr8aGDRsQGhqKrKwspUOiArBIICIiIr0mhMC3336Ldu3aYfDgwVi8eDHMzc2VDoteg4uLC3744QecPn0abdu2xfXr15UOiV7AIoGIiIj0VlpaGoYOHYo5c+ZgxYoVGDp0KC9PeUM4OjpizZo1cHFxQYMGDfD1119Do9EoHRb9PxYJREREpJfi4uLQpEkTXLp0CREREWjSpInSIVEJMzc3x+zZs/Gvf/0Ln376KXx8fPDrr79yULMeYJFAREREekWr1WLlypVo2bIlAgICsHr1ajg6OiodFpWili1bYseOHejatSuGDh0KX19fbNmyBbm5uUqHVmGV2uxG9+Pu48bBG/AO8YZZZTPZsnM/nkP6o3R49fOCpZOl1DePoakhKrtUhmtbVxiaGBa4/qynWYhdFwtze3M0GNig0DjSH6Xjzok7UCeqYV3dGlWbVIW5bclcx3jtt2tIvpb80n5e//CCWWUzxKyMkdoMjAxgXcMa1ZpUg6WTpaz/mbVnkK3OzreeWt1qobJrZVnbk9tPcOfPO8h+mg1bD1vUaFmDp2GJiKjcunXrFoYNG4aLFy9ixYoVPHtQgRgbG2PIkCEIDg7G1q1bMWnSJEyZMgVjx47FiBEj4OTkpHSIFUqpFQk3Dt5A5IRIuHdwlxUJuyfvxvEvjyNwcaB0cJzXt8HgBjCrbIasx1nY//t+GBgZYMieIXCsl//bg/ObzyNyQiRUhiq4tnVFJedK+focXXIUBxcchENdBzjVd0JKfAru/HkHDYc1RNcVXV97H5/ceYKkS0nS69vHbiPxVCJ8R/nKipvcjFxozDWInBAJZ19nVG9RHZocDWJWxeDR5Ufo+HlHNHnv71+C+2bvg6WjJWq0qiHbnms7V+m5JkeDbYO3IfFUIqr6VYXKUIXrUddh4WCB4IhgOHnxPxIREZUfubm5+Pe//405c+agU6dO2Lp1K6ysrJQOixRgbm6OkJAQDBgwAAcOHEBERATmz5+Pnj17YuTIkQgMDIShYcFfIlPJKbP7JAghsGv8Lpz8+iSCvgpC84nN8/VpM7sNHOo4AADSk9Kxot4K/DLmFww7PCxf39h1sXD0ckTSpSSc3XAWraa1ki1POJCA36f9jvYftUfrma2l9mu7r+HXsb+WyD75jvCVvd47Yy8STyWi42cd8509yTsz4N7RHR0+6QDgWU62DtyK3yb+Brf2btK+A4BboBs6LelU+MYF4BPqg+DNwdKZg8zHmVjZcCV2jtiJkcdHlsQuEhERlSohBCIjIzFt2jRkZGRg2bJlaNq0qdJhkR4wMjJChw4d0KFDB1y/fh07duxASEgIjI2NMWDAAAwcOBB+fn68gqKUlMmYBKEV+HnUzzj5zUl0W9mtwALhRRYOFvDo5IHbx29Dky0f6Z7yVwpuRt9E43cbw7WtK2LXxeZ7f/yeeACA3xg/WbtnkCcG/TLoNfam5KhUKjQa0QjaXC1uHrn5Su81NDFEra61ZP8xzGzM4Bbohvux90s6VCIiohKl1Wrx008/oUWLFhg8eDC6d++OrVu3skCgArm5uWHSpEnYs2cP5syZg6tXr6J9+/ZwcXHBuHHj8Msvv+DJkydKh/lGKfUzCVqNFjtCdyBuUxx6re0Fn6E+xX6vobEhtLla5GTkyC7fObPuDFQGKtQLrgdDU0P8MvoXJJ5KhLPv3zdVyfsmX52ohoW9hWy9BV2+pJS8/XqxEEq9nopT35+CgaEBnH2dUcW7ykvXJbQC987cg7Mfby5DRET6RwiBc+fOISIiAuvWrUNWVhZCQkLw1Vdf8dIiKhYjIyP4+/vD398f2dnZOH78OKKjozFhwgTcvHkTDRo0QNOmTdGwYUPUrl0b7u7ucHZ2hpmZ2ctXTjKlXiT8PPJn3D15F3039oVXf69ivy83KxfX91+HfW17mNn8/cEKIXB2/Vm4BLjA6i0r1OtbD7vG7cKZdWdkRYJXfy8c+uchbO69Gf7h/nDv4A6bt21KdN9KwuWdlwEAVf2qytqTLifBxMoE6ntq7By5E55Bnui7qS9MrU1l/R5eeIjr+64j60kWrv56FYYmhui1rldZhU9ERFQgIQQePXqES5cuIS4uDkePHsXBgweRlJQEf39/TJs2Df7+/jA2NlY6VCqnTExM0KZNG7Rp0wYAcP/+fZw+fRoXLlzApk2bcPPmTdy5cwcajQZWVlawsbGBlZUVzM3NYWpqClNTU5iYmMDMzAwWFhaoVKkSzM3NUaVKFTg4OMDJyUl6ODo6onLlyhXq0qZSLxLuxtxFpWqV4BLg8tK+Z384C6sqVsh8nImL/7uIbHU2eq/vLetz49ANpF5PRasPn41BMLczh3sHd5zbeA6dlnSCofGzb+Yru1bGqJOjcHDBQeyZsgeZqZmwessKNbvVRNv5bWFdzbrkd7YYEk8l4sTyE9DmanHnxB2c+/Ecmk5oimpNq0l9+vzQB64BrtLrG4dv4L+B/8Wu8bvQ+7/yfGQ+zkTSpSSkJ6Uj5a8UONV3AoqYWjgrK0t2+3O1Wl1i+/YqBg0ahKioKEW2TURvPgshkDf3nJ2dHdIr0B/2kpA3R/3z/774eFUqlQqNGjVC586dYWpqiitXruDKlSslGvebJCfHBMAXAIA1a9bA2Dj/rIdUMEtLSzRs2BANGzaEVqtFSkoKkpOToVarkZGRgaysLKjVauTm5iI7OxuZmZnIzMxEenp6icahUqmgUqlgYGAgFRcv/gsA06dPx8KFC0t02yWh1IuE7qu7I3JCJP4b+F+EHQiDhYNFoX1Tr6ciMyUTaQ/SkHQ5CXV710X15tVlfWLXxUJlqELdPnWlNq9/eOFa5DVci7yG2j1qS+0OtR3Qd2NfaDVaJF9LxrXIazj88WFc2n4JY8+ORaWq+WdEAp4VK5kpmdJr9w7uskHFryPjUQaSLiXBwOjZZUTNP2guKxAAyAoEAHBp7QLvwd44+8NZdPu2G4wt/v7WpUaLGqjR4tksSFlPs7A2YC02BG3AuIvjCpw+dvHixViwYEGJ7MvrOH78uKxYISIqSVkApD/B2Ty4KksmJiZwcHBAtWrVUL169XyXEeUdkL1MTk5OhT/LMGTIUADAqx67MndyRkZG0hmBlykod0IIpKWl4eHDh0hKSkJKSgpSU1NfehyTV1Brtdoi+127dg1CCL07S1HqRUL1ZtUx6NdB+KHzD1jfcT2G7hta6H0Knp/d6M6JO1jjvwZWzlbSLD856Tm48L8LqFS1Es5vPi+9L+vJsw8pdl2srEjIY2BoAIfaDnCo7YBqTathTas1iFkdg7bz2hYYR+r1VKjv/f0Ne9UmVQvsp4vnZzd6FQ51HaDN0UJ9Tw1bd9sC+5hWMkWT95rg51E/496Ze/mKDwAIDw/H5MmTpddnzpxBQEDAK8fzuv766y9YWVlxCrNXpNFooFarmTsdMHe6Y+50w7zpjrnTHXOnO+ZOrkymQHVp7YIBOwdgU7dN2BC0AUP3Ds13bf2LqjWthsZjG+OPpX/Ab7Qf7GvZ4+K2i8h+mo3a3WvL7k8AAG/7v40rv1xBRnIGzO3Mob6nhtVb+QdB5Q0AfnK78BHwbWa30WEvS9fjW48BFWBuX/SN4FSGz6pQrabgqjXvGrw8HChGRERERC8qkylQAcA90B39t/XH/dj7+OGdH5Cd9vLTv63DW8PA2ACHPzoM4NmZgkpVK6H3+t54Z/k7skfnZZ2hydbg3I/nAAAnVpzA3vC90OTIZw269NMlAJANctYnqQmp+XKTeiMVZ/97Fh4dPaRB3KkJqchJz5H10+ZqceY/Z2BZxVJv94+IiIiI9F+Z3UwNAGp2qYngzcHY0m8LNnXfhEG/DoKxeeHXzFm9ZQXfUb74c8Wf8B3li+v7rqPZB82gMsh/zZZzI2c41HVA7LpYNHmvCRzrOeLXsb/iQsQF1OtfD+a25rgfex/nt5xHrW618t0ITV8kxydjfcf1cG3nCjtPOzy9+/RZcVStErp/113q9+jKI2wI2gCXti6w87BDZmrmszMt6mwE/xgMI9My/WiJiIiI6A1SakeSVbyroMm4JjCzlc9LW6dXHfTf2h/xe+IRvzsedXrVKbQvAPh/6A+hEbh17BYaj20Mv1F++frkab+oPa7vv45sdTYaDGyAOr3q4FrkNTy88BBPE5/Csb4jQseG4m3/t0t8fwGgRssaaDKuCQxN81/HZmhiiCbjmqBGyxpFrsM90B2jTo7Cpe2X8PDiQxhbGqPnf3qiZtea0sxNAODRyQMjT4zEpR2XkHTx2XSp7Ra2Q+2etVkgEBEREdFrUQld5jGjN8apU6fg5+eHmJgY+PqWzdkVDgzSHXOnO+ZOd8ydbpg33TF3umPudFdRclfcY78yG5NARERERETlA4sEIiIiIiKSYZFAREREREQyLBKIiIiIiEiGRQIREREREcmwSCAiIiIiIhkWCUREREREJMMigYiIiIiIZFgkEBERERGRDIsEIiIiIiKSYZFAREREREQyRkoHQMrKyMgAAFy8eLHMtqnRaJCeng4LCwsYGhqW2XbfBMyd7pg73TF3umHedMfc6Y65011FyV3eMV/eMWBhWCRUcAkJCQCAkJAQZQMhIiIiojKTkJCAVq1aFbpcJYQQZRgP6ZmkpCTs3r0brq6uMDc3L5NtqtVqBAQE4ODBg7CysiqTbb4pmDvdMXe6Y+50w7zpjrnTHXOnu4qSu4yMDCQkJCAoKAgODg6F9mORQGXuyZMnsLGxwePHj2Ftba10OOUKc6c75k53zJ1umDfdMXe6Y+50x9zJceAyERERERHJsEggIiIiIiIZFglU5kxNTTFv3jyYmpoqHUq5w9zpjrnTHXOnG+ZNd8yd7pg73TF3chyTQEREREREMjyTQEREREREMiwSiIiIiIhIhjdTozL1888/Y/PmzXj8+DEaNWqEiRMnwt7eXumw9MqTJ0+wdetW7NixA2lpafjpp59gaWmZr59Go8H333+P3bt3Q6vVol27dnj33XdhYmKiQNTKy8zMREREBA4fPoyHDx/C09MTw4cPR7169fL1ffz4MZYuXYqTJ0/CysoKffr0QXBwsAJR64ecnBxERETgwIEDePDgAd5++20MGjQILVq0yNf3+vXr+Pe//40rV67grbfewogRIwrsVxH9+eefCA8Ph5WVFXbs2JFv+R9//IHvvvsOiYmJqFmzJsaPHw8PD4+yD1QP3Lp1C8OGDStwWWRkJIyNjWVt27dvx5YtW6BWq+Hr64sPPvgAlStXLoNI9deNGzewatUqnDt3DlWqVEFYWBhatmwp65Obm4tVq1YhKioKQggEBgZizJgxMDKqmId/QUFB0Gg0BS4LCQlBWFiY9Do1NRVLly5FTEwMrKys0K9fP/Tu3buMItUPPJNAZWbRokUYMGAAfH19MXz4cBw8eBCNGzfGw4cPlQ5Nbxw5cgQ1a9bEwYMHYWRkhKioKOTk5OTrJ4RAcHAwFi1ahB49eiA4OBjLli1Dly5dCv0F+CbLyclB9erVMXv2bNSrVw9hYWF48uQJfHx8sHr1alnf1NRUNGvWDJGRkQgLC0OLFi0wbNgwfPjhhwpFrzx/f3/s378fLVu2RFhYGIQQaNWqFebPny/rd/78eTRq1Ah3797FmDFj4OTkhDZt2mDz5s3KBK5HcnJyMHz4cBw+fBgHDhzIt3zr1q3w9/eHg4MDxowZg3v37qFRo0Y4e/Zs2QerB9LS0hAVFYUOHTpgxowZsoehoaGs7+zZszF06FA0b94cYWFh2LNnD5o2bYrk5GSFolfe7t270aBBA6SmpmLEiBEICAhAeHg4fv/9d6mPVqtFjx498Pnnn6NXr17o3bs3Pv/8c/To0QNarVbB6JXz4Ycf5vt5c3Z2RlRUFGxtbaV+ycnJaNq0Kfbs2YOwsDA0a9YMQ4YMwaxZsxSMXgGCqAzEx8cLIyMjsWzZMqnt8ePHwsHBQbz33nsKRqZfUlJSRHp6uhBCiHnz5gkAIiUlJV+/rVu3CgDi6NGjUltsbKwAINasWVNW4eqNjIwM0bFjR5GamiprHzp0qLCwsBBPnz6V2qZNmyZsbGxEcnKy1LZq1SqhUqnEuXPnyixmffJ8LvKEhYUJY2NjWe7atWsn/Pz8hFarldpGjx4tHBwcRFpaWpnEqq/mzZsnatasKYKDg4WNjY1sWUZGhnBychKjR4+W2rRarfDz8xNt2rQp40j1w8WLFwUAERkZ+dJ+BgYGYtWqVVJbcnKysLGxEZMnTy7tMPXS3bt3hY2NjVi6dGm+ZU+ePJGe//DDDwKAiImJkdpOnDghAIiNGzeWSazlQYMGDYSjo6PIzs6W2iZNmiRsbW1lf1O+/vprYWBgIC5duqREmIrgmQQqExEREdBoNAgJCZHarK2t0bt3b/z4448V9luNF1WuXBnm5uYv7bdx40Z4eHjILvPw9vZGo0aN8MMPP5RmiHrJxMQEP//8M2xsbGTtAQEBSE9Px6VLl6S2jRs3olu3brJvjQYNGgRDQ0Ns2rSpzGLWJ8/nIk/NmjWRk5OD9PR0AEBiYiIOHDiAkJAQqFQqqV9oaCiSkpKwZ8+eMotX35w7dw6LFy/G8uXLC5w6ce/evXjw4AGGDBkitalUKgwdOhSHDh3CrVu3yjLccuXHH3+ESqXCwIEDpTZbW1v06NEDGzduVDAy5Xz99dcwMjLC2LFj8y2rVKmS9Hzjxo3w8vKCr6+v1NakSRPUrVu3Qv6dKEhsbCzi4uIwePBg2SVuGzduRI8ePWR/U/J+91WkvxMsEqhMxMTEoHr16vkORnx8fJCcnIyEhARlAiunYmJi4O3tna/dx8cHp06dUiAiZRkYGBR4cHb+/HkAgJ2dHQDg/v37uHPnTr7cWVpawtPTs0LmriApKSnYvHkz2rZtCycnJwDAqVOnIITIlzsfHx9peUWk0WgwfPhw9OzZE506dSqwT0xMDAAUmrvTp0+XbpB67IsvvsA777yDgQMHYtWqVcjKypItj4mJgbu7O6ysrGTtPj4+uHfvHu7evVuW4eqFPXv2oHnz5jh48CBCQ0PRtWtXTJ48GfHx8bJ+/DvxcuvXrwfw7MuOPHfu3MH9+/fz5a5SpUpwc3OrULljkUBl4t69e3B0dMzXntd27969sg6pXCsqnykpKcjOzlYgKv1y48YNrFy5Ek2bNoW7uzuAv3/OCstdRf45jI2NRYcOHdCyZUu4uLjA29sbO3fulJYXljtLS0uYm5tX2Nx9+eWXuHjxIr788stC+9y7dw8mJiawtraWtVf0338NGjTAO++8g9GjR6Nu3bqYOnUqWrVqhadPn0p9+Lcjv/j4ePz555/o168ffH19ERoaitOnT6NRo0Y4efIkgGfj1u7fv19o7u7fv1/WYesdrVaLTZs2wcfHBw0bNpTa+XfibxVzeDuVuZycnHyD0QBIMywUNDiXClecfFbUWY4AICMjA/3794dGo8GqVauk9ryfs8JyV5F/Dt9++23MmDEDT548wb59+7B69WrUrl0bs2fPBsDcFeTatWuYO3cuFi1ahGrVqhXaj7//8nN1dcWpU6ekHPTq1QsdOnSAv78/Fi9ejI8//hjAs9wUdAlmRc5dZmamNPNdjx49AAA9e/aEp6cnpk6digMHDkCj0UAIUejPnVarRW5uboWd5Qh4dhng3bt3MW3aNFn7y37XZWRklEl8+oBnEqhMWFlZSdc2P0+tVgOQX0dJL1dUPg0NDWFhYaFAVPpBo9Fg0KBBiImJwQ8//CBd0gFAumShsNxV5J9DW1tbdOjQAX369MHy5csxa9YszJ07V5p9p7DcCSGQnp5eIXM3duxYeHp64v333y+yn5WVFbKysvKNvarIv//MzMzyHaC2bNkSPj4+iIyMlNr4tyM/a2trmJiYoFu3blKbqakpunbtiujoaOTk5MDIyAimpqaF5s7c3LxCFwgAsGHDBhgbG2Pw4MGydv6d+BuLBCoTnp6euHnzJoQQsvYbN24AgHQ5CBWPp6dngeM4bty4ATc3N9nA0ormvffew44dO7By5Ur06dNHtszNzQ2GhoaF5q6izllfkC5dukAIIV2+4OnpCQD5cnf79m1oNJoKmbu4uDhkZmaic+fO6NChAzp06ICoqCikpaWhQ4cOWLp0KYBnudNqtbh586bs/Xm//ypi7gpjb2+PJ0+eSK89PT2lPD3vxo0bMDAwgJubW1mGpxc8PT1hZWUFAwP5IZy1tTU0Gg0yMzOlfvxdV7C0tDRs374d77zzTr7Litzd3WFgYMDcgUUClZHAwEA8ffo034Cf/fv3w8/Pr8LfFOdVBQYG4uTJk7JvOrKzs3HkyBEEBgYqGJmy5syZg1WrVmHJkiUYMWJEvuWmpqZo1aoVDh48KGu/cOECHjx4UKFz96LExEQAkG526OfnBxsbm3y5279/PwBUyNxt2bIFX3/9tWzOdS8vL5iammLGjBkICgoC8HduCsqdtbU1mjRpUuax66OsrCzExcWhZs2aUltgYCCSkpKkSQjy7N+/Hy1atCjWbHBvmo4dOyI5ORkPHjyQtV+8eBFOTk7SN92BgYH4448/ZIPBMzIycPz48Qr5//V527dvh1qtlt08LY+FhYU0MPx5Z8+eRXJycsXKnZLzr1LFkZ2dLTw9PUVQUJA0F/Gvv/4qVCqV2LRpk8LR6aei7pOQkJAgLCwsxLRp06S2hQsXCmNjY3H+/PkyjFJ/LF++XAAQs2fPLrLfzp07BQCxdetWIYQQubm5okePHqJGjRrSPSoqkrNnz4qIiAjZvQ8SEhJE/fr1haurqywnc+fOFVZWVtLPWEpKiqhTp44ICgoq87j11eDBg/PdJ0EIId555x1Ru3Zt6Z4UFy5cEFZWVmLWrFllHKF+WLdunbh8+bL0OjMzU4waNUqoVCqxa9cuqT0jI0O4uLiIHj16iJycHCGEENu2bRMAxPbt28s6bL3w4MEDUblyZREWFiY0Go0QQoj9+/cLAwMDsWDBAqnf1atXhampqZgzZ47UNmvWLGFqaiquXbtW5nHrk6CgIOHg4CC7N8Lz8n7GfvrpJyGEEDk5OaJr167CxcVFZGZmlmWoimKRQGXm4sWLol69eqJatWqiWbNmwszMTCxcuFDpsPTK48ePRWBgoAgMDBRubm4CgAgICBCBgYFi8eLFsr6RkZHCyclJ1KlTR9SvX19UrlxZ/O9//1MocmXdv39fGBgYCGNjYyl/zz+OHz8u6//FF18Ic3Nz0aRJE/H2228LT09PcebMGYWiV9b9+/dFWFiYcHJyEk2bNhUNGjQQpqamonfv3iI+Pl7WNycnR4wYMUKYm5uLli1bCjs7O9GmTRvx4MEDhaLXP4UVCUlJSaJdu3bCzs5OtGzZUlhYWIjQ0FDpwLei2b17t2jUqJFwdXUVLVq0EHZ2dsLZ2VmsX78+X9+zZ8+KWrVqiRo1aogmTZoIMzMz8emnnyoQtf6Ijo4WLi4uonr16sLX11eYmpqKCRMmiNzcXFm/nTt3Cnt7e+Hl5SXq1asn7OzspAPfiioxMVEYGhqK999/v8h+n332mTA3NxdNmzYVNWrUELVq1RJnz54toyj1g0qIFy4SJypFQgicO3cOjx8/Rr169aT56+mZnJycfKc48zg7O8PLy0vWlp2djTNnzkAIAR8fH5iZmZVFmHonKysLhw8fLnR5w4YN4eDgIGt7/Pgxzp07B0tLS3h7e+e7vreiSUtLw8WLF2FkZARXV9ciLwFMTEzEtWvX8NZbb8kuDaFn9+ZISkpCQEBAgcuvXbuGxMREeHh4oGrVqmUcnf65desWEhISYGdnhzp16hQ4owzw7G/H2bNnoVar4eXlxUtU8WwKz9jYWKjVatSrV0+6NPBFWVlZOHPmDFQqFXx8fAq8p0xF8vDhQ8TGxsLb21u6D0xhUlNTcf78eVhZWcHb27vCjfdjkUBERERERDIV+6szIiIiIiLKh0UCERERERHJsEggIiIiIiIZFglERERERCTDIoGIiIiIiGRYJBARERERkQyLBCIiIiIikmGRQEREREREMkZKB0BEVFEJIRASEoKsrCwAQGhoKLp37y7rM3z4cDx58gQA0KJFC0yZMkW2/KOPPsLp06cLXV5cn3zyCU6ePAkAaNeuHcaNG6fTekp7naVlxYoV2L9/PwCgcePGmDFjhsIRlb7y9PkQUdljkUBEpBCVSoXr16/j2LFjAAATExNZkXDz5k385z//kV6fPn06XxGwdOlSPHz4EADg5+encyzR0dH49ddfAQBWVlY6r6ck1hkVFYVvvvkGAGBtbY01a9aUSDxF+fPPP7F161YAQGZmZqlvTx+UxmdORG8OFglERApq3bq1VCQcPnxYtiw6Olr2+q+//kJiYiKcnZ0BAJcvX5YKBABo1apVKUdbNq5fvy4dsNvb2yscDRFRxcQxCURECvL395ee3759Gzdu3JBe5xUJxsbG+dpefG5iYoKmTZvqHEd4eDi2bNmCLVu2YPz48Tqvh4iI3gw8k0BEpKBWrVpBpVJBCAHg2dkEFxcXAH8XAd27d0dUVBQeP36M6Oho9OvXT7YceHapkZmZmWzd586dwy+//IIrV64gKysLzs7OaN++Pbp06QKVSiXre/jwYdn16Y0bN5Ytz8zMxLp163Ds2DGYmJigU6dOCA4OfuVr+Xft2oWdO3dCo9HA29sbI0eOhLm5ubSNkJAQJCQkSP2fPn2K4OBg6fWkSZNkZ0xeZR/ztrF27VocP35cth+v6/Tp09i1axfi4+ORmZkJR0dHNGvWDL1795b2DwDGjh0rnf0ZNmwYunbtKlvPsmXLcOjQIQD5x5i8yr6+ON5g7NixiIiIwL59+5CSkoLNmzcXuT/ff/89IiMjpdcqlQoWFhZwcXFBYGAg2rRp89Jtjhs3Dlu3bsXu3buRnZ2NZs2aISwsTJYPItJjgoiIFFWvXj0BQAAQY8aMEUIIkZqaKgwMDAQA8dVXX4nOnTsLAMLX11d6n4eHh/S+qVOnSu3p6ekiJCREWvbio1mzZuL27duyGLp27SotDw0NlS27e/euLMa8x4ABA8TQoUOl1127di10nUOHDhUjRozIt47atWsLtVothBDi6dOnhcac99i0aZPO+6jrfhRFrVaL4ODgQuN46623RFRUlNR/+vTp0rKAgADZurKysoSdnZ20fMuWLSXyeQ4ZMkT06tVL9p6cnJwiP/Nx48YV+TkEBQWJjIyMIj/vAQMG5HtfnTp18sVKRPqJRQIRkcLGjBkjHUTVq1dPCCHErl27pLaTJ0+Kjz76SAAQhoaG4unTp+LevXuyg68dO3ZI63v+4MzR0VEsXrxYrFmzRrRp00Zqb9iwocjNzZXeU9QBY6dOnaRl5ubmYv78+eLbb78VPj4+wtLSslhFgqWlpfDy8hLffPONmDZtmlQAARCffvqpEEKI3NxcsWXLFvHuu+9KyypVqiS2bNkiPW7duqXzPj6/H5aWlmLhwoVi5cqVws/Pr8j9KMrzBYK5ubmYN2+eWL16tfD395faLSwsxMWLF4UQQsTHxwuVSiUACJVKJf766y9pXVu2bJEVF9nZ2SXyeVpYWIiqVauKTz75RERERIgtW7YIrVZb5Gd++vRpWd43bNgg5s+fL+zt7aX3zJo1q9DP28LCQjRs2FCsXLlSLFq0SFhZWUnLAgMDi51fIlIOiwQiIoWtX79eOoBSqVQiKSlJzJw5UzqYzcnJEYcOHZL6/P7777IDSgDi4cOHQgghzp07J2s/dOiQtJ20tDTh6OgoLdu8ebO0rLADxgsXLsjW9+2330rLkpKSil0kWFhYiPv370vL8s6MABD9+/eXvW/16tXSMnt7+3z50mUfX9yP77//XnpPamqqqFSp0isXCXFxcYWuMysrS1SrVk1aFhYWJi0LCgqS2ufNmye1d+vWTWqfPXu2zvv6Yu7NzMxEQkJCvviLKhIK891330nv8fT0LHR9VlZWIjk5WVq2bt062X7ExcUVa3tEpByOSSAiUtjzg5eFEDhy5Ig03qB58+YwMjJC06ZNYWpqiqysLERHRyM1NVV6T506deDg4ABAPkOSSqXC0qVLsXTpUqlNo9FIzw8ePIj+/fsXGduJEydkr3v16iU9t7e3h7+/P3bv3v3SfWzdujWcnJyk11WrVpWeP3jw4KXvf54u+/jifjw/FsDGxgatWrXCb7/99kpxvDj7VM+ePaXnJiYm6Ny5M77//vt8fceOHSvlbN26dZg3bx4ePHggbd/Q0BCjR4/WeV9f1Lp1a2mcy6s4evQooqKikJCQALVaDY1Gg7t370rLr1+/DiFEgWM//P39YWtrK71+cezFiRMnUL9+/VeOiYjKDosEIiKFubq6onr16rh9+zYAYN++ffjzzz8B/F1AmJqaonHjxlIB8XyR8PxA3rwbrwHPCo68qUQL8vz0qYVRq9Wy15UrVy7ydWGqVKkie/38jE3i/wdtF5cu+/iy/bCxsXmlGF6Mo6B1PL+N5/t269YNNWrUwK1bt5CQkIADBw7g1KlTyM3NBfDsgLpGjRr53qfr55k3ZW5x5ebmom/fvti5c2eR/TQaDbKzs2Fqappv2cvy++LnQUT6h0UCEZEe8Pf3x48//ggA+M9//oOMjAyp/fk+R44cwbFjx5CdnS1rz1O9enXpuaGhIdavXy87IH/e830L8/w3/gBw69YteHp6Sq+vX7/+0nUAKPDbZl3pso8vHijfuHEDtWrVkr1+nTgAICEhQZab52dpejHmUaNGYe7cuQCAtWvXSnfNBp6daSjsfbp8nq+a++3bt8sKhNGjR6N169YwMzPD0aNH8eWXX0rLCivwXszni69ftXAhorLH+yQQEemB5w/08749NjQ0RPPmzaX21q1bAwDS09Olb51ffG9QUBAsLS0BPPum9/r16+jbty+Cg4OlR9OmTXHz5s1iFQkBAQGyKSu/+uor6fm+ffvyXcZTEp6fylWtVssKIkC3fQwICJCtd8mSJdLz6Oho/PHHH68cZ6dOnWBhYSG9/uyzz6SD5vPnz+OXX36RlvXu3Vv23pEjR8LI6Nn3dJs2bUJcXBwAwN3dHUFBQa+1r6/rzp070nNra2ssX74cISEh6NWrV7GLqePHj8susfriiy+k56ampmjbtm2JxEpEpYdFAhGRHnj+QD+Pj48PrKyspNd591R4XpUqVWTfXtvb28u+6Z01axY8PT3RrVs3BAQEwM3NDS4uLpgyZYrskqXC2NnZYfr06dLrFStWwNvbG+3bt0ePHj1Qr169V9nNYvH29paeZ2Vlwc/PTzowTk9P12kf7e3tMXXqVOk9q1evRoMGDRAYGIguXbqgbt26rxyng4ODrNhYvXo1vLy80LFjRzRr1gxZWVnS/kyaNEn2XmdnZ2kMQ05OjtQ+evRo2Wdc0p9ncfj7+0sxPHnyBF5eXujevTs8PDxw9erVYq2jbt266Ny5M9q3bw9vb29888030rJJkybB0dGxRGIlolKk3JhpIiLKo9FoROXKlWUzwLz//vv5+jVo0EDWp0+fPgWu7+effxbe3t6yvnkPDw8PMXXqVJGamir1L2qmG61WK8LDw4WRkZHUx8bGRkRERIi+fftKbcHBwbL3FbXO56d9ffF+AUIIMX78eGmq0OcfKSkpOu+jRqMR06dPL3A/QkNDX3l2ozxbtmwRtWrVyheDqampGDVqlCzm5+3du1fW38TERDx48KDAviX5eRanz/Lly2XTlqpUKjFmzJh8s2o9f6+EF9cXEREhbGxspDZDQ0MxZcoUodFoXim/RKQMlRCvOGKMiIhKxb59+5CcnCy99vX1hbu7u6zPH3/8gVu3bkmv69atCy8vr0LXefPmTVy5cgXp6elwcnJCjRo1UK1atXz9jhw5gsTERADPBlK/eMdl4NnA2JMnT8LExATNmzeHpaUl6tSpg8uXLwMApk2bhs8++6xY6zx9+jTi4+MBAI6OjggICMi3vdu3byMuLg7p6enSZTw9e/bMd01+cfcxz4MHDxATEyPbj5MnT0pjCJydnWWDwYvr6tWriI+PR1ZWFhwdHdGwYUPZ5UgvEkJg+/bt0Gq1AJ6dNWjXrl2R2yjJz/NlfVJTUxEXFwe1Wo369eujRo0auHv3Lo4ePSr16dOnDwwMnl2U0K1bN/z6668AgNDQUKxduxZpaWk4fvw4srOz4efnJ5vhioj0G4sEIiIq0p07d7B3714MHDgQJiYmAJ4d4H7xxReYNm2a1O/YsWOyMRRUsRRUJBBR+cXZjYiIqEgpKSkICwvDxIkTUbduXVSuXBmXL1+WzWw0fvx4FghERG8QFglERFSk6tWrY+TIkdi0aROOHz8uW+bm5obp06djzJgxCkVHRESlgZcbERFRsaSnp+Py5cu4ffs2TE1N4eHhAQ8PD6XDIj1RnHEQRFR+sEggIiIiIiIZ3ieBiIiIiIhkWCQQEREREZEMiwQiIiIiIpJhkUBERERERDIsEoiIiIiISIZFAhERERERybBIICIiIiIiGRYJREREREQkwyKBiIiIiIhk/g9r5ySXHF++rgAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "top_hits = significant.reindex(\n", + " significant[\"nES\"].abs().sort_values(ascending=False).index\n", + ").head(6)\n", + "\n", + "fig = ss.ridge_plot_ed(top_hits, result_obj[\"obj\"])\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "6b41049f", + "metadata": {}, + "source": [ + "#### Significance heatmap of all tested pairs\n", + "\n", + "`significance_heatmap` gives a gene x gene overview of every tested pair at once: cell color encodes CO/ME/not-significant, and color intensity scales with `|nES|` (the normalized effect size)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "fe136f2f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:52.052238Z", + "iopub.status.busy": "2026-07-14T19:10:52.051940Z", + "iopub.status.idle": "2026-07-14T19:10:52.725521Z", + "shell.execute_reply": "2026-07-14T19:10:52.724140Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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TqJERERERFTqfZYzgyEhIRg5ciRmzpyZa8zEiRMRFRUlfH716hV8fX3RvXt3tfEVKlTAuHHj1B5Ur6SlpYWxY8eiSZMmRR88ERERERHRRyBRKBSKkh4E0efm6pqrGstt389eY7mJiOjzYWZmVtJDIKJS7rNcJkpERERERER5YzFIRERERERUBrEYJCIiIiIiKoNYDBIREREREZVB3ECG6BMTsj0k/6AP8Lj+Y43llkgkGsvtGJH7Tr/FIdE1UWO57ctxU6CPLfJgpMZy6+hrbqPuah2raSy3psVdjNNYbvNm5hrLrWnJYckay12jVQ2N5SaizwNnBomIiIiIiMogFoNERERERERlEItBIiIiIiKiMojFIBERERERURmkubfcP5KsrCxERETkG6evrw8rKyu8efMGL168ELVXrVoVWlqqdfHDhw+hbn+dqlWrwtDQUKU9NjYWBgYGMDIyKuRTEBERERERfVyffDEYHh4Ob29v4bNcLsfTp09hYWEBY2Njob1z585YtWoVtmzZgnHjxsHOzg4AkJiYiHfv3uHbb7/F0qVLhfiYmBjY29vDysoKUqlU1GdgYKBwf1RUFJYvX47du3dDV1cXsbGxcHJywvr169G0aVNNPjoREREREVGRffLFoLOzMx48eCB8PnToELy9vbF161Z06tRJJT4oKAjm5uaie77//nssXboU7u7u6NWrlxAHABcuXIC1tXWu/R89ehSenp5Yvnw5pFIpYmJi8OWXX6Jr166IiYmBtrZ2cT0qERERERFRsfns3hkMDg4GANStW1ftdZlMBmdnZ1HbuHHjAAD//fef0BYUFAQTE5M8C0EAGDlyJLp16ybMHlauXBk+Pj6Ij49HTExMkZ+DiIiIiIhIkz67YjAkJAQVK1ZEtWqqB/NmZGTg1q1bKsXgmzdvAAC6urpCm0wmg6urK2JiYvDy5ctCjeHKlSuwt7dHlSpVivAEREREREREmvfZFYPBwcG5zgqGh4cjNTVVpRg8fPgwAOCLL74Q2oKCgnDhwgW4u7ujatWqqFq1Knbs2KE274sXL3D//n2cP38eQ4YMQXh4OPbu3QuJRFJMT0VERERERFS8Pvl3Bt/39u1bPHz4EF5eXmqvy2QyAICRkREePHiA169f49SpU5g/fz769++Pjh07AsjehKZnz5747rvvYG5ujqSkJAwePBiDBg2C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a2uLmTNnws7ODjY2NkhOToabmxsmTpyIVq1aYcaMGXj8+DHmzZuXaXutrKxQvHhxJoNERERERJSv6UnG5LmPLDY2Fi9fvoSjo6PO9d713KSkJDx+/BhmZmZqcxCTkpIQGxsLW1tbGBq+Pwd+9eoV4uLiYG9vr7a34MOHDwEARYoUyfK5jx49goi8sw4REb1bTExMXjeB6F8tq7UYiIgy5FkySERE9C5MBolyhskgEb1Pnq4mSkRERERERHmDySAREREREdEniMkgERERERHRJ4jJIBERERER0Scoz7aWICIiepcPufgFF6chIiJizyAREREREdEnickgERERERHRJ4jJIBERERER0SeIySAREREREdEnKFsLyCQlJWHv3r24cOECEhMTUa5cOTRv3hzly5dX6pw/fx4rV64EAMydOxd2dnZqMSIjIzFr1iwAwODBg+Hj46NxnpCQECxatAiurq4YNWqUxvE3zwEAZmZmcHZ2Rq9evVCwYMEs2/7333/jwoULiImJQdGiReHp6YkWLVrAwMBA5za87ezZs1i9ejVKlCiBKVOmvLc+ERERERFRXtC5Z9Df3x+urq6YNm0azM3NUaZMGWzfvh0uLi6YO3euUm///v1YvXo1Vq9ejYCAAI04EydOVI4bGxtneq5vv/0Wq1evxo8//pjp8f3792PdunWoXr06qlevjqJFi2LGjBlwc3PLdKW4Q4cOoUKFChg9ejREBM7Ozrhz5w66dOkCJycnxMXF6dyGN926dQvt2rXD6tWrERIS8t76REREREREeUWnnsEXL16gefPmqFSpEv744w/o67/OJb/99ltMmTIFqampSl2VSgVXV1dERUXh6tWraNiwoXLsypUr+PXXX1GpUiWEhobC2dlZ41z79u3D4cOH0axZM/z555949uwZbGxs1OqoVCqULVsW/fr1U8p8fX3RrFkzrF27Vq0nb9++fWjfvj2GDBmCBQsWKG0HgBEjRqBz586wsLDQuQ1vvjYtW7ZEmzZtsGLFCnh7e2vxihIREREREeUNnXoG9+7di+joaAwdOlQtmdLT08P06dMxYMAApczf3x8+Pj5wd3dHaGioWpxx48ahWbNmsLKygoeHh8bwzNTUVIwZMwaff/65EjMwMFCjPRm9lG/y8vICANy5c0cpi46ORu/evdG4cWMsXLhQre0A4OrqiqNHj2ar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evUq0+fp6enBzs5OrSwtLQ3Pnj2DhYUFTExM3htPX18fBQsWRIECBbKs+775gqmpqXj+/Hmmx8zMzN674AwRERH9O3ABGSL6r8oXq4kSERER5VdMBonov+qj7zNIREREREREeY/JIBERERER0SeIySAREREREdEniMkgERERERHRJ8gwrxtARERElJ99yIVY/q2L0xDRfwN7BomIiIiIiD5BTAaJiIiIiIg+QUwGiYiIiIiIPkFMBomIiIiIiD5B+W4BmYSEBBw4cAAXLlxATEwMihYtCk9PT7Rr1w4FChQAAJw5cwa//PILAGDRokUoVKiQWowbN25g6tSpAIARI0agZMmSGDFixHvP7eHhgXHjxqnFBwAzMzM4Ozvjq6++go2NjcbzIiMjsWfPHly/fh3W1tZo06YNqlWrlt2XgIiIiIiI6IPLVz2De/fuhZOTE2bMmAErKytUq1YNL168QN++fVG6dGnExcUBAP78809s3rwZmzdvRmBgoEac7777Tjlubm6OtLQ0NG3aVHmULl0amzdvhpWVlVp5/fr1lfjbtm1Tyl1cXLBo0SK4ubnhyZMnaucaPnw4XFxccOXKFXh5eSE6Oho1a9bEihUrPvjrRURERERElF35pmdw165d6NSpE8aOHYvZs2erHRs2bBg6deoECwsLAIC/vz/c3Nxw//59hIaG4rPPPlPqXrx4ETt27EDVqlURHByMChUqQF9fHz169FDqrFy5EgDQt29f+Pr6arTF398fZcuWVXuOp6cnPv/8c6xduxZjxoxRysPCwnDu3Dn4+PgAAAYMGIBnz55h5MiR+Oqrr2BomG9eYiIiIiIiIkW+6Bm8f/8+vvrqK7Rq1UojEQQAJycnHDlyRPm3SqWCt7c33N3dcfXqVbW6Y8eORatWrWBubg4PDw/o62teYkBAAAwMDFCxYsVM26NSqeDq6qpW5ubmBgC4d++eWvmvv/6qJIIZPD09ER8fj8jIyHdcNRERERERUd7JF91Wfn5+ePHiBRYsWJBlnYy5ek+ePMH9+/fh7e0NS0tLhIaGKnUOHDiAf/75B4GBgahduzY6d+6caazAwEA4OzvDxMRE41hG/J49e6qVh4WFAQBcXFzUyq2trTViqFQqGBoaonDhwlleDxERERERUV7K82QwNTUVmzZtQt26dVGuXLn31vf39wcA+Pj4wNzcHDt2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", + "text/plain": [ + "
" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fig = ss.significance_heatmap(result_obj[\"result\"], title=\"TCGA LUAD\")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "a9daaa12", + "metadata": {}, + "source": [ + "#### Oncoprint: observed vs. a null-model simulation\n", + "\n", + "`oncoprint` draws a multi-gene co-mutation matrix (with a per-sample TMB track) for the observed data side by side with one retained null-model permutation, so you can see at a glance how a real co-mutation pattern compares to what the background model alone would produce for the same gene panel. We use the gene set from `top_hits` above." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "6ab2203a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:52.729596Z", + "iopub.status.busy": "2026-07-14T19:10:52.729111Z", + "iopub.status.idle": "2026-07-14T19:10:53.260060Z", + "shell.execute_reply": "2026-07-14T19:10:53.258780Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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3tkMlMlpRn/nzh0AwJkzZ7BmzRq135cqcWtK27Zt4e7ujqCgINy9excBAQEIDw9HfHw8KlSogKZNm6JPnz75JgmaN2+Ohw8f4smTJ7h58yaCg4NhZGSE+vXrY+DAgQp/e2vWrKnWb4+i/4/Tp09LXn/zzTcq10lEVNSMBCHP6CpEREREeigwMFCcAg54fwOXk3Sg9wNaTpgwQXz99OlTdO7cWYcRGYYOHTrg5cuXAN4PdJl37AIiIn3CLgBEREREJcDo0aMlA1jmntOetOPp06fizT8ALF26VIfREBHljwkAIiIiohKgVKlS+PPPP8XXx48fR0BAgA4jKvnWrl0r/nvAgAHo37+/DqMhIsofxwAgIiIiKiFGjhyJ+fPni/PSOzs7S7pNkOYkJSWhcuXK4jgC8+fP13FERET54xgAREREVCzExMRg3rx54msLCwts3rxZhxEREREVL0wAEBERERERERkAjgFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDEApXQdgSJKSkvD06VMEBwcjLi4OFhYWqFevHjp37oyyZcsq3fb169e4cuWK+LpZs2YYMmSItkOmXE6cOIGwsDDx9ahRo1CvXj0dRkRERJrk4+MDR0dHREZGwtjYGFWrVkXVqlXRokUL1KpVS+42giBg8+bNkmU//vhjEUSrGfoUvz7Fogk2NjYICAgAALRp0wb9+/fXcURU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nSaZyFBlZWWhWrVq4lgEX375pY4jKt44BgARERERURHKzs7G/PnzJcs2bdqkm2CIyKAwAUBERERERERkADgGABEREREREZEBYAJAw5KTk/Hy5UskJyfrOhQiIiKSg+dqIiIyVEwAaJiXlxc6dOgALy8vXYdCREREcvBcTUREhooJACIiIiIiIiIDwAQAERERERERkQFgAoCIiIiIiIjIADABQFqRkZGh6xCIiIi0IjMzE5xFmYiIiiMmAEjjAgIC0K9fP12HQUREpBWzZ8/G9evXdR0GERGR2pgAICIiIiIiIjIATACQyubPn49bt27pOgwiIiKNuHDhgq5DICIiKlJMAJDKkpKS2LefiIhKjPT0dF2HQEREVKSYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAAVKU9PT12HQERERAqEhIQgLi5O12EQEZGWMAFARWrw4MG6DsHgCIIAQRB0HQYRERUDf//9N27cuJFvuaysrCKIhoiINI0JAKICuHnzJlavXq3rMFSyatUqHDhwQNdhEBHptejoaIwdO1bXYRQbTZs21XUIRERUAEwAEBVAQkICwsLCdB0GERFpSGZmJt69e6frMIiIiLSKCQAiA/D27VvEx8frOgwiIioAHx8fJCQk6DoMIiIqAZgAIDIAf/75J54/f67xeidPnoyIiAiN10tERP9vxYoVcHZ21nUYavHy8sKCBQt0HQYREeXBBAARqczLywvHjh0TX3t7eyMjI0OHERERadfKlSuRmZmp6zCKneTkZPj6+uo6DCIiyoMJACJSWWBgIO7cuaPrMIiIisyJEyeYACAiohKDCQAqcRYsWFDsmkoSEVHxMm3aNPj5+ek6DCIiIrUwAUAlTlBQEJKTk3UdBhERlWABAQFIT0/XdRhERERqYQKAiIiIiIiIyAAwAVBCsF82ERFR4cTFxcHJyUnXYRAREWkNEwBaUtQj306ePLlI90dERFTSvHr1CqtXr9ZYfV5eXhqri4iISBOYANCSOXPm6DoEKqC0tDT8/vvvug6D9FBYWBiioqJ0HQYRFRODBg0S/21ra4vHjx/jzZs3HDuAiIh0RqMJgIyMDLi6uuLJkydISUnRZNVERSY9PR1nzpzRdRikh/bv3w8bGxtdh0FExdCLFy/g5eWF6dOn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Y8eO4vKcwZTatGkjdzueq4mIiN7jLAD/M3z4cPj5+cHJyUlcFhsbi1u3bmH48OE6i4uIiIje++STT1C2bFlxZOUc586dg6WlpZggICIiIvk4BsD/ZGVloVu3bkhLS8PBgwdRoUIFzJs3D8+fP4eTkxNq1Kih6xCJiIgM3po1a/D777/j0KFD6NmzJ06cOIGffvoJNjY2GDFihK7DIyIi0mtMAOQSGxuL3377Dba2tkhPT0f37t2xZs0aNGnSRNehERER0f/s3LkTO3fuREhICCwtLfHzzz+ztR4REZEKmADQAssNlroOgUinfBb46DoEIiKleK4mQ8dzNZFh4hgARERERERERAaACQAiIiIiIiIiA8AEABEREREREZEBYAKAiIiIiIiIyAAwAUBERERERERkAJgAICIiIiIiIjIATAAQERERERERGQAmAIiIiIiIiIgMABMARERERERERAaglK4DyC08PBw//PBDvuU+/PBD/Prrr3jw4AG2bdsmLi9dujQsLS3xzTffoE6dOuLyjIwMTJw4EYIgyNT1xx9/oGnTpuLrwMBAnDlzBp6enihTpgx69OiBkSNHwtiYuRIiIiIiIiIqvvQqASAIAoYPHy6+9vT0xO+//445c+agW7du4nJLS0sAgL29PWxsbHDkyBEAQHR0NDZu3Ih//vkHzs7OaNCgAQDA3d0dJ0+exLp161C/fn3JPuvVqyf+e+fOnTh69Cj69++Pjz76CK6urpgwYQJGjRqFEydOaOttExEREREREWmdXiUAatasiXHjxomvd+/eDQCYMmUKrK2tZco7OjqiefPmkm06dOiArl274uDBg1i2bJlYztjYGN999x3Kli2rcP9DhgzBzJkzJcsyMjKwbds2bN26FdWqVSvU+yMiIiIiIiLSFb1u1+7s7IxSpUqhVatWctc7OTmhdevWkmWNGzcGAAQHB4vLHB0d0bRpU6U3/wBkWgcAgJmZGUxMTFCmTBl1wyciIiIiIiLSG3rVAiAvFxcXfPDBBzA3N5dZFxERgeDgYJkEgJeXFwCgWbNm4jInJydkZWVh+vTpMDY2hpWVFaZOnYrSpUsr3b+XlxcOHTqEefPmoVy5chp4R0RERERERES6odcJAFdXV3z66ady1zk6OgKAJAEQHx+P3377DZUrV8bXX38N4P24An5+fvjiiy/Qpk0bvHnzBr/88gt27NgBBwcHmVYBK1asgKenJ0JDQxEQEID169djypQpCmNMS0tDWlpaYd8qUYkSHx+v6xCISIMqVqyo6xCISMN4riYqeVQ5X+ttAuDdu3eIi4uDlZWV3PVOTk4A3o8TcPbsWcTHx+PZs2eoU6cObty4gRo1agB434ff0dERVapUEbf96KOPMGzYMOzdu1dm1oEOHTrA0tIS7u7uePnyJa5evYrJkyfDyMhIbhyrV6/GypUrJcuarG9S0LdNVCJYWFjoOgQi0iB5s+gQUfHGczVRyaPK+VpvEwDOzs4A3k/5J4+TkxOqVauGqVOnwsjICGXKlMGGDRtkxgswMzOT3PwDwKBBg2BkZAR3d3eZeocOHSr+u3Hjxpg5cyZmzZqF3r17y41j8eLFmD9/vmSZ9W7ZAQuJDElcXJyuQyAiIiIleK4mMkx6mwBwcXEBAIUtABwdHdG+fXvJDACqioyMhCAIYisBRRo2bAgACA8PV1jG3Nxc7hgFRIaMzYWJiIj0G8/VRIZJb2cBcHZ2RtWqVVGnTh2ZdSkpKfD29ka7du0KVPfp06dhZGSE0aNHKy1369YtGBkZoUOHDgXaDxEREREREZG+0NsEgIuLi8Kn/y4uLsjKylK4PsfVq1fFwQJz3LhxA7/99ht++uknsXvBvn37EBoaKil38uRJbNy4Ed9//z2aNGGffiIiIiIiIire9LILQFJSEnx8fPDZZ5/JXZ9zU59fAiAxMRHTpk2DhYUFmjZtisDAQLx58wa//vorFi9eLJbz8vLCggUL0KxZM9SqVQteXl6Ii4vDn3/+iYULF2rujRERERERERHpiJGgh0P7JiQkwNbWFu3bt0fz5s1l1ru4uMDDwwOjRo1CqVLKcxjp6elwdnZGQEAAatasifbt26NMmTIy5WJiYuDs7Izo6GjUqlULHTp0KHDffssNlgXajqik8Fngo+sQiIiU4rmaDB3P1USGSS8TAMUdLyrI0PGigoj0Hc/VZOh4riYyTHo7BgARERERERERaQ4TAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAI0EQBF0HQURERERERETaxRYARERERERERAaACQAiIiIiIiIiA8AEABEREREREZEBYAKAiIiIiIiIyAAwAUBERERERERkAJgAICIiIiIiIjIApXQdQElkucFS1yGozGeBj65DoBKoOH0HSgJ+j4nUx9+posPfKP1U3L4D/BwRaQZbABAREREREREZACYAiIiIiIiIiAwAEwBEREREREREBoAJACIiIiIiIiIDwAQAERERERERkQFgAoCIiIiIiIjIADABQERERERERGQAmAAgIiIiIiIiMgCldB1AjsjISLi5ucHKygqVK1eWrHNzc0NkZCSaN2+OOnXqiGVzlC1bFk2bNkWVKlWU7iMpKQnPnj1D1apV0bZtW4XlMjIy4OnpidTUVFhaWqJq1aqFe3NEREREREREOqY3LQCOHDmCvn37IiIiQrJ83bp1sLKywsGDB8Ub8ZyyK1aswIoVKzB58mRUr14d33//vdJ9/P777+jbty++/PJLuetTU1Px888/o1q1ahg5ciRmzZqFWrVqoVu3brh9+7Zm3igRERERERGRDuhNAsDJyQnly5dH06ZNAQBpaWmYOHEiFi9ejDVr1uDgwYMwNzcXy9asWRN37tzBnTt34OHhgRUrVmD79u04ffq03PrfvXuHzZs3o27duvDy8kJGRoZMmZEjR+LYsWO4desWvL298eLFC3h4eCA1NRX29vbae/NEREREREREWqZXCQArKysYGxsjNDQUffr0wcWLF/Hff/9h4cKFMmVbtGghWTZ58mQAwNOnT+XW/8svv6BGjRpYvny52MQ/t1u3buHKlStYvXo1OnToIC5v1qwZbt++jaFDh2rgXRIRERERERHphl4kANLT0+Hp6Qlra2u8fPkSnTp1QkREBB4/foxPP/1UbtmWLVtKlickJAAAKlSoIFP/48ePcfr0aSxduhTt2rUDALi4uEjK5Ly2tLSU2d7CwgI9evQo8PsjIiIiIiIi0jW9SAC4ubkhIyMD7969Q8+ePdGsWTM4ODigVatWCsvmTQAcPXoUxsbGGDVqlMw28+fPR9OmTTFlyhSx5UDeBEDO8uXLl8Pd3V1Tb42IiIiIiIhIL+jFLABOTk4AAFtbWzRt2hRXr16Fqamp0rLJycm4c+cOEhMTYWdnh8OHD2PPnj1o06aNpPyJEyfw5MkTHDt2DKVKlUKFChVQr149mQTAoEGDsG7dOqxfvx5t2rRBo0aN8PHHH2PKlCno3r27wtjT0tKQlpZW8DevY/Hx8boOgYgKid9j0raKFSvqOgQqxvgbRZrAzxFR/lQ5X+tFAsDR0RGlSpXC33//jblz52Lr1q2YP3++wrLGxsawt7eHnZ0d3r17B39/f/z111+YOnWqpGxqaioWL16MNm3aYNy4ceLyFi1ayCQAAGDhwoWYP38+nj9/jrt372LPnj3Yt28fduzYgVmzZsmNZ/Xq1Vi5cqVkWZP1TdQ9BDpjYWGh6xCoBCpO34GSgN9j0jZBEHQdAhVj/I3ST8XtXM3PEVH+VDlfGwl6cFbv2bMn4uLi4OLigunTp+PAgQOwt7dH//795ZaNioqCh4cHACA7OxuTJ0/G8ePH4eXlJc4iALy/Of/111/x888/Y/DgweLyjRs34tKlS4iIiEC1atUUxhUfH486derA0tISzs7OcsvIawFgvdtarfevS44zHHUdApVAxek7UBLwe0zaVhJbAFhukB3zh7SDv1H6qbidq/k5IspfsWgBIAgCXFxcMHz4cADA9u3b4erqirFjx8LBwUEyKF9O2dwj8hsbG2P58uU4cuQIDh48iD/++AMAEB4ejjVr1qB58+Z4+vSpZHaA2NhYAO/HAejXrx9SUlJQpkwZmdgqVqyIcuXKKY3f3NxcnJ6wOCqJF3VEhobfYyLSZ/yNIk3g54hIM3Q+CKCPjw/i4+PF0fnNzMxgY2MDU1NTDB8+HImJiTJlra2lGUtLS0tYWVnh/Pnz4rKlS5ciKysLd+7ckfk7ceIEgP8fCHDatGm4d++eTGz//fcfwsPDMXr0aE2/bSIiIiIiIqIipfMEQM6gfrlv6uvWrYuzZ8/i1atXmDhxotiXIads+/btZer57LPP4OHhgTdv3sDd3R379u3DggULULt2bZmyTZs2hampqZgA8PHxQb9+/TBjxgxcunQJZ8+exQ8//IAxY8Zg/Pjx+OWXXzT8romIiIiIiIiKls4TAMnJyejdu7fYAiBHjx498O+//yI6Ohq2traSsnlbAADAqFGj0Lt3b7i7u+P8+fMYMGAAFi5cKHefpqamGD16NMqWLQsAePDgAWxsbFC6dGns2bMHBw4cgLGxMa5evYrjx48rnJGAiIiIiIiIqLjQi0EAS5riNLCQzwIfXYdAJVBx+g6UBPweE6mPv1NFh79R+qm4fQf4OSLSDJ23ACAiIiIiIiIi7WMCgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRATASBEHQdRBEREREREREpF1sAUBERERERERkAJgAICIiIiIiIjIATAAQERERERERGQAmAIiIiIiIiIgMABMARERERERERAaglK4DKIksN1jCZ4GPrsNQieUGS5XKFZf3Q/pB1c9VQfCzKKuwx5vHlAyRNn+n5DHk75kqx9qQj4+u5P5/KQ7HX93vbHF4T0S6wBYARERERERERAaACQAiIiIiIiIiA8AEABEREREREZEBYAKAiIiIiIiIyAAwAUBERERERERkAJgAICIiIiIiIjIATAAQERERERERGQAmAIiIiIiIiIgMABMARERERERERAagVFHuzNvbG7t27YKDgwNSU1NhaWmJoUOHYsyYMQgICMDQoUPzraN3797Yvn07Ll68iCVLluDEiRNo27atuN7Pzw9TpkyBIAjYv38/GjduDAAIDg7G6dOnce7cOURHR+PatWuoU6eOTP2qliMiIiIiIiIqToosAXDixAlMnToV48ePx5IlS1CxYkWcPXsWEydOxNOnT/Hnn3/i5MmTYvmnT59i2rRp+OeffzBgwABxedWqVQEA9+7dg4eHh3iDDwAPHjzAF198gZo1a+LSpUviutOnT2Pt2rUYPXo00tPT4efnh9q1a8vEqGo5IiIiIiIiouKmSBIAgYGBmDp1KmbMmIHNmzeLyz/66CP06dMH7969Q/ny5dGmTRtx3a1btwAAgwYNQsuWLWXqdHJyQrNmzVC+fHkAwP79+zFr1iwMHDgQx44dQ4UKFcSyOa0MAGDXrl1o27YtjIyMZOpUtRwRERERERFRcVMkYwBcuXIFqampGDlypMy6oUOHYvbs2TLLnZ2dUbp0aTRv3lxunc7OzrC2tkZWVhbmzZuHb775BvPmzcOFCxckN/8AUKZMGQBAYmIi/Pz8YGVlJbdOVcsRERERERERFTdFOgaAi4sLevXqJbPcxMREbtnWrVvLXefv74+oqCg0btwYQ4YMwd27d3HkyBF89dVXSvfv5uYGQRDyvbFXtRwRERERERFRcVEkCYBRo0Zhw4YNmDNnDmxsbPDZZ5/h448/Rrt27eSWz8rKgru7O8aNGyd3vZOTEwBg48aNsLCwwN27d9G5c+d843BzcwMAlRIAqpQDgLS0NKSlpcksj4+Pz3fb4qSkvR8qvvhZ1DweU8pPxYoVdR1CscfvmXI8PrpVEo9/SXxPRPlR5XxdJAmAKlWqwNXVFefOncOVK1ewZcsWLFiwANbW1jh58qRMM39vb2+kpKQovAHPSQBYW1vDw8ND5QsTV1dXGBkZSWYNKEw5AFi9ejVWrlwpWdZkfRNYWFioFJOuNVnfRKVyxeX9kH5Q9XNVEPwsyirs8eYxpfwIgqDrEIo9Q/6eqfIbZcjHR1dy/78Uh+Ov7rmuOLwnIk1T5XxdZF0AzM3NMX78eIwfPx7A+xH3x40bh7lz58LOzk5S1tnZGQDw4Ycfyq3L0dERtWvXhq2tLTp27Ihhw4bh6dOnqFSpktIY3NzcYGlpKQ4cWNhyALB48WLMnz9fssx6tzXi4uLy3VYfWO+2VqlccXk/pB9U/VwVBD+Lsgp7vHlMibTPkL9nqvxGGfLx0ZXc/y/F4fire64rDu+JSBeKdAyA3MaMGYOffvoJb968kVnn4uICQHECwMnJCe3atUOVKlVw/vx5dOvWDRMmTMDly5dhbKx4XENXV1e5YxAUtBzwPrFhbm4us7ykNZcsae+Hii9+FjWPx5RI+/g9U47HR7dK4vEvie+JSBO0PgvAkSNH5PaRj4yMREhIiNybfGdnZ9StWxdVq1aVWRcbG4t3797B2vp9FtDKygp79+6FnZ0dFi9erDCOiIgIRERE5NuvX9VyRERERERERMWJ1hMAmzdvRp8+fcRm/QDw6tUrDB8+HBUrVsSff/4ps42Li4tK/f9zjB8/HvPmzcO6detw8uRJudu5uroCUNyqQN1yRERERERERMWJ1hMAS5YsQfny5dGpUyc0aNAAtWrVgrW1NerUqYNnz56hRYsWkvIxMTEICAjINwGQdwaB9evXo1+/fpg6dSpevnwJ4P3onx9++CHatGmDSZMmAQB++ukntGnTBgsXLhS3VbUcERERERERUXGl9TEARowYgREjRiArKwuBgYHIzs5G/fr1UaqU/F2XLVsWrq6uqFOnjtz148aNQ//+/WFpaSlZbmJigosXL+Ldu3eoXLkyAMDMzAzHjx+XW0/16tXFf6tajoiIiIiIiKi4KrJBAE1MTNCwYcN8y5mbm6NNmzYK19eqVQu1atWSu658+fKSbUuXLq20LnXLERERERERERVXWu8CQERERERERES6xwQAERERERERkQFgAoCIiIiIiIjIADABQERERERERGQAmAAgIiIiIiIiMgBMABAREREREREZACYAiIiIiIiIiAwAEwBEREREREREBsBIEARB10EQERERERERkXaxBQARERERERGRAWACgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDEApXQdAxYPlBssi3Z/PAp8i3R+RIdPW95vfY9Jn6nzuNfFZLsj3rKR8hyw3WOb7XhQdn5JyDPRd7uOvr8c8J0ZV41P3O6ev75tI09gCgIiIiIiIiMgAMAFAREREREREZACYACAiIiIiIiIyAEwAEBERERERERkAJgCIiIiIiIiIDAATAEREREREREQGgAkAIiIiIiIiIgPABAARERERERGRASil6wBye/PmDezt7eWuMzIywsyZM2FiYiJZHhAQgKdPnyI6Oho1atRAkyZN8OGHH6pc76BBg9C0aVOZMmXLlsUHH3yA7t27F/JdEREREREREemeXrUAOHPmDObOnQsvLy+Zv5CQEMnNf2BgIEaMGIFmzZphz549cHBwwN69e9G5c2d88MEHuHDhgkr1mpqaimV++OEHcbm9vT369u2Lvn37Ij09vagPBREREREREZFG6VULACcnJzRp0gTbtm1TWs7X1xe9e/eGpaUl3r59izp16ojrQkNDMXHiRHh7e6tVr5OTE+rXry8pc/jwYUyaNAkHDx7EjBkzCvHOiIiIiIiIiHRLrxIAjo6OaN26tdIyGRkZGDp0KCpVqoTLly+jXLlykvW1atXCpUuXEBAQoFa9jo6OaNGihWRZ//79AQAeHh7qvA0iIiIiIiIivaM3XQCSkpLg4+OT74361q1b4e7ujm3btsnc/OcoXbo0mjVrpnK9OWVatmwpWR4WFgYAqF27tjpvhYiIiIiIiEjv6E0LAGdnZ2RnZ+Pdu3cyTfUtLCzw9ddfAwD27duHVq1aoVevXoWqt2zZspg6daqkTO4EgCAI2LBhA8qUKYOvvvqqsG+PiIiIiIiISKf0JgHg6OgIAChXrhy8vLwk63Ka5gcGBsLDwwNz5syRrM/IyMCuXbsky6ZOnYqyZcuK9ZYvX15Sb6NGjWT27eLigm3btiExMRH29vYICwuDvb096tatqzDutLQ0pKWlySwzNzdX5W2TAvHx8boOgYgKid/jkqNixYq6DkGndPVZLknfoYK+l5J0DIoLfT/m2opP3983kSpUOV/rTQLAyckJFhYW2L17t8Iy/v7+AABLS0vJ8oiICPHm3tbWFlFRUfjuu+/EeqtWrSqTIMi7bzMzMxgZGcHT0xNv377FvXv3MHv27HxbGqxevRorV65U6T0WZ03WNynS/VlYWBTp/ogMmba+3/welxyCIOg6BJ3SxGe5IN+zkvIdarK+Sb7vRdHxKSnHQN/lPv76esxzYlQ1PnW/c/r6vonUocr5Wq8SAHn74OeVkZEBAMjOzpYsr1Onjti8/86dO2jQoAGMjIzEeq2srPLdd+vWrSVdBJYsWYK//voLkydPRseOHRVuu3jxYsyfP1+yrCS2ALDebV2k+4uLiyvS/REZMm19v/k9ppJCE5/lgnzPSsp3yHq3db7vRdHxKSnHQN/lPv76esxzYlQ1PnW/c/r6vok0TS8SAFlZWXBzc8P48eOVlmvZsiWMjIwUjsqfmpqKV69e4YcffpDUO3PmzHz3/eWXX0qWz507F2vXrsXhw4eVJgDMzc1L3M2+PjD05qZEJQG/x1RS6OqzXJK+QwV9LyXpGBQX+n7MtRWfvr9vIk3Ri1kAPD09kZqailatWiktV6NGDYwYMQJHjx6VmwR48eIFMjMz0aFDB0m9yloA5JRp3769zL4++ugjXLhwQf03RERERERERKRn9KIFgJOTEwDAy8tLZgYAAPjyyy9RuXJlAMDu3bvx+eefo0OHDhg/fjxatGiBhIQEuLi4wM7ODs2bN0e3bt0k9SpLAOSUsbaWbSb02WefYdGiRXj58qVMgoCIiIiIiIioONGLBEDlypXFQfvyzgBgbGyM8uXLi6+rVq2KBw8e4MaNG3j48CFCQ0NRvXp1jBgxAuvXr0fz5s1l6lXWsiCnjLwkwZgxYxAQEMA+QURERERERFTs6UUCYMiQIRgyZIjK5Y2MjDBgwAAMGDCg0PUqK9O4cWO5LRKIiIiIiIiIihu9GAOAiIiIiIiIiLSLCQAiIiIiIiIiA8AEABEREREREZEBYAKAiIiIiIiIyAAwAUBERERERERkAJgAICIiIiIiIjIATAAQERERERERGQAmAIiIiIiIiIgMABMARERERERERAbASBAEQddBEBEREREREZF2sQUAERERERERkQFgAoCIiIiIiIjIADABQERERERERGQAmAAgIiIiIiIiMgBMABAREREREREZgFK6DoCKB8sNlvBZ4FOk+yuIooyRdKOoP4uGQFvHVJ3vMf9Pqajl/nzm9/mT91lW9zObtw5VttfEfou7ghw3Khx1vhu6UNTnLH08BkSFwRYARERERERERAaACQAiIiIiIiIiA8AEABEREREREZEBYAKAiIiIiIiIyAAwAUBERERERERkAJgAICIiIiIiIjIATAAQERERERERGQAmAIiIiIiIiIgMABMARERERERERAZArxIAe/bsgZGRkfhnbGyMGjVqYMSIEXj79q3SsmXLloW1tTVOnz6t0j66deumUgy5/27evKmx90pERERERERUlPQqAeDk5IQqVapAEAQIgoDU1FTY2Njg9u3b+Prrr2XKVqhQQSz77t07NGnSBOPHj8fjx4/l1p+YmIhly5ahdOnScHV1hSAI+caQ++/jjz/WyvsmIiIiIiIi0ja9SwC0adNGfG1mZoaePXuib9++cHZ2linbokUL8XWNGv/Xzh2ERNHGcRz/7aKrWBSFvIGoYBK4FYYdDDSIDkFEBl0E67LnpZZQihU7WLR4kDDB09u1CKqjEKmHDh4CiV1EkAiTNoiCCKq12KidDjHzNs72tu6uO7PO9wN7eXjmP8/zzLMs82N2/lEikVAul9P09HTe+mNjY/r8+bOGh4eVyWS0urqadwwHDhwo04wAAAAAAPAGzwQAhmFocXHRFgCY7SsrK7Z2s284HLb1ra+vlyR9//7dUf/169eamJhQLBbTsWPHJEmLi4t5x0AAAAAAAADYajwTALx48UKZTMa60TcMQ+l0WhcuXFA6ndatW7ccfdcHAHNzc5Kk48ePO+rH43GFQiFdvnzZOm59AGDW3b9/fzmnBgAAAACA62rcHoAplUpJkqLRqKLRqNXe1NSkx48f68iRI46+5l8AMpmMHj16pCtXrigSiejkyZO22gsLC7p3756uX7+uXbt2SZJ2797tCADMurFYTLFYzGrv6OjQ8vJy3nFns1lls1lHW11dXYEzrx6fPn1yewh/VQ1jROm4zuXn9pq6fX78vx07drg9hE1VzP4rdc8We7zfvyt+n3+leXW9Kzkur64BkE8hv9eeCQCSyaRqamq0tramUCikXC6nlZUVRSIR9fX16fnz59bNezKZlCSdPXvWVuPGjRsaGRlx1B4cHFRjY6MuXbpktXV0dDgCgGQyqdraWq2tram2tragcY+NjenatWsbmWpV2ju+Vzt37qzo+YpRyTHCHZXei36wWWu6ke8x19Tb8r00dyv52/7Lt5c3umfX1yjk+HKct9oVs24oze9r7sX1rvRvlhfXAPiTQn6vPRMApFIptbe3KxQKSZKCwaD27duneDyuM2fOaHZ2Vv39/VbflpYWpdNpGYahly9famBgQOPj44pGo1ZQIEkPHjzQ/Py8bt68qe3bt1vt4XBYT58+1ZcvX9TQ0GDVDYfDBd/8S9Lw8LAGBwdtbVvxCYCuf7v08ePHip6vGJUcI9xR6b3oB5u1phv5HnNN4aa/7b98e3mje3Z9jUKOL8d5q10x64bS/L7mXlzvSv9meXENgFJ4KgDo6elxtNfU/Brit2/fbH27u7slSYFAQO3t7ZqYmNDRo0d1584dXbx40TomHo9LkoaGhjQ0NOSov7S0ZNVKpVI6ceLEhsZdV1e35W72/6QaHgGthjGidFzn8nN7Td0+P/ytmP1X6p4t9ni/f1f8Pv9K8+p6V3JcXl0DoFieeAngu3fv9Pbt27wv35uZmVEwGFRvb6+t7+HDh239ent71dzcrIcPH1ptk5OTWl1d1bNnz2QYhu2zsLAg6b8XAZp1Ozs7N2uaAAAAAAC4xhNPAJj/6f89AHjz5o3u3r2rqakpXb16VW1tbba+XV3Ox3ROnz6t27dv6/3795KkRCKh8+fPO8IC6ddfAAKBgBUAmHUPHTpUxpkBAAAAAOANnngCwHz7/rlz5xQIBBQMBnXw4EHNzMzo/v37tpfsmX3z3dT39fXpx48fmp6e1ujoqLLZrBKJRN5zbtu2TS0tLVYAYNblCQAAAAAAwFbkiQAgHo/bHs/P5XL68OGDZmdnHW/6N/s2Nzc76pw6dUqGYSgSiWhqakpfv35Va2vrH8/76tUrPXnyxFZ3z549ZZ0bAAAAAABe4IkAAAAAAAAAbC4CAAAAAAAAfIAAAAAAAAAAHyAAAAAAAADABwgAAAAAAADwAQIAAAAAAAB8gAAAAAAAAAAfIAAAAAAAAMAHCAAAAAAAAPCBgGEYhtuDAAAAAAAAm4snAAAAAAAA8AECAAAAAAAAfIAAAAAAAAAAHyAAAAAAAADABwgAAAAAAADwAQIAAAAAAAB8gAAAAAAAAAAfIAAAAAAAAMAHCAAAAAAAAPCBnyGr8ZJ6iY+zAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "genes = pd.unique(pd.concat([top_hits[\"SFE_1\"], top_hits[\"SFE_2\"]])).tolist()\n", + "\n", + "fig = ss.oncoprint(\n", + " genes, result_obj[\"obj\"],\n", + " title=f\"TCGA LUAD oncoprint (n={missense.shape[1]})\",\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "64834cab", + "metadata": {}, + "source": [ + "#### Two-gene co-mutation detail (`oncoprint_pair`)\n", + "\n", + "For a single gene pair, `oncoprint_pair` zooms in further: it highlights the co-mutated samples directly and labels the co-mutation count, again comparing the observed pattern to one null-model simulation. We use the top hit from `top_hits`." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "facae961", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:53.263937Z", + "iopub.status.busy": "2026-07-14T19:10:53.263606Z", + "iopub.status.idle": "2026-07-14T19:10:53.596257Z", + "shell.execute_reply": "2026-07-14T19:10:53.595263Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA2UAAAHHCAYAAAA/PfCfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAApxFJREFUeJzs3XdcU9f7B/APQxQUFdx1UFfViruuqnVbR7VVqrZ1FrXOVmu1aq11b61W66yCuPdWXKgoOEBlKiAgMmTK3iO5vz/8kp8hCSSQcEE+79crrzb3nnvuk3iBPDnnPkdPEAQBREREREREJAp9sQMgIiIiIiIqy5iUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYgMxQ6AiIh0LzU1FQ8fPpTb9vnnn8PExESh7bNnzxAXFye3rX379jA3N1faTy59fX1UrFgRderUQf369aGnp6dRjA4ODhAEQW5bhw4dYGZmpnYfKSkp8Pf3R1JSEipWrIhq1aqhZs2aqFixokax6EJ+75062rVrh2rVqhXYT4UKFVC9enU0bdoUBgYGKts9fPgQqampBZ63fPny6NGjh8r92dnZiIyMRHh4OLKyslClShU0atQIlSpVKrBvIiL6H4GIiD54Xl5eAgC5h4+Pj0K7/fv3C3p6enLtJkyYIEgkEpX9KHuYm5sLP/30kxAeHq5WfPfv31faz6pVq9Q6/saNG0LPnj0FfX19hT709PSExo0bCyNHjhQcHBzUf9O0TN33TtXD3t5eo34qVqwojB07VggICFAaT7NmzdTqp1atWgrHSiQSYfv27cKXX34plC9fXul7/tlnnwm2tra6fEuJiD4YnL5IREQAgP3792Py5Mlyo1UTJ06EjY0N9PU1+3MRFxeHvXv3omPHjoiKiiqw/cGDB5VuP3ToUIHHbtu2DQMGDICjoyOkUqnCfkEQEBgYiFOnTuHevXsFB/+BSE1NxeHDh9GmTRvcvXtXq31nZWXh559/xvXr15GZmamwXxAEPHnyBD/++COmTZum1XMTEX2IOH2RiIjw33//YerUqXIJ2Y8//oh9+/YVmJB17doVJiYmSEpKwosXL+SmxL158wabNm3Cxo0bVR6fmZmJU6dOKd3n5+cHFxcXdOrU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16+f7zF592dmZiIpKSnfY/744w/4+fnhxo0bCAgIQKdOneT2b926FQCQkJCA5ORk2faff/4Zz549w4ULF3Dv3j28evUKERERsLGxQfv27eX6WLp0qdzzXr16ITAwENevX4e/vz9Gjhwp25eUlIT169fnGzMAfP311wgODoaDgwNOnz6NU6dOAQAmTpwoayORSGTbASA9PV2uMmWdOnUwcOBArcW5bds2uedffvklXr16hevXryMwMBDjx48v8HURERERERUGkzIdyMrKUthWrly5fI9Rtl9ZP7lq1KiBv/76S/a8atWqWLlypVwbJycnZGdno3LlyjAxMZFtv3r1Kvbs2QMnJyfZaFTNmjXx448/YvDgwbJ2YWFh8PDwkOtz586dskIihoaG2LBhg9z+s2fP5vs6K1asiP/++09pEZMJEybIjdi9PzJ26dIluSqW48ePh4GBgVbijI6OxvPnz+X2r1+/HsbGxgAAAwMDbNy4EYaGrItDRERERNrHT5k6YG5urrDt7du3+R6Td7+enh7MzMxUtm/ZsiXKly8vty3vKFdGRgaio6NRt25d/Pjjj9ixYwcAIDAwENOmTZOLt0ePHhg3bhysrKxk2wMDA+X6q1SpEp4/f66QwJQrVw7Z2dkAgKioKISHh6tcBNvS0hI1atRQuq9evXro37+/bEmAhw8fIjg4GBYWFgpTF3/88UetxZl3KqOhoSEsLS3lttWsWRN169YtcHomEREREZGmmJTpQMWKFVG/fn25e5CePn2a7zF59zdt2lQ2EqRM7ijO+94fDcuVO9q2ZcsWVK1aFbt370ZsbKxcm7i4OFy4cAEXLlzA1KlTZfef5SYwuVJSUuSmAary9u1blUlZQaX3ra2tZUmZIAg4fvw4pk2bBnt7e1mbzz//HM2aNZM9L2qceY83MjJS+t4re3+JiIiIiIqKSZmO9OvXD7a2trLn58+flyVGecXGxuLixYsKx+cnKCiowG16enqoXr06gHejRKtWrcLy5cvh4eGB58+f49WrV/D29sbFixdlyduePXuwcOFCfPzxxwrrepmYmGDQoEH5xgVA6fpi78eUn6+//hrm5uaIi4sD8G4KY82aNZGZmSlr8/4oGYAix5l35C4tLQ3R0dGoWbOmbJtEImGhDyIiIiLSDZGrP36w3NzcFMqqW1lZCdnZ2XLtsrKyhG+++UZhTbMXL17ItctbWh6AcPfuXbk2s2bNUig/n8vb21uQSqVKY8173NWrVwVBeLe2mbm5uVzp+idPnqh8zcnJyYKfn1++cVtZWRX43uWN55NPPpH9v4mJiZCUlCTXvqhxSqVSwczMTO6cq1atkjvmwIEDCu8/S+ITERERkTZwpExH2rZti+nTp2PXrl2ybWfOnEG7du0wfvx41KtXD6GhoThw4AB8fHzkjp0zZw5atGhR4DmGDh2KuXPnwsLCAnfu3MGhQ4fk9r9fMbBv374wMDDAwIED0bBhQ9SpUwfGxsbw9/fH4cOH5Y6rUqUKAEBfXx8zZszAqlWrALybTti7d2+MHTsW7dq1Q8WKFREVFYWIiAi4urriwYMHGDduHPbt26fZm5XHjz/+iH///Vf2/OXLl7L///bbbxWKhBQ1Tj09PYwZM0bunEuWLEFYWBg6d+6MFy9e4J9//inSayIiIiIiUknsrPBDlpWVJQwfPlytRZtzH6NHjxZycnIU+so74mRqappvP02bNhVSU1Nlx9eqVUut81tYWMiN5qWlpQmdO3dWO/5JkyblG7c6I2WCIAht2rRR2n/e0UFtxRkVFSXUqFFDZXs9PT2hYsWKHCkjIiIiIq1jSXwdKleuHM6cOYPt27erLHyRq169eti9ezeOHz+eb4GPXAMGDMDq1auVlmlv1qwZrl+/LleYom3btioXiM71ySef4OLFi3J9Ghsb49atW/jpp58KLAlvamoqV4CjKPLeNwYAjRs3xhdffKG0fVHjrFmzJq5fv660EImxsTH279+P5s2ba/AKiIiIiIjUoycIgiB2EGVBVlYW7t27B2dnZ7x58wbJycmoXLky6tati+7du6NHjx75rmXm7OyMLVu2yJ537doVv/32G3x9fXHixAkEBgaiQoUK6NatG7777juFcvkAEB8fDwcHB/j4+CAiIgJxcXEwNjZGgwYN0K1bN9kUR1XCw8Nx9epVeHp6IjY2FkZGRqhVqxZq166NTp06oVOnTgoJkaq4CxIbG4tp06bh/ctz2LBhai3iXJg4cyUnJ+PIkSNwcXGBRCJB8+bN8cMPP8DCwgILFiyQK7+/fPlytGzZssB4iIiIiIjyw6SMiIiIiIhIRJy+SEREREREJCImZURERERERCJiUkZERERERCQiJmVEREREREQiYlJGREREREQkIiZlRERERERE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atQIenp6Cm1ycnIQEBCAuLg41K5dGw0bNlTaTh2p3hPQ+eYDpEukhTq+LDA20Mfj/p8DAN8rIspX4LxAsUMgIiLSqRI5UmZiYpJvAY9GjRqhUaNGavdXv3591K9fP982hoaGaN68udp9EhERERERaQOXDCYiIiIiIhIRkzIiIiIiIiIRMSkjIiIiIiISEZMyIiIiIiIiEZXIQh8fAmMDLsicH74/RERERETvMCnTkbt9OosdAhERERERlQIlcp0yIiIiIiKisoL3lBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZERERERGRiJiUERERERERiYhJGRERERERkYiYlBEREREREYmISRkREREREZGIDMUOoKyIjo6Gn58fkpKSULFiRVSrVg21atVCzZo1VR7z5s0buLq6yp7XrVsXHTt2LI5wtaIkxV+SYikqiUSCy5cvQxAEAEDnzp1Rp04dkaOi4vQhXc/v8/T0xKtXr1C+fHkMGjQIAJCeno7r168DADp06ID69evn20dWVha8vLwQExODihUrokmTJvn+fISGhuLp06cAAAMDAwwdOlRLr4aIiEh9TMp0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34kTJ7BixQpcvXoVz58/R2JiIkxMTNCwYUP07dtX5RRTdfrv3r27XFlxTdYaA9598JwzZw5SU1MBAKdOncKXX36p1rF//fUXrKyscPz4cQQGBqJChQro1q0bxowZIyu48D5TU1Ps378fy5Ytw5UrV+Dp6YnY2FgYGRmhVq1aqF27Njp16oQuXbooVIJU573IVdKvYUD9a4zXs2bXM/Auaf76669RpUoV2b9ddHQ0evXqBeDd71F1ffvtt/jyyy9x6dIlPHjwADExMTAxMUHTpk3x9ddfFzgN8uTJk7L/r1GjBoYNG6bx6yEiIiosPUF47+50IirRJk+eLCu7bmZmhqioKJQrV06uzfHjx+VKiFtZWeH06dPFGidRaZKSkoIaNWrISuDPmzcPGzduFDkqIiIqSzh9kagU+f3332XrQ8XHx+PEiRMiR0RU+tnZ2ckSMmNjY8yZM0fcgIiIqMxhUkZUinzyySeYNGmS7Pn69evBwW6iwpNIJNi8ebPs+ezZswt9rywREVFh8Z4yolJm2bJliIqKkiVjfn5+ohVqICrtPD090bp1a7Ru3RoGBgZYuHCh2CEREVEZxHvKiD4wTk5OcutQff755/j9999FjIiIiIiI8sOkjIiIiIiISES8p4yIiIiIiEhETMq0LC0tDc+ePUNaWprYoRARERERUSnAQh9a5uvriw4dOuDy5cto1aqV2OFQGWRoaIhatWrBwMBA7FCIiIiISA1MynREX19fYVFfIl2TSCTIycmBVCplUkZERERUSjAp0xF9fX1+KCZRSKVSsUMgIiIiIg3wnjIiIiIiIiIRMSmjQjl16hQ2b94sdhhERERERKUekzIqlJs3b+Lw4cNih0FEREREVOoxKdORoKAgDBs2DI8fPxY7FCIiIiIiKsGYlOlIamoqHj9+jLi4OLFDISIiIiKiEoxJGRERERERkYiYlBEREREREYmISRkREREREZGImJQRERERERGJiEkZlWipqakICwuDRCIROxQiIiIiIp1gUkYl2qlTp9ChQweEhoaKHQoAICIighU1iYiIiEirmJSRzK5du9CgQQPExsaKHUqJNWDAAPz5559ih0FEREREHxAmZSSTk5ODzMxMCIIgdihERERERGUGkzKiQlqwYAHWrFkjdhi4evUqnJycxA6DiIiIiAqJSRlRIbm4uMDd3b1YziWVSnHl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77//1nacRKWGn58fHj16VKQ+zp8/j507d2opIiIiIiIqyYpcEr9FixbYuHEjgoODMW3aNERGRqq1BheRKjY2NujevTvS0tLUah8ZGYnx48cXuEYcALx8+RKbN29GVFRUUcPUuoyMDJWLlBMRERHRh6vISVlAQAD+/PNPNGnSBLt370aNGjXw9ddfayM2KqNiY2Ph7+8PQRDUap+amorr168jJCSkwLa+vr7YsGEDIiIiihqm1n3//fcYNmyY2GEQERERUTEr1M1fqampOHXqFGxtbXHv3j0YGhpi4MCB2LZtG7766iuUK1dO23ESlTmZmZm4efMmQkNDxQ6FiIiIiHRIo6TM2dkZtra2OHnyJJKTk9G8eXOsX78e48ePR+3atXUVI1GZFBsbi7Fjx4odBhERERHpmNpJmbOzM7p3747KlStj9OjRsLa2RteuXXUZW5mzePFiNGrUCJMmTRI7FCpGWVlZSEhI4PptRERERGWU2veU5d7fk5KSAltbW/To0QOGhoYFPnr16qWr2EudVatWoXv37ir329vbF7lqX2l1//597Nu3L982bm5umD59OoKCgoopquJx7949tGrVCq6urggKCsK4cePg4uIidlhEREREVEzUHikzMTEp1ELKdevW1fiYD1ViYiISExPFDqNEunz5Mk6cOIHJkyerbPPmzRucPXv2gx5JTEpKwo0bN8QOg4iIiIiKkdpJWfv27eHr66vLWKiQ/v77bzx9+lTsMEhNV65cQe3atdGhQwexQ6EPgIuLCzIyMvDFF1+IHQoREREVUpFL4lPRNGjQAH/88Ue+bY4dO4YdO3ao3L9p0ybcuXNH26GRjsybNw8HDx4UOwz6QCxduhQzZsxQu72npyeWLVtWItfqIyISg4uLC9fYJbW8evUK3t7eOulb7aTs4cOHqFSpEgYMGKCTQMqygtbjunLlCo4fP15M0RDR+2JiYhAfHy92GFrj4eGB5cuXMykjIvqf3bt3Y8qUKWKHQaXA77//jqFDh+qkb7WTMolEgtTUVKSlpekkEPow+Pn54cyZM6wkWEZdvXoVn3zyCZ4/fy52KFrTrVs3TJw4UewwiIiIqAi+//571KtXT+wwVOL0RdJIYGAghg8fDicnJ6X7r127hhkzZjB5L6OSkpLg7++PjIwMsUMhIiIiKjU0WjyaShd3d3eUK1cOLVu21Oi4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3r3w8/NDfHw8bt++jezsbGzduhXjx4+Hr68vVq5c2XI3TERERERE9AjZk7LMzEw4ODhII2W1IiIiEB0djQ0bNmDfvn319gWA0NBQqNVqHDp0CHFxcbCzs8N7771n9pr29vbw8fGRNpA+cOAAIiMjkZCQAH9/fwwfPhzx8fGYO3cuTp06hQ4dOrTwXRMREREREdWwiEIf7U1KSgoLfdAv7tFCH4GBgVxTRkRERC2u9+recofQ5jSm0IfsI2VERERERERPMyZlREREREREMmJSRkREREREJCMmZURERERERDJiUkZERERERCQjG7kDaK9UViq5Q6CnDN9zRERERG0Tk7JWss5vndwhEBERERFRG8B9yoiIiIiIiGTENWVEREREREQyYlJGREREREQkIyZlREREREREMmJSRkREREREJCMmZURERERERDJiUkZERERERCQj7lPWClJSUuQOgZ5ykd9HosJQIXcYRC0i6+0suUMgIiJqVUzKWsnstNnQG/Ryh0FPEZWVipuWExEREbVBTMpaid6g50gFERERERE1iGvKiIiIiIiIZMSkjIiIiIiISEZMyoiIiIiIiGTEpIyIiIiIiEhGFlXo4/bt25gxY0aD/QICArBy5Up8++23WLVqldRuZ2eH3r17480330SfPn2kdr1ej4kTJ0IIYXKu9evXw9vbW3r+ww8/YMeOHcjMzIRarUZgYCDCw8Nha2v7hHf3dBIGgeKjxUZtdl52UHupTfqW55RDn62HEAJ23e1g19PO7Dkr8ipQklECAFB1VcG+v32Lx01ERERE9EuxqKRMpVJhzpw50vP09HTMnz8fCxYswKhRo6T27t27AwCOHDmCpKQk7N27FwBQWFiIDz/8EJs2bYJWq0Xfvn0BABcuXMDXX3+NTZs2oUePHkbX7Nmzp/T12rVrcejQIYwbNw5Tp05FRkYG3nrrLezbtw/79+9vpbtu5wQgqmqS4YenHqJaVw3XEFejpEwIgYJdBdCd0UHtrYbCRoHC/YVwGOwA95fdobBSSH2rHlThzpY7qCquAgA4jXBiUkZEREREbZpFJWUdO3bEuHHjpOc3btwAALz00kvw9fU16Z+WloZ+/foZHdO/f3/4+/tj27ZtWL58udTP2toa4eHhsLMzP/oCANOmTcPcuXOl55MmTcL9+/exZs0a5Ofnw93d/Ynv8WmjsFag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", + "text/plain": [ + "
" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gene1, gene2 = top_hits.iloc[0][[\"SFE_1\", \"SFE_2\"]]\n", + "\n", + "fig = ss.oncoprint_pair(gene1, gene2, result_obj[\"obj\"])\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "d0cb19a5", + "metadata": {}, + "source": [ + "#### Comparing null-model backgrounds across two runs (`ridge_plot_ed_compare`)\n", + "\n", + "`ridge_plot_ed_compare` overlays the null-model background distribution from *two* `selectX()` runs on the same ridge plot, for a shared set of gene pairs -- the typical use case is comparing two cohorts (e.g. primary vs. metastatic samples, as in the accompanying manuscript) or two timepoints. The bundled example data is a single cohort, so here we instead re-run `selectX()` on the same data with a different seed purely to illustrate the plot's API; the observed weighted overlap (solid lines) is therefore identical between the two runs; only the null-model background (the shaded ridges and dashed mean-background lines) differs, since that's resampled independently per run." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f710be3d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:53.600242Z", + "iopub.status.busy": "2026-07-14T19:10:53.599959Z", + "iopub.status.idle": "2026-07-14T19:10:55.886460Z", + "shell.execute_reply": "2026-07-14T19:10:55.883097Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAwkAAALXCAYAAADG2AxnAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAA6vFJREFUeJzs3Xd8VFX6x/HPvVPSQwsdpPdeVRRRVARRBEFREexi111dV3d1bau4tt+uu+raRRCliKIioiKKgIL03msC6b1Nvb8/shkZEiAJSSaB7/v1yq5z59xznntIuc/cUwzLsixERERERET+xwx1ACIiIiIiUrMoSRARERERkSBKEkREREREJIiSBBERERERCaIkQUREREREgihJEBERERGRIEoSREREREQkiD3UAUhopaamsnDhQlq3bk1ERESowxERERGRKlRQUMC+ffu45JJLiIuLO2Y5JQmnuYULF3L99deHOgwRERERqUbTp09nwoQJx3xfScJprnXr1kDRN0qXLl2qpU2fz0d+fj6RkZHYbLZqafNUob6rOPVdxanvKkb9VnHqu4pT31Xc6dJ3W7du5frrrw/cAx6LkoTTXPEQoy5dutC3b99qadPn85Gbm0t0dPQp/UNYFdR3Fae+qzj1XcWo3ypOfVdx6ruKO9367kTDzDVxWUREREREgihJEBERERGRIEoSREREREQkiJIEEREREREJoiRBRERERESCKEkQEREREZEgShJERERERCSIkgQREREREQmiJEFERERERIIoSRARERERkSBKEkREREREJIiSBBERERE5ZRQWFnLZZZcxc+bMUIdSq9lDHYCIiIhIWTz00ENs27YNAMMwiI6OpmHDhvTq1YvLL7+cRo0aVbjuW265hU6dOvHwww9XVrgnpaLxbNy4kUceeYQ33niDNm3alFrmoYceIjU1lQ8++KASIq15vF4v8+fP56yzzgp1KLWakgQRERGpFZYuXcquXbsCN7f5+fns27ePqVOncuedd3L//fczZcoU7Pby394sWrSIvLy8So644ioaz+zZs1m3bh0tW7Y8ZpmlS5cSHx9/MuHJaUBJgoiIiNQa4eHhXHbZZUHHHn74Yd566y0mT55MXl4er7/+eoiiC70vv/ySESNGYBhGqEORWk5JgoiIiNR6t99+O9988w1vvvkmDzzwAB07dgTg0UcfZePG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M7jiiiu47LLLgvrk4osvZseOHWzYsCGojnnz5pUpfofDwdChQ5kzZ05Qu/v372fZsmUMHz48cKxjx44cOHCAlJTfF1VZuXJlhVeHuvjii/n000+DVjwqS33liRmKnl75/X6mTZvG7NmzGTt2bGB5U4fDwahRo5g5c2aJfzcI/l4rK8MwsNlsJZ76vfvuu6WWN02TmTNnBh375JNP6NKlS6lL0VYmJQkiIiJSY7z66qusX7+ejh07HrPMmDFjmDRpEmPHjuXll19m+fLlfPfddzz33HN06dIlUO66665jz549/OEPf+DXX3/lrbfe4pVXXikx9r4yHLm60bJly3j44Yf56quvePTRRwN7EpU1njFjxjB+/HgmTJjAyy+/zC+//MLs2bM5//zz2b9/f4m2bTYb06ZNY8yYMYwaNYqffvoJgKuvvpozzzyTMWPGMHfuXH788Ueuu+66wBj3smyO+cILL5Cens6oUaNYtGgRX3zxBcOHD6dNmzb86U+/zz29/vrriYqK4sYbb2Tp0qV8/PHHTJkypcR4+rJ6+umn2bt3L1dffTU//vgjs2fP5vHHHy8xb+RkYgZo0qQJF110EY8//jhpaWklhrC98sorWJbFkCFDmD17Nr/99huffvopt956a9DKVeVx3XXX8dprrzF9+nSWLl3KXXfdRVxcXKllGzZsyOLFi3nzzTdZtmwZf/jDH5g3bx4vvPBClW9uquFGIiIiEhJ2u51u3bod8wapWNeuXWnevHnQsalTpzJz5kxmzpzJjBkzaNiwIQMGDAgaO37eeefx5Zdf8vrrr3PfffcxcOBA5s6dy6OPPhq0MVlERATdunUrMawqNjaWbt26BZU91jUsWbKEJUuWAEVPRNq2bcv333/PhRdeWO54DMNgxowZTJ06lTlz5vDxxx/TsWNHpkyZEtis7OiYbTYbH374IVFRUTz11FO0a9eObt268c033/D3v/+dKVOm0KBBA/70pz+xfv16DMMgPDz8uNcPReP2V61axUsvvcQjjzyCw+Fg9OjRPPTQQ0HDfurUqcOiRYt45plneOCBBxg4cCDvvvsuDz74YNAmd02bNqVz584l2mnZsmXQ2Pvu3bvz888/M2XKFB566CF69OjB+++/zxNPPHHcf4/yxFxs8uTJxMfH07x5c3r37h30XvPmzVm7di3/+c9/eOONN8jJyaF1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r78elliHgjlz5oiMjAzh8Xj8277//e+LlJSUgL8pf/rTn4Qsy+L48eORCDMi+CSBwmLNmjXQNA233Xabf1tiYiKuv/56vPnmmzH7qcbpkpOTERcXd8Zyr7/+OiZNmhTQzWPu3LmYP38+/vnPfw5miFHJaDTivffeQ1JSUsD2ZcuWweFw4Pjx4/5tr7/+Oq6++uqAT41uvfVWKIqCN954I2wxR5NT2+KkKVOmwOv1wuFwAABqamqwZcsW3HbbbZAkyV/uzjvvRGNjIz7++OOwxRttcnNz8cwzz+C5557rdurETz/9FPX19bj99tv92yRJwh133IFt27ahoqIinOEOKW+++SYkScItt9zi35aSkoKVK1fi9ddfj2BkkfOnP/0JqqrigQce6LIvISHB//3rr7+OWbNmYcGCBf5t55xzDmbMmBGTfye6k5OTgyNHjuCb3/xmQBe3119/HStXrgz4m3LyvS+W/k4wSaCw2L9/P0aPHt3lZiQ7OxvNzc0oLS2NTGBD1P79+zF37twu27Ozs3HgwIEIRBRZsix3e3N29OhRAEBqaioAoK6uDlVVVV3aLj4+HpMnT47JtutOS0sL3nrrLVx00UXIzMwEABw4cABCiC5tl52d7d8fizRNw913341rr70Wl156abdl9u/fDwA9tt3BgwcHN8go9tvf/hZXXnklbrnlFrzwwgtwu90B+/fv34+JEyfCarUGbM/OzkZtbS2qq6vDGW5U+Pjjj3Heeedh69atuPPOO3HVVVfh4YcfRlFRUUA5/p04s1dffRVA54cdJ1VVVaGurq5L2yUkJGDChAkx1XZMEigsamtrkZGR0WX7yW21tbXhDmlI6609W1pa4PF4IhBVdCkrK8Pzzz+PRYsWYeLEiQC++j3rqe1i+fcwJycHK1aswOLFizFu3DjMnTsX69ev9+/vqe3i4+MRFxcXs233u9/9Dnl5efjd737XY5na2loYjUYkJiYGbI/19785c+bgyiuvxL333osZM2bgkUcewZIlS9DR0eEvw78dXRUVFWHv3r248cYbsWDBAtx55504ePAg5s+fj3379gHoHLdWV1fXY9vV1dWFO+yoo+s63njjDWRnZ2PevHn+7fw78ZXYHN5OYef1ersMRgPgn2Ghu8G51LO+tGesznIEAE6nEzfddBM0TcMLL7zg337y96yntovl38OxY8fi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7Kuurg6I8eTXzTffLO644w7/66uuuqrHOu+44w7xrW99q0sd06ZNEzabTQghREdHR48xn/x64403gr7GYK+jNzabTaxatarHOEaOHCk2bdrkL//DH/7Qv2/ZsmUBdbndbpGamurfv3bt2pD8PG+//XZx3XXXBRzj9Xp7/Zk/+OCDvf4cLrvsMuF0Onv9ed98881djps+fXqXWIkoOjFJICKKsPvuu89/EzVz5kwhhBAffvihf9u+ffvEL37xCwFAKIoiOjo6RG1tbcDN1zvvvOOv79Sbs4yMDPHMM8+Il156SVx44YX+7fPmzRM+n89/TG83jJdeeql/X1xcnPjpT38q/vKXv4js7GwRHx/fpyQhPj5ezJo1S/z5z38Wjz76qD8BAiB+9atfCSGE8Pl8Yu3ateL+++/370tISBBr1671f1VUVAR9jadeR3x8vPjZz34mnn/+ebFw4cJer6M3pyYIcXFx4qmnnhIvvviiWLp0qX+7xWIReXl5QgghioqKhCRJAoCQJEkUFxf761q7dm1AcuHxeELy87RYLOKss84Sv/zlL8WaNWvE2rVrha7rvf7MDx48GNDur732mvjpT38q0tLS/Mc8/vjjPf68LRaLmDdvnnj++efFz3/+c2G1Wv37li9f3uf2JaLIYZJARBRhr776qv8GSpIk0djYKP7f//t//ptZr9crtm3b5i/zySefBNxQAhANDQ1CCCFyc3MDtm/bts1/HrvdLjIyMvz73nrrLf++nm4Yjx07FlDfX/7yF/++xsbGPicJFotF1NXV+fedfDICQNx0000Bx7344ov+fWlpaV3aK5hrPP06/va3v/mPaW1tFQkJCf1OEo4cOdJjnW63W4waNcq/b/Xq1f59l112mX/7U0895d9+9dVX+7c/8cQTQV/r6W1vNptFaWlpl/h7SxJ68te//tV/zOTJk3usz2q1iubmZv++V155JeA6jhw50qfzEVHkcEwCEVGEnTp4WQiBnTt3+scbnHfeeVBVFYsWLYLJZILb7caOHTvQ2trqP2b69OlIT08HEDhDkiRJePbZZ/Hss8/6t2ma5v9+69atuOmmm3qNbc+ePQGvr7vuOv/3aWlpWLp0KTZu3HjGa7zggguQmZnpf33WWWf5v6+vrz/j8acK5hpPv45TxwIkJSVhyZIl+Oijj/oVx+mzT1177bX+741GIy6//HL87W9/61L2gQce8LfZK6+8gqeeegr19fX+8yuKgnvvvTfoaz3dBRdc4B/n0h+ff/45Nm3ahNLSUthsNmiahurqav/+kpISCCG6HfuxdOlSpKSk+F+fPvZiz549mD17dr9jIqLwYZJARBRh48ePx+jRo1FZWQkA2Lx5M/bu3QvgqwTCZDLh7LPP9icQpyYJpw7kPbnwGtCZcJycSrQ7p06f2hObzRbwOjk5udfXPRkxYkTA61NnbBJfDtruq2Cu8UzXkZSU1K8YTo+juzpOPcepZa+++mqMGTMGFRUVKC0txZYtW3DgwAH4fD4AnTfUY8aM6XJcsD/Pk1Pm9pXP58PXv/51rF+/vtdymqbB4/HAZDJ12Xem9j3950FE0YdJAhFRFFi6dCnefPNNAMDf//53OJ1O//ZTy+zcuRO7du2Cx+MJ2H7S6NGj/d8rioJXX3014Ib8VKeW7cmpn/gDQEVFBSZPnux/XVJScsY6AHT7aXOwgrnG02+Uy8rKMHXq1IDXA4kDAEpLSwPa5tRZmk6P+Z577sGTTz4JAHj55Zf9q2YDnU8aejoumJ9nf9v+7bffDkgQ7r33XlxwwQUwm834/PPP8bvf/c6/r6cE7/T2PP11fxMXIgo/rpNARBQFTr3RP/npsaIoOO+88/zbL7jgAgCAw+Hwf+p8+rGXXXYZ4uPjAXR+0ltSUoKvf/3rWLVqlf9r0aJFKC8v71OSsGzZsoApK3//+9/7v9+8eXOXbjyhcOpUrjabLSAhAoK7xmXLlgXU+5vf/Mb//Y4dO/DFF1/0O85LL70UFovF//rXv/61/6b56NGjeP/99/37rr/++oBjv/3tb0NVOz+ne+ONN3DkyBEAwMSJE3HZZZcN6FoHqqqqyv99YmIinnvuOdx222247rrr+pxM7d69O6CL1W9/+1v/9yaTCRdddFFIYiWiwcMkgYgoCpx6o39SdnY2rFar//XJNRVONWLEiIBPr9PS0gI+6X388ccxefJkXH311Vi2bBkmTJiAcePG4Qc/+EFAl6WepKam4oc//KH/9R//+EfMnTsXl1xyCVauXImZM2f25zL7ZO7cuf7v3W43Fi5c6L8xdjgcQV1jWloaHnnkEf8xL774IubMmYPly5fjiiuuwIwZM/odZ3p6ekCy8eKLL2LWrFn42te+hnPPPRdut9t/Pd///vcDjs3KyvKPYfB6vf7t9957b8DPONQ/z75YunSpP4b29nbMmjUL11xzDSZNmoSCgoI+1TFjxgxcfvnluOSSSzB37lz8+c9/9u/7/ve/j4yMjJDESkSDKHJjpomI6CRN00RycnLADDDf+973upSbM2dOQJkbbrih2/ree+89MXfu3ICyJ78mTZokHnnkEdHa2uov39tMN7qui8cee0yoquovk5SUJNasWSO+/vWv+7etWrUq4Lje6jx12tfT1wsQQoiHHnrIP1XoqV8tLS1BX6OmaeKHP/xht9dx55139nt2o5PWrl0rpk6d2iUGk8kk7rnnnoCYT/Xpp58GlDcajaK+vr7bsqH8efalzHPPPRcwbakkSeK+++7rMqvWqWslnF7fmjVrRFJSkn+boijiBz/4gdA0rV/tS0SRIQnRzxFjREQ0KDZv3ozm5mb/6wULFmDixIkBZb744gtUVFT4X8+YMQOzZs3qsc7y8nKcOHECDocDmZmZGDNmDEaNGtWl3M6dO1FTUwOgcyD16SsuA50DY/ft2wej0YjzzjsP8fHxmD59OvLz8wEAjz76KH7961/3qc6DBw+iqKgIAJCRkYFly5Z1OV9lZSWOHDkCh8Ph78Zz7bXXdumT39drPKm+vh779+8PuI59+/b5xxBkZWUFDAbvq4KCAhQVFcHtdiMjIwPz5s0L6I50OiEE3n77bei6DqDzqcHFF1/c6zlC+fM8U5nW1lYcOXIENpsNs2fPxpgxY1BdXY3PP//cX+aGG26ALHd2Srj66qvxwQcfAADuvPNOvPzyy7Db7di9ezc8Hg8WLlwYMMMVEUU3JglERNSrqqoqfPrpp7jllltgNBoBdN7g/va3v8Wjjz7qL7dr166AMRQUW7pLEoho6OLsRkRE1KuWlhasXr0a//Ef/4EZM2YgOTkZ+fn5ATMbPfTQQ0wQiIiGESYJRETUq9GjR+Pb3/423njjDezevTtg34QJE/DDH/4Q9913X4SiIyKiwcDuRkRE1CcOhwP5+fmorKyEyWTCpEmTMGnSpEiHRVGiL+MgiGjoYJJAREREREQBuE4CEREREREFYJJAREREREQBmCQQEREREVEAJglERERERBSASQIREREREQVgkkBERERERAGYJBARERERUQAmCUREREREFIBJAhERERERBfj/meKnA+eX2RwAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "result_obj_2 = ss.selectX(\n", + " M=M,\n", + " sample_class=sample_class,\n", + " alteration_class=alteration_class,\n", + " n_cores=1,\n", + " min_freq=10,\n", + " n_permut=1000,\n", + " lambda_var=0.3,\n", + " tao=1,\n", + " save_object=False,\n", + " verbose=False,\n", + " estimate_pairwise=False,\n", + " max_fdr=0.25,\n", + " seed=99,\n", + ")\n", + "\n", + "compare_df = top_hits[[\"SFE_1\", \"SFE_2\", \"type\", \"w_overlap\"]].merge(\n", + " result_obj_2[\"result\"][[\"SFE_1\", \"SFE_2\", \"w_overlap\"]],\n", + " on=[\"SFE_1\", \"SFE_2\"], suffixes=(\"1\", \"2\"),\n", + ")\n", + "compare_df = compare_df.rename(\n", + " columns={\"w_overlap1\": \"dataset1_w_overlap\", \"w_overlap2\": \"dataset2_w_overlap\"}\n", + ")\n", + "\n", + "fig = ss.ridge_plot_ed_compare(\n", + " compare_df, result_obj[\"obj\"], result_obj_2[\"obj\"], \"Run A (seed=42)\", \"Run B (seed=99)\"\n", + ")\n", + "fig" + ] + }, + { + "cell_type": "markdown", + "id": "5c55c60b", + "metadata": {}, + "source": [ + "### Session info" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "122cfeea", + "metadata": { + "execution": { + "iopub.execute_input": "2026-07-14T19:10:55.890574Z", + "iopub.status.busy": "2026-07-14T19:10:55.890147Z", + "iopub.status.idle": "2026-07-14T19:10:56.057809Z", + "shell.execute_reply": "2026-07-14T19:10:56.056266Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "python: 3.12.13 | packaged by conda-forge | (main, Mar 5 2026, 16:50:00) [GCC 14.3.0]\n", + "selectsim: 0.2.0\n", + "numpy: 2.1.3\n", + "pandas: 2.2.3\n", + "scipy: 1.14.1\n", + "matplotlib: 3.11.0\n", + "seaborn: 0.13.2\n" + ] + } + ], + "source": [ + "import sys\n", + "import numpy, pandas, scipy, matplotlib, seaborn\n", + "import selectsim\n", + "\n", + "print(\"python:\", sys.version)\n", + "print(\"selectsim:\", selectsim.__version__)\n", + "print(\"numpy:\", numpy.__version__)\n", + "print(\"pandas:\", pandas.__version__)\n", + "print(\"scipy:\", scipy.__version__)\n", + "print(\"matplotlib:\", matplotlib.__version__)\n", + "print(\"seaborn:\", seaborn.__version__)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index ac92efc..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1098 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "1.0.0" -description = "A light, configurable Sphinx theme" -optional = false -python-versions = ">=3.10" -files = [ - {file = "alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b"}, - {file = "alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e"}, -] - -[[package]] -name = "asttokens" -version = "2.4.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = "*" -files = [ - {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, - {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, -] - -[package.dependencies] -six = ">=1.12.0" - -[package.extras] -astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] -test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] - -[[package]] -name = "babel" -version = "2.16.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -files = [ - {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, - {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, -] - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "certifi" -version = "2024.8.30" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, - {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.0" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, - {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, - {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, - {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, - {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, - {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, - {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, - {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, - {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, - {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "decorator" -version = "5.1.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.5" -files = [ - {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, - {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, -] - -[[package]] -name = "docutils" -version = "0.21.2" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.9" -files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "executing" -version = "2.1.0" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.8" -files = [ - {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, - {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, -] - -[package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "ipython" -version = "8.30.0" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.10" -files = [ - {file = "ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321"}, - {file = "ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} -prompt_toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack_data = "*" -traitlets = ">=5.13.0" -typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} - -[package.extras] -all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] -kernel = ["ipykernel"] -matplotlib = ["matplotlib"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] - -[[package]] -name = "jedi" -version = "0.19.2" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -files = [ - {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, - {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, -] - -[package.dependencies] -parso = ">=0.8.4,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "3.0.2" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, -] - -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.8" -files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, -] - -[package.dependencies] -traitlets = "*" - -[[package]] -name = "numpy" -version = "2.1.3" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"}, - {file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"}, - {file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"}, - {file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"}, - {file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"}, - {file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"}, - {file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"}, - {file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"}, - {file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"}, - {file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"}, - {file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"}, - {file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"}, -] - -[[package]] -name = "packaging" -version = "24.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "pandas" -version = "2.2.3" -description = "Powerful data structures for data analysis, time series, and statistics" -optional = false -python-versions = ">=3.9" -files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, -] - -[package.dependencies] -numpy = [ - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, -] -python-dateutil = ">=2.8.2" -pytz = ">=2020.1" -tzdata = ">=2022.7" - -[package.extras] -all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] -aws = ["s3fs (>=2022.11.0)"] -clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] -compression = ["zstandard (>=0.19.0)"] -computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] -consortium-standard = ["dataframe-api-compat (>=0.1.7)"] -excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] -feather = ["pyarrow (>=10.0.1)"] -fss = ["fsspec (>=2022.11.0)"] -gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] -hdf5 = ["tables (>=3.8.0)"] -html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] -mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] -output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] -parquet = ["pyarrow (>=10.0.1)"] -performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] -plot = ["matplotlib (>=3.6.3)"] -postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] -pyarrow = ["pyarrow (>=10.0.1)"] -spss = ["pyreadstat (>=1.2.0)"] -sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] -test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] -xml = ["lxml (>=4.9.2)"] - -[[package]] -name = "parso" -version = "0.8.4" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, -] - -[package.extras] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "pockets" -version = "0.9.1" -description = "A collection of helpful Python tools!" -optional = false -python-versions = "*" -files = [ - {file = "pockets-0.9.1-py2.py3-none-any.whl", hash = "sha256:68597934193c08a08eb2bf6a1d85593f627c22f9b065cc727a4f03f669d96d86"}, - {file = "pockets-0.9.1.tar.gz", hash = "sha256:9320f1a3c6f7a9133fe3b571f283bcf3353cd70249025ae8d618e40e9f7e92b3"}, -] - -[package.dependencies] -six = ">=1.5.2" - -[[package]] -name = "prompt-toolkit" -version = "3.0.48" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, - {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, -] - -[package.dependencies] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pygments" -version = "2.18.0" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, - {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pytz" -version = "2024.2" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, - {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "scipy" -version = "1.14.1" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.10" -files = [ - {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, - {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, - {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, - {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, - {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, - {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, - {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, - {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, - {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, - {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, - {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, - {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, - {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, - {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, - {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, - {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, - {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, - {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.3" - -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "sphinx" -version = "8.1.3" -description = "Python documentation generator" -optional = false -python-versions = ">=3.10" -files = [ - {file = "sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2"}, - {file = "sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927"}, -] - -[package.dependencies] -alabaster = ">=0.7.14" -babel = ">=2.13" -colorama = {version = ">=0.4.6", markers = "sys_platform == \"win32\""} -docutils = ">=0.20,<0.22" -imagesize = ">=1.3" -Jinja2 = ">=3.1" -packaging = ">=23.0" -Pygments = ">=2.17" -requests = ">=2.30.0" -snowballstemmer = ">=2.2" -sphinxcontrib-applehelp = ">=1.0.7" -sphinxcontrib-devhelp = ">=1.0.6" -sphinxcontrib-htmlhelp = ">=2.0.6" -sphinxcontrib-jsmath = ">=1.0.1" -sphinxcontrib-qthelp = ">=1.0.6" -sphinxcontrib-serializinghtml = ">=1.1.9" -tomli = {version = ">=2", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["flake8 (>=6.0)", "mypy (==1.11.1)", "pyright (==1.1.384)", "pytest (>=6.0)", "ruff (==0.6.9)", "sphinx-lint (>=0.9)", "tomli (>=2)", "types-Pillow (==10.2.0.20240822)", "types-Pygments (==2.18.0.20240506)", "types-colorama (==0.4.15.20240311)", "types-defusedxml (==0.7.0.20240218)", "types-docutils (==0.21.0.20241005)", "types-requests (==2.32.0.20240914)", "types-urllib3 (==1.26.25.14)"] -test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=8.0)", "setuptools (>=70.0)", "typing_extensions (>=4.9)"] - -[[package]] -name = "sphinx-rtd-theme" -version = "3.0.2" -description = "Read the Docs theme for Sphinx" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinx_rtd_theme-3.0.2-py2.py3-none-any.whl", hash = "sha256:422ccc750c3a3a311de4ae327e82affdaf59eb695ba4936538552f3b00f4ee13"}, - {file = "sphinx_rtd_theme-3.0.2.tar.gz", hash = "sha256:b7457bc25dda723b20b086a670b9953c859eab60a2a03ee8eb2bb23e176e5f85"}, -] - -[package.dependencies] -docutils = ">0.18,<0.22" -sphinx = ">=6,<9" -sphinxcontrib-jquery = ">=4,<5" - -[package.extras] -dev = ["bump2version", "transifex-client", "twine", "wheel"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "2.0.0" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5"}, - {file = "sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "2.0.0" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2"}, - {file = "sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.1.0" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8"}, - {file = "sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-napoleon" -version = "0.7" -description = "Sphinx \"napoleon\" extension." -optional = false -python-versions = "*" -files = [ - {file = "sphinxcontrib-napoleon-0.7.tar.gz", hash = "sha256:407382beed396e9f2d7f3043fad6afda95719204a1e1a231ac865f40abcbfcf8"}, - {file = "sphinxcontrib_napoleon-0.7-py2.py3-none-any.whl", hash = "sha256:711e41a3974bdf110a484aec4c1a556799eb0b3f3b897521a018ad7e2db13fef"}, -] - -[package.dependencies] -pockets = ">=0.3" -six = ">=1.5.2" - -[[package]] -name = "sphinxcontrib-qthelp" -version = "2.0.0" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb"}, - {file = "sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["defusedxml (>=0.7.1)", "pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "2.0.0" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" -optional = false -python-versions = ">=3.9" -files = [ - {file = "sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331"}, - {file = "sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d"}, -] - -[package.extras] -lint = ["mypy", "ruff (==0.5.5)", "types-docutils"] -standalone = ["Sphinx (>=5)"] -test = ["pytest"] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tomli" -version = "2.2.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "tzdata" -version = "2024.2" -description = "Provider of IANA time zone data" -optional = false -python-versions = ">=2" -files = [ - {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, - {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, -] - -[[package]] -name = "urllib3" -version = "2.2.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, - {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wcwidth" -version = "0.2.13" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = "*" -files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, -] - -[extras] -docs = [] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "85aa8519b8585d16c96a9d48c524fde512b5c718919b9dc0eb989e3c780a04fd" diff --git a/pyproject.toml b/pyproject.toml index 6c025ec..ddd78ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,29 +1,57 @@ -[tool.poetry] +[project] name = "selectsim" -version = "0.0.1" -description = "A python package that implement SelectSim algorithm" -authors = ["Arvind iyer "] +version = "0.3.0" +description = "A python package that implements the SelectSim algorithm" +authors = [ + { name = "Arvind Iyer", email = "ayalurarvind@gmail.com" }, +] readme = "README.md" license = "MIT" -repository = "https://github.com/CSOgroup/SelectSim_py" -packages = [{include = "selectsim"}] +# Upper-bounded below the latest CPython release: as of this writing, numpy/ +# scipy/zarr don't yet all publish prebuilt wheels for 3.13+, and +# installing on an unsupported interpreter can silently fall back to a slow +# (or failing, without a C/C++ toolchain) from-source build of numpy. Verified +# empirically that 3.10/3.11/3.12 all resolve prebuilt wheels and pass the +# full test suite; re-verify before widening this range. +requires-python = ">=3.10,<3.13" +dependencies = [ + "scipy>=1.14.1,<2", + "numpy>=2.1.3,<3", + "pandas>=2.2.3,<3", + "joblib>=1.4.0,<2", + "tqdm>=4.66.0,<5", + "pyarrow>=18.0.0,<19", + "matplotlib>=3.9.0,<4", + "seaborn>=0.13.0,<0.14", +] -[tool.poetry.dependencies] -python = "^3.10" -scipy = "^1.14.1" -numpy = "^2.1.3" -pandas = "^2.2.3" +[project.urls] +Repository = "https://github.com/CSOgroup/SelectSim_py" +[project.optional-dependencies] +storage = [ + "zarr>=2.18.0,<3", +] +docs = [ + "sphinx>=8.1.3,<9", + "sphinx-book-theme>=1.1.3,<2", + "sphinx-autodoc-typehints>=2.3.0,<3", + "sphinx-copybutton>=0.5.2,<0.6", + "myst-nb>=1.1.0,<2", + "jupyter>=1.1.0,<2", +] -[tool.poetry.group.dev.dependencies] -sphinx = "^8.1.3" -ipython = "^8.30.0" -sphinx-rtd-theme = "^3.0.2" -sphinxcontrib-napoleon = "^0.7" - -[tool.poetry.extras] -docs = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-napoleon"] +[dependency-groups] +dev = [ + "pytest>=8.0.0,<9", + "pytest-cov>=4.1.0,<5", + "pyreadr>=0.5.0,<0.6", + "ipython>=8.30.0,<9", +] [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["selectsim"] diff --git a/scripts/benchmark_python.py b/scripts/benchmark_python.py new file mode 100644 index 0000000..1d2d36e --- /dev/null +++ b/scripts/benchmark_python.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +""" +Benchmark the Python SelectSim port (numpy and jax backends) on the +bundled LUAD dataset for a range of n_permut values. + +Design notes +------------ +- Each invocation of this script performs exactly ONE benchmark + "combination" and prints CSV row(s) to stdout. An orchestrating shell + loop (see run_benchmarks.sh usage below, or just call this script in a + loop) launches one fresh Python process per (backend, n_permut, rep) so + that resource.getrusage(...).ru_maxrss (which is a whole-process, + monotonically-non-decreasing high-water mark on Linux) is meaningful + per run rather than accumulating across repeats. + +- backend="numpy": times the full `selectsim.selectX()` pipeline + (parsing/filtering, template generation, weight matrix, null model, + statistics, interaction table) -- this is the real, fully-supported + code path and matches what a user actually calls. + +- backend="jax": `selectX()` does NOT expose a backend passthrough (it + always calls `null_model_parallel(..., backend="numpy")` internally as + of this port). Per the benchmark task's own instructions, we instead + benchmark `null_model_parallel(..., backend="jax")` directly, with the + (untimed) setup of the AlterationLandscape / template matrices / + weight matrix done first. This is a reasonable proxy for the hot path + but is NOT an apples-to-apples full-pipeline number -- it omits the + statistics/interaction-table step that both R's selectX() and Python's + numpy-backend selectX() include. This is called out in the report. + + Because jax JIT-compiles the vmapped simulation function per input + shape (which depends on n_permut), a fresh process pays a one-time + "cold" compile cost. We report both a "cold" call (first call in the + process, includes trace+compile) and a "warm" call (second call, same + n_permut, same process, reuses the compiled function) so the report + can discuss JIT overhead explicitly. + +Metrics +------- +- wall_sec: time.perf_counter() delta around the timed call. +- tracemalloc_peak_mb: peak Python-allocated memory during the timed + call only (tracemalloc.start()/stop() scoped tightly around the + call). Does NOT capture native buffers allocated by numpy/jax's C/XLA + layer outside Python's allocator -- an undercount, especially for jax. +- ru_maxrss_mb: resource.getrusage(RUSAGE_SELF).ru_maxrss measured after + the call, for the whole process since it started (includes Python + startup, imports, data loading/setup, and the timed call) -- an + overcount of the timed call in isolation, but the only cheap + whole-process (including native/BLAS/XLA buffers) peak-RSS proxy + available without extra dependencies. Reported in MB (KB on Linux from + the kernel, /1024). + +Usage +----- + PYTHONPATH= python3 benchmark_python.py --backend numpy --n-permut 100 --rep 1 + PYTHONPATH= python3 benchmark_python.py --backend jax --n-permut 100 --rep 1 +""" + +import argparse +import resource +import sys +import time +import tracemalloc +from pathlib import Path + +import pandas as pd + +REPO_ROOT = Path(__file__).resolve().parent.parent +DATA_DIR = REPO_ROOT / "tests" / "data" + + +def load_luad_run_data(): + """Replicate tests/conftest.py's luad_run_data fixture assembly.""" + missense = pd.read_parquet(DATA_DIR / "luad_missense.parquet") + truncating = pd.read_parquet(DATA_DIR / "luad_truncating.parquet") + tmb_missense = pd.read_parquet(DATA_DIR / "luad_tmb_missense.parquet") + tmb_truncating = pd.read_parquet(DATA_DIR / "luad_tmb_truncating.parquet") + sample_class_df = pd.read_parquet(DATA_DIR / "luad_sample_class.parquet") + alteration_class_df = pd.read_parquet(DATA_DIR / "luad_alteration_class.parquet") + + M = { + "M": {"missense": missense, "truncating": truncating}, + "tmb": {"missense": tmb_missense, "truncating": tmb_truncating}, + } + sample_class = dict(zip(sample_class_df["sample"], sample_class_df["sample_class"])) + alteration_class = dict(zip(alteration_class_df["gene"], alteration_class_df["alteration_class"])) + return {"M": M, "sample_class": sample_class, "alteration_class": alteration_class} + + +def ru_maxrss_mb() -> float: + # Linux: ru_maxrss is in KB. + return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0 + + +def timed_call(fn, *args, **kwargs): + tracemalloc.start() + t0 = time.perf_counter() + result = fn(*args, **kwargs) + elapsed = time.perf_counter() - t0 + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + peak_mb = peak / (1024 * 1024) + rss_mb = ru_maxrss_mb() + return result, elapsed, peak_mb, rss_mb + + +def bench_numpy(n_permut: int, seed: int): + from selectsim import selectX + + data = load_luad_run_data() + result, elapsed, peak_mb, rss_mb = timed_call( + selectX, + data["M"], + data["sample_class"], + data["alteration_class"], + n_cores=1, + min_freq=10, + n_permut=n_permut, + lambda_var=0.3, + tao=1.0, + verbose=False, + seed=seed, + ) + n_rows = len(result["result"]) + return [("python-numpy", n_permut, elapsed, peak_mb, rss_mb, n_rows, "full_pipeline")] + + +def bench_jax(n_permut: int, seed: int): + from selectsim.alteration_landscape import AlterationLandscape + from selectsim.template import template_obj_gen + from selectsim.weights import generate_w_block + from selectsim.null_model import null_model_parallel + + data = load_luad_run_data() + # Unmeasured setup shared by both the cold and warm timed calls. + al = AlterationLandscape( + data["M"], + feat_covariates=data["alteration_class"], + sample_covariates=data["sample_class"], + min_freq=10, + verbose=False, + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al, lambda_=0.3, tao=1.0) + + rows = [] + + sim, elapsed, peak_mb, rss_mb = timed_call( + null_model_parallel, + al, temp_data["temp_mat"], W["W"], + n_cores=1, n_permut=n_permut, seed=seed, verbose=False, backend="jax", + ) + rows.append(("python-jax", n_permut, elapsed, peak_mb, rss_mb, len(sim), "null_model_core_cold")) + + sim, elapsed, peak_mb, rss_mb = timed_call( + null_model_parallel, + al, temp_data["temp_mat"], W["W"], + n_cores=1, n_permut=n_permut, seed=seed + 1, verbose=False, backend="jax", + ) + rows.append(("python-jax", n_permut, elapsed, peak_mb, rss_mb, len(sim), "null_model_core_warm")) + + return rows + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--backend", choices=["numpy", "jax"], required=True) + parser.add_argument("--n-permut", type=int, required=True) + parser.add_argument("--rep", type=int, required=True) + parser.add_argument("--seed", type=int, default=42) + parser.add_argument("--header", action="store_true", help="print CSV header first") + args = parser.parse_args() + + seed = args.seed + args.rep + + if args.backend == "numpy": + rows = bench_numpy(args.n_permut, seed) + else: + rows = bench_jax(args.n_permut, seed) + + if args.header: + print("backend,n_permut,rep,elapsed_sec,tracemalloc_peak_mb,ru_maxrss_mb,n_result_rows,scope") + for backend, n_permut, elapsed, peak_mb, rss_mb, n_rows, scope in rows: + print(f"{backend},{n_permut},{args.rep},{elapsed:.4f},{peak_mb:.2f},{rss_mb:.2f},{n_rows},{scope}") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/benchmark_r_vs_python.R b/scripts/benchmark_r_vs_python.R new file mode 100644 index 0000000..24955f9 --- /dev/null +++ b/scripts/benchmark_r_vs_python.R @@ -0,0 +1,57 @@ +#!/usr/bin/env Rscript +# Benchmark R SelectSim::selectX() wall-clock time and peak memory on the +# bundled LUAD dataset, for a range of n.permut values. +# +# Memory is measured with base R only (no tictoc/peakRAM, per the +# environment constraints for this benchmark): gc(reset=TRUE) before the +# call, gc() after, summing the "max used (Mb)" column across the Ncells +# and Vcells rows. This tracks peak R-heap usage since the reset, not +# whole-process RSS (comparable in spirit to Python's tracemalloc, not to +# resource::ru_maxrss). +# +# Usage: +# Rscript benchmark_r_vs_python.R > benchmark_r_results.csv +# +# Run from the SelectSim (R package) directory or with the package +# installed in the active R environment. + +suppressMessages(library(SelectSim)) + +data(luad_run_data) + +n_permut_values <- c(100, 500, 1500) +n_reps <- 2 + +run_once <- function(n_permut, seed) { + invisible(gc(reset = TRUE)) + set.seed(seed) + t0 <- Sys.time() + res <- selectX( + M = luad_run_data$M, + sample.class = luad_run_data$sample.class, + alteration.class = luad_run_data$alteration.class, + n.cores = 1, + min.freq = 10, + n.permut = n_permut, + lambda = 0.3, + tau = 1, + verbose = FALSE + ) + t1 <- Sys.time() + elapsed <- as.numeric(difftime(t1, t0, units = "secs")) + g <- gc() + peak_mb <- g[1, 6] + g[2, 6] # Ncells max used (Mb) + Vcells max used (Mb) + n_rows <- nrow(res$result) + list(elapsed = elapsed, peak_mb = peak_mb, n_rows = n_rows) +} + +cat("backend,n_permut,rep,elapsed_sec,peak_mem_mb,n_result_rows\n") +for (n_permut in n_permut_values) { + for (rep in seq_len(n_reps)) { + r <- run_once(n_permut, seed = 42 + rep) + cat(sprintf( + "R,%d,%d,%.4f,%.2f,%d\n", + n_permut, rep, r$elapsed, r$peak_mb, r$n_rows + )) + } +} diff --git a/scripts/csv_to_parquet.py b/scripts/csv_to_parquet.py new file mode 100644 index 0000000..361106d --- /dev/null +++ b/scripts/csv_to_parquet.py @@ -0,0 +1,53 @@ +""" +Convert the flat CSVs produced by scripts/export_r_data.R into compressed +Parquet files bundled with the test suite (tests/data/). + +Run: python scripts/csv_to_parquet.py +""" + +from pathlib import Path + +import pandas as pd + +RAW_DIR = Path(__file__).parent.parent / "tests" / "data_raw" +OUT_DIR = Path(__file__).parent.parent / "tests" / "data" + + +def convert_matrix(name: str) -> None: + """genes x samples binary matrix, first column is the gene name index.""" + df = pd.read_csv(RAW_DIR / f"{name}.csv") + df = df.set_index("gene") + df.to_parquet(OUT_DIR / f"{name}.parquet", compression="zstd") + + +def convert_plain(name: str) -> None: + df = pd.read_csv(RAW_DIR / f"{name}.csv") + df.to_parquet(OUT_DIR / f"{name}.parquet", index=False, compression="zstd") + + +def main() -> None: + OUT_DIR.mkdir(parents=True, exist_ok=True) + + convert_matrix("luad_missense") + convert_matrix("luad_truncating") + + for name in [ + "luad_tmb_missense", + "luad_tmb_truncating", + "luad_sample_class", + "luad_alteration_class", + "luad_result", + "luad_maf", + "oncokb_genes", + "oncokb_truncating_genes", + "variant_catalogue", + ]: + convert_plain(name) + + total_bytes = sum(f.stat().st_size for f in OUT_DIR.glob("*.parquet")) + print(f"Wrote {len(list(OUT_DIR.glob('*.parquet')))} parquet files, " + f"{total_bytes / 1024:.1f} KiB total, to {OUT_DIR}") + + +if __name__ == "__main__": + main() diff --git a/scripts/export_r_data.R b/scripts/export_r_data.R new file mode 100644 index 0000000..b73833f --- /dev/null +++ b/scripts/export_r_data.R @@ -0,0 +1,66 @@ +#!/usr/bin/env Rscript +# Exports the .rda test/example datasets bundled with the R SelectSim +# package into flat CSV files that Python can convert to Parquet. +# +# Run under `micromamba activate r_env`: +# Rscript SelectSim_py/scripts/export_r_data.R +# +# luad_run_data.rda is a nested list (M$M$missense, M$M$truncating, +# M$tmb$missense, M$tmb$truncating, sample.class, alteration.class) which +# pyreadr cannot read directly, so we flatten it here into separate +# rectangular files. + +args <- commandArgs(trailingOnly = TRUE) +r_data_dir <- if (length(args) >= 1) args[[1]] else "../SelectSim/data" +out_dir <- if (length(args) >= 2) args[[2]] else "tests/data_raw" + +dir.create(out_dir, showWarnings = FALSE, recursive = TRUE) + +write_matrix_csv <- function(mat, path) { + df <- as.data.frame(mat) + df <- cbind(gene = rownames(mat), df) + write.csv(df, path, row.names = FALSE) +} + +message("Loading luad_run_data.rda ...") +load(file.path(r_data_dir, "luad_run_data.rda")) + +write_matrix_csv(luad_run_data$M$M$missense, file.path(out_dir, "luad_missense.csv")) +write_matrix_csv(luad_run_data$M$M$truncating, file.path(out_dir, "luad_truncating.csv")) + +write.csv(luad_run_data$M$tmb$missense, file.path(out_dir, "luad_tmb_missense.csv"), row.names = FALSE) +write.csv(luad_run_data$M$tmb$truncating, file.path(out_dir, "luad_tmb_truncating.csv"), row.names = FALSE) + +sample_class_df <- data.frame( + sample = names(luad_run_data$sample.class), + sample_class = unname(luad_run_data$sample.class) +) +write.csv(sample_class_df, file.path(out_dir, "luad_sample_class.csv"), row.names = FALSE) + +alteration_class_df <- data.frame( + gene = names(luad_run_data$alteration.class), + alteration_class = unname(luad_run_data$alteration.class) +) +write.csv(alteration_class_df, file.path(out_dir, "luad_alteration_class.csv"), row.names = FALSE) + +message("Loading luad_result.rda ...") +load(file.path(r_data_dir, "luad_result.rda")) +write.csv(luad_result, file.path(out_dir, "luad_result.csv"), row.names = FALSE) + +message("Loading luad_maf.rda ...") +load(file.path(r_data_dir, "luad_maf.rda")) +write.csv(luad_maf, file.path(out_dir, "luad_maf.csv"), row.names = FALSE) + +message("Loading oncokb_genes.rda ...") +load(file.path(r_data_dir, "oncokb_genes.rda")) +write.csv(data.frame(gene = oncokb_genes), file.path(out_dir, "oncokb_genes.csv"), row.names = FALSE) + +message("Loading oncokb_truncating_genes.rda ...") +load(file.path(r_data_dir, "oncokb_truncating_genes.rda")) +write.csv(data.frame(gene = oncokb_truncating_genes), file.path(out_dir, "oncokb_truncating_genes.csv"), row.names = FALSE) + +message("Loading variant_catalogue.rda ...") +load(file.path(r_data_dir, "variant_catalogue.rda")) +write.csv(variant_catalogue, file.path(out_dir, "variant_catalogue.csv"), row.names = FALSE) + +message("Done. CSVs written to: ", normalizePath(out_dir)) diff --git a/selectsim/__init__.py b/selectsim/__init__.py index f7ccc5a..f7be984 100644 --- a/selectsim/__init__.py +++ b/selectsim/__init__.py @@ -1,2 +1,135 @@ -from .selectsim import selectX -from .alteration_landscape import AlterationLandscape \ No newline at end of file +""" +SelectSim - Python implementation of the SelectSim algorithm. + +A bioinformatics package for inferring inter-dependencies (co-mutations +and mutual exclusivity) between functional alterations in cancer. +""" + +from selectsim.selectsim import selectX +from selectsim.alteration_landscape import AlterationLandscape +from selectsim.template import generate_s, template_obj_gen +from selectsim.weights import generate_w_mean_tmb, generate_w_block +from selectsim.null_model import null_model_parallel, retrieve_outliers +from selectsim.stats import ( + am_stats, + al_stats, + am_pairwise_alteration_overlap, + am_weight_pairwise_alteration_overlap, + r_am_pairwise_alteration_overlap, + w_r_am_pairwise_alteration_overlap, + effect_size, + estimate_fdr2, + binary_yule, + interaction_table, + estimate_p_val, + estimate_pairwise_p +) +from selectsim.utils import ( + add, + pairwise_indices, + matrix_to_pairwise_vector, + create_pair_template +) +from selectsim.gam import ( + mutation_type, + TCGA_maf_schema, + GENIE_maf_schema, + filter_maf_column, + filter_maf_complex, + filter_maf_sample, + filter_maf_gene_name, + filter_maf_mutation_type, + filter_maf_mutations, + filter_maf_schema, + filter_maf_truncating, + filter_maf_missense, + filter_maf_ignore, + stat_maf_column, + stat_maf_sample, + stat_maf_gene, + maf_to_gam +) +from selectsim.plotting import ( + theme_publication, + obs_exp_scatter, + significance_heatmap, + overlap_pair_extract, + ridge_plot_ed, + ridge_plot_ed_compare, + oncoprint_pair, + oncoprint +) +from selectsim.io import ( + read_parquet, + write_parquet, + create_zarr_null_store, + open_zarr_store +) + +__version__ = "0.3.0" + +__all__ = [ + # Main function + "selectX", + # Core classes + "AlterationLandscape", + # Template functions + "generate_s", + "template_obj_gen", + # Weight functions + "generate_w_mean_tmb", + "generate_w_block", + # Null model functions + "null_model_parallel", + "retrieve_outliers", + # Statistics functions + "am_stats", + "al_stats", + "am_pairwise_alteration_overlap", + "am_weight_pairwise_alteration_overlap", + "r_am_pairwise_alteration_overlap", + "w_r_am_pairwise_alteration_overlap", + "effect_size", + "estimate_fdr2", + "binary_yule", + "interaction_table", + "estimate_p_val", + "estimate_pairwise_p", + # Utility functions + "add", + "pairwise_indices", + "matrix_to_pairwise_vector", + "create_pair_template", + # MAF / GAM ingestion + "mutation_type", + "TCGA_maf_schema", + "GENIE_maf_schema", + "filter_maf_column", + "filter_maf_complex", + "filter_maf_sample", + "filter_maf_gene_name", + "filter_maf_mutation_type", + "filter_maf_mutations", + "filter_maf_schema", + "filter_maf_truncating", + "filter_maf_missense", + "filter_maf_ignore", + "stat_maf_column", + "stat_maf_sample", + "stat_maf_gene", + "maf_to_gam", + # Plotting + "theme_publication", + "obs_exp_scatter", + "significance_heatmap", + "overlap_pair_extract", + "ridge_plot_ed", + "ridge_plot_ed_compare", + "oncoprint_pair", + "oncoprint", + # I/O (Parquet / Zarr) + "read_parquet", + "write_parquet", + "create_zarr_null_store", + "open_zarr_store", +] diff --git a/selectsim/alteration_landscape.py b/selectsim/alteration_landscape.py index 5505faf..3a3db7c 100644 --- a/selectsim/alteration_landscape.py +++ b/selectsim/alteration_landscape.py @@ -1,12 +1,22 @@ +""" +Alteration Landscape module for SelectSim. + +This module contains the AlterationLandscape class which is the core +data structure for representing gene alteration matrices and tumor +mutation burden data. +""" + +from typing import Dict, Any, Optional, List import numpy as np import pandas as pd + class AlterationLandscape: def __init__(self, am, feat_covariates=None, sample_covariates=None, min_freq=0, verbose=False): """ Initialize the Alteration Landscape (AL) object. - Parameters: + Parameters ---------- am : dict Dictionary containing the binary alteration matrix (`M`) and tumor mutation burden (`tmb`). @@ -19,36 +29,37 @@ def __init__(self, am, feat_covariates=None, sample_covariates=None, min_freq=0, verbose : bool Whether to print verbose information during initialization. - Methods: - ---------- + Methods + ------- get_blocks() Get sample and alterations blocks from alteration landscape object. - Examples: - --------- - # Example usage - import numpy as np - import pandas as pd - import selectsim as ss - am = { - "M": { - "missense": pd.DataFrame(np.random.randint(0, 2, (5, 10)), index=[f"gene{i}" for i in range(5)], columns=[f"sample{j}" for j in range(10)]), - "nonsense": pd.DataFrame(np.random.randint(0, 2, (5, 10)), index=[f"gene{i}" for i in range(5)], columns=[f"sample{j}" for j in range(10)]), - }, - "tmb": { - "missense": pd.DataFrame({"sample": [f"sample{j}" for j in range(10)], "mutation": np.random.randint(0, 10, 10)}), - "nonsense": pd.DataFrame({"sample": [f"sample{j}" for j in range(10)], "mutation": np.random.randint(0, 10, 10)}), + Examples + -------- + .. code-block:: python + + import numpy as np + import pandas as pd + import selectsim as ss + + am = { + "M": { + "missense": pd.DataFrame(np.random.randint(0, 2, (5, 10)), index=[f"gene{i}" for i in range(5)], columns=[f"sample{j}" for j in range(10)]), + "nonsense": pd.DataFrame(np.random.randint(0, 2, (5, 10)), index=[f"gene{i}" for i in range(5)], columns=[f"sample{j}" for j in range(10)]), + }, + "tmb": { + "missense": pd.DataFrame({"sample": [f"sample{j}" for j in range(10)], "mutation": np.random.randint(0, 10, 10)}), + "nonsense": pd.DataFrame({"sample": [f"sample{j}" for j in range(10)], "mutation": np.random.randint(0, 10, 10)}), + } } - } - keys = [f'sample{i}' for i in range(10)] - categories = ['LUAD', 'LUSC', 'GBM'] # Possible categories - # Randomly assign a category to each sample - values = np.random.choice(categories, size=10) - values = [str(value) for value in values] - sample_dict = dict(zip(keys, values)) - - al_object = ss.AlterationLandscape(am=am,sample_covariates=sample_dict,min_freq=1,verbose=True) - + keys = [f'sample{i}' for i in range(10)] + categories = ['LUAD', 'LUSC', 'GBM'] # Possible categories + # Randomly assign a category to each sample + values = np.random.choice(categories, size=10) + values = [str(value) for value in values] + sample_dict = dict(zip(keys, values)) + + al_object = ss.AlterationLandscape(am=am, sample_covariates=sample_dict, min_freq=1, verbose=True) """ if "M" not in am or am["M"] is None: raise ValueError("Problem: Input data is null") @@ -109,9 +120,10 @@ def get_blocks(self): """ Get sample and alteration blocks from an AlterationLandscape object. - Returns: - -------- - - dict: A dictionary containing 'sample.blocks', 'alteration.blocks', 'missing.samples', and 'missing.alterations'. + Returns + ------- + dict + A dictionary containing 'sample.blocks', 'alteration.blocks', 'missing.samples', and 'missing.alterations'. """ # Ensure the alteration and sample covariates are correctly set alteration_class = pd.Series(self.alterations['alteration_class']) diff --git a/selectsim/gam.py b/selectsim/gam.py new file mode 100644 index 0000000..a3fd6a3 --- /dev/null +++ b/selectsim/gam.py @@ -0,0 +1,736 @@ +""" +MAF (Mutation Annotation Format) ingestion utilities for SelectSim. + +This module ports ``SelectSim/R/gam_utils.r``. It provides: + +- A set of composable ``filter_maf_*`` functions for whittling a raw MAF + dataframe down to the mutations relevant to an analysis (by column + value, sample, gene, mutation type, schema-driven mutation-type + buckets, or arbitrary gene/mutation combinations). +- ``stat_maf_*`` helper functions summarizing a MAF (counts per column, + per sample, per gene). +- ``maf_to_gam``, which turns a (filtered) MAF dataframe into a binary + Gene Alteration Matrix (GAM) -- the ``genes x samples`` presence/ + absence matrix consumed by :func:`selectsim.selectX`. +- ``mutation_type``, ``TCGA_maf_schema`` and ``GENIE_maf_schema``: schema + constants describing which ``Variant_Classification`` values count as + truncating / missense / ignorable mutations, and which MAF columns + hold gene, sample and mutation information, for TCGA- and GENIE-style + MAF files respectively. + +See ``SelectSim/vignettes/data_processing.Rmd`` for the canonical, +end-to-end example of chaining these functions together to go from a +raw MAF to the ``M`` / ``tmb`` structure expected by ``selectX``. +""" + +import warnings +from typing import Any, Dict, Iterable, List, Optional, Sequence, Union + +import numpy as np +import pandas as pd + + +def _unique_preserve_order(values: Iterable[Any]) -> List[Any]: + """Return the unique elements of ``values``, preserving first-seen order.""" + return list(dict.fromkeys(values)) + + +## --------------------------------------------------------------------- +## General schemas +## --------------------------------------------------------------------- + +#: Mapping from mutation-type bucket name to the ``Variant_Classification`` +#: values that fall into it. Mirrors R's ``mutation_type`` list object. +mutation_type: Dict[str, List[str]] = { + "truncating": [ + "Nonsense_Mutation", + "Frame_Shift_Ins", + "Frame_Shift_Del", + "Splice_Site", + "In_Frame_Ins", + "In_Frame_Del", + ], + "missense": ["Missense_Mutation", "Splice_Site"], + "ignore": [ + "Silent", + "lincRNA", + "IGR", + "3'UTR", + "5'UTR", + "Intron", + "5'Flank", + "3'Flank", + "RNA", + "synonymous_variant", + "upstream_gene_variant", + "intron_variant", + "3_prime_UTR_variant", + "5_prime_UTR_variant", + "downstream_gene_variant", + "5_prime_UTR_premature_start_codon_gain_variant", + ], +} + + +#: Schema describing a TCGA-style MAF: which columns hold gene/sample/ +#: mutation information, and which ``Variant_Classification`` values are +#: considered truncating, missense, or ignorable. Mirrors R's +#: ``TCGA_maf_schema`` list object. +TCGA_maf_schema: Dict[str, Any] = { + "name": "TCGA_maf", + "column": { + "gene": "Hugo_Symbol", + "gene.name": "Hugo_Symbol", + "sample": "Tumor_Sample_Barcode", + "sample.name": "Tumor_Sample_Barcode", + "mutation.type": "Variant_Classification", + "mutation": "HGVSp_Short", + }, + "mutation.type": { + "truncating": _unique_preserve_order( + mutation_type["truncating"] + + ["Nonsense_Mutation", "Frame_Shift_Ins", "Frame_Shift_Del", "Splice_Site"] + ), + "missense": _unique_preserve_order( + mutation_type["missense"] + + ["Missense_Mutation", "Splice_Site", "In_Frame_Ins", "In_Frame_Del"] + ), + "ignore": _unique_preserve_order(mutation_type["ignore"]), + }, +} + + +#: Schema describing a GENIE-style MAF. Mirrors R's ``GENIE_maf_schema`` +#: list object. +GENIE_maf_schema: Dict[str, Any] = { + "column": { + "gene": "Hugo_Symbol", + "gene.name": "Hugo_Symbol", + "sample": "Tumor_Sample_Barcode", + "sample.name": "Tumor_Sample_Barcode", + "mutation.type": "Variant_Classification", + "mutation": "HGVSp_Short", + }, + "mutation.type": { + "truncating": _unique_preserve_order( + [ + "Nonsense_Mutation", + "Frame_Shift_Ins", + "Frame_Shift_Del", + "Splice_Site", + "In_Frame_Ins", + "In_Frame_Del", + ] + ), + "missense": _unique_preserve_order(["Missense_Mutation", "Splice_Site"]), + "ignore": _unique_preserve_order(mutation_type["ignore"]), + }, +} + + +## --------------------------------------------------------------------- +## Filtering functions +## --------------------------------------------------------------------- + +def filter_maf_column( + maf: pd.DataFrame, + values: Union[Any, Sequence[Any]], + column: str, + inclusive: bool = True, + fixed: bool = True, + dedupe: bool = True, + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by retaining rows whose ``column`` value is in ``values``. + + Parameters + ---------- + maf : pd.DataFrame + A MAF as a dataframe. + values : Any or Sequence[Any] + The value(s) to filter on. + column : str + Column in ``maf`` to filter on. + inclusive : bool, optional + If True (default), keep rows whose ``column`` value is in ``values``. + If False, keep rows whose ``column`` value is *not* in ``values``. + fixed : bool, optional + If True (default), match ``values`` exactly (set membership). If + False, match rows whose (stringified) ``column`` value *contains* + one of the ``values`` as a literal substring. Mirrors R's + ``grep(..., fixed = TRUE)`` semantics used in the original + ``filter_maf_column()``. + dedupe : bool, optional + If True (default), drop duplicate rows from the result. Pure + boolean-mask filtering can never introduce a duplicate row that + wasn't already present in ``maf``, so deduplication only ever + needs to happen once. When chaining several ``filter_maf_*`` + calls, pass ``dedupe=False`` to the later calls in the chain (or + dedupe ``maf`` once up front) to avoid re-scanning for duplicates + after every step. + **kwargs : Any + Ignored; accepted for interface compatibility with the R version's + ``...``. + + Returns + ------- + pd.DataFrame + The filtered (and, by default, de-duplicated) MAF dataframe. + + Examples + -------- + >>> import pandas as pd + >>> maf = pd.DataFrame({"Variant_Classification": ["Missense_Mutation", "Silent"]}) + >>> filter_maf_column(maf, values="Missense_Mutation", column="Variant_Classification") + Variant_Classification + 0 Missense_Mutation + """ + if column not in maf.columns: + raise ValueError(f"{column} is not a valid column for the specified maf") + + if not isinstance(values, (list, tuple, set, np.ndarray, pd.Series)): + values = [values] + values = list(values) + + if fixed: + mask = maf[column].isin(values) + if not inclusive: + mask = ~mask + filtered_maf = maf.loc[mask] + else: + unique_values = _unique_preserve_order(values) + if len(unique_values) * len(maf) > 100_000: + warnings.warn( + f"Running grep on {len(unique_values)} unique values vs {len(maf)} maf rows..." + ) + col_str = maf[column].astype(str) + # Build the "contains ANY of values" mask first, then negate once for + # inclusive=False -- negating per-value inside the loop and unioning + # the results (the previous approach) computes the union of + # exclusions rather than their intersection, so a row containing + # only one of several excluded values would incorrectly survive. + mask = np.zeros(len(maf), dtype=bool) + for v in unique_values: + mask |= col_str.str.contains(str(v), regex=False, na=False).to_numpy() + if not inclusive: + mask = ~mask + filtered_maf = maf.loc[mask] + + if dedupe: + filtered_maf = filtered_maf.drop_duplicates() + return filtered_maf + + +def filter_maf_complex( + maf: pd.DataFrame, + values: pd.DataFrame, + left_on: Optional[Union[str, Sequence[str]]] = None, + right_on: Optional[Union[str, Sequence[str]]] = None, + on: Optional[Union[str, Sequence[str]]] = None, + how: str = "inner", + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by a combination of column values. + + Equivalent to R's ``filter_maf_complex()``, which inner-joins ``maf`` + against a dataframe of allowed (column, value) combinations. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + values : pd.DataFrame + A dataframe of (column, value) combinations to match against. + left_on : str or Sequence[str], optional + Column(s) in ``maf`` to join on. + right_on : str or Sequence[str], optional + Column(s) in ``values`` to join on. + on : str or Sequence[str], optional + Column name(s) to join on when they are identical in both frames. + how : str, optional + Join type, passed to :func:`pandas.merge`. Default ``"inner"``, + matching R's ``merge()`` default of ``all = FALSE``. + **kwargs : Any + Additional keyword arguments passed through to :func:`pandas.merge`. + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe containing only rows matching the value + combinations in ``values``. + + Examples + -------- + >>> import pandas as pd + >>> maf = pd.DataFrame({"Hugo_Symbol": ["TP53", "KRAS"], + ... "Variant_Classification": ["Missense_Mutation", "Silent"]}) + >>> combos = pd.DataFrame({"Hugo_Symbol": ["TP53"], + ... "Variant_Classification": ["Missense_Mutation"]}) + >>> filter_maf_complex(maf, combos, on=["Hugo_Symbol", "Variant_Classification"]) + Hugo_Symbol Variant_Classification + 0 TP53 Missense_Mutation + """ + filtered_maf = maf.merge( + values, + left_on=left_on, + right_on=right_on, + on=on, + how=how, + suffixes=("", "_to_ignore"), + **kwargs, + ) + filtered_maf = filtered_maf.drop_duplicates() + return filtered_maf + + +def filter_maf_sample( + maf: pd.DataFrame, + samples: Union[Any, Sequence[Any]], + sample_col: str = "Tumor_Sample_Barcode", + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by sample ID. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + samples : Any or Sequence[Any] + Sample ID(s) to retain. + sample_col : str, optional + Column name containing sample IDs. Default ``"Tumor_Sample_Barcode"``. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_column`. + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + return filter_maf_column(maf, values=samples, column=sample_col, **kwargs) + + +def filter_maf_gene_name( + maf: pd.DataFrame, + genes: Union[Any, Sequence[Any]], + gene_col: str = "Hugo_Symbol", + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by gene name. + + R equivalent: ``filter_maf_gene.name()``. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + genes : Any or Sequence[Any] + Gene name(s) to retain. + gene_col : str, optional + Column name containing gene symbols. Default ``"Hugo_Symbol"``. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_column`. + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + return filter_maf_column(maf, values=genes, column=gene_col, **kwargs) + + +def filter_maf_mutation_type( + maf: pd.DataFrame, + variants: Union[Any, Sequence[Any]], + variant_col: str = "Variant_Classification", + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by mutation type. + + R equivalent: ``filter_maf_mutation.type()``. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + variants : Any or Sequence[Any] + Variant classification value(s) to retain. + variant_col : str, optional + Column name containing variant classifications. + Default ``"Variant_Classification"``. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_column`. + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + return filter_maf_column(maf, values=variants, column=variant_col, **kwargs) + + +def filter_maf_mutations( + maf: pd.DataFrame, + values: pd.DataFrame, + maf_col: Sequence[str] = ("Hugo_Symbol", "HGVSp_Short"), + values_col: Optional[Sequence[str]] = None, + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by specific gene-mutation combinations. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + values : pd.DataFrame + Dataframe of allowed (gene, mutation) combinations. + maf_col : Sequence[str], optional + Columns in ``maf`` to join on. + Default ``("Hugo_Symbol", "HGVSp_Short")``. + values_col : Sequence[str], optional + Corresponding columns in ``values`` to join on. Defaults to + ``maf_col``. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_complex`. + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe containing only rows matching the allowed + gene-mutation combinations. + """ + maf_col = list(maf_col) + values_col = list(values_col) if values_col is not None else list(maf_col) + return filter_maf_complex(maf, values, left_on=maf_col, right_on=values_col, **kwargs) + + +## --------------------------------------------------------------------- +## Schema-driven filters +## --------------------------------------------------------------------- + +def filter_maf_schema( + maf: pd.DataFrame, + schema: Dict[str, Any] = TCGA_maf_schema, + *, + column: str, + values: Union[Any, Sequence[Any]], + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe using a schema-defined column and values. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + schema : Dict[str, Any], optional + A schema dataframe/dict; see :data:`TCGA_maf_schema` for an + example. Default :data:`TCGA_maf_schema`. + column : str + Key into ``schema["column"]`` identifying which MAF column to + filter on (e.g. ``"mutation.type"``, ``"gene"``, ``"sample"``). + values : Any or Sequence[Any] + Value(s) to filter on. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_column` + (e.g. ``inclusive=False``). + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + + Examples + -------- + >>> import pandas as pd + >>> maf = pd.DataFrame({"Variant_Classification": ["Nonsense_Mutation", "Silent"]}) + >>> filter_maf_schema(maf, TCGA_maf_schema, column="mutation.type", + ... values=TCGA_maf_schema["mutation.type"]["truncating"]) + Variant_Classification + 0 Nonsense_Mutation + """ + variant_col = schema["column"][column] + return filter_maf_column(maf, values=values, column=variant_col, **kwargs) + + +def filter_maf_truncating( + maf: pd.DataFrame, + schema: Dict[str, Any] = TCGA_maf_schema, + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by retaining truncating mutations. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + schema : Dict[str, Any], optional + A schema dict; see :data:`TCGA_maf_schema`. Default + :data:`TCGA_maf_schema`. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_schema` / + :func:`filter_maf_column` (e.g. ``inclusive=False``). + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + column = "mutation.type" + return filter_maf_schema( + maf, schema=schema, column=column, values=schema[column]["truncating"], **kwargs + ) + + +def filter_maf_missense( + maf: pd.DataFrame, + schema: Dict[str, Any] = TCGA_maf_schema, + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by retaining missense mutations. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + schema : Dict[str, Any], optional + A schema dict; see :data:`TCGA_maf_schema`. Default + :data:`TCGA_maf_schema`. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_schema` / + :func:`filter_maf_column` (e.g. ``inclusive=False``). + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + column = "mutation.type" + return filter_maf_schema( + maf, schema=schema, column=column, values=schema[column]["missense"], **kwargs + ) + + +def filter_maf_ignore( + maf: pd.DataFrame, + schema: Dict[str, Any] = TCGA_maf_schema, + **kwargs: Any, +) -> pd.DataFrame: + """ + Filter a MAF dataframe by retaining "ignore" (non-functional) mutations. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + schema : Dict[str, Any], optional + A schema dict; see :data:`TCGA_maf_schema`. Default + :data:`TCGA_maf_schema`. + **kwargs : Any + Additional keyword arguments passed to :func:`filter_maf_schema` / + :func:`filter_maf_column` (e.g. ``inclusive=False`` to instead + drop ignorable mutations and keep everything else). + + Returns + ------- + pd.DataFrame + Filtered MAF dataframe. + """ + column = "mutation.type" + return filter_maf_schema( + maf, schema=schema, column=column, values=schema[column]["ignore"], **kwargs + ) + + +## --------------------------------------------------------------------- +## Summary functions +## --------------------------------------------------------------------- + +def stat_maf_column(maf: pd.DataFrame, column: str, **kwargs: Any) -> pd.Series: + """ + Count occurrences of each value in a MAF column. + + Equivalent to R's ``stat_maf_column()``, which returns ``table(maf[, column])``. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + column : str + Column to tabulate. + **kwargs : Any + Ignored; accepted for interface compatibility with the R version. + + Returns + ------- + pd.Series + Counts per distinct value, indexed by value and sorted + ascending by index (mirroring R's ``table()`` alphabetical + ordering by factor level). + """ + if column not in maf.columns: + raise ValueError(f"{column} is not a valid column for the specified maf") + return maf[column].value_counts().sort_index() + + +def stat_maf_sample( + maf: pd.DataFrame, column: str = "Tumor_Sample_Barcode", **kwargs: Any +) -> pd.Series: + """ + Count mutations per sample in a MAF dataframe. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + column : str, optional + Column name containing sample IDs. Default ``"Tumor_Sample_Barcode"``. + **kwargs : Any + Additional keyword arguments passed to :func:`stat_maf_column`. + + Returns + ------- + pd.Series + Mutation counts per sample. + """ + return stat_maf_column(maf, column, **kwargs) + + +def stat_maf_gene(maf: pd.DataFrame, column: str = "Hugo_Symbol", **kwargs: Any) -> pd.Series: + """ + Count mutations per gene in a MAF dataframe. + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + column : str, optional + Column name containing gene symbols. Default ``"Hugo_Symbol"``. + **kwargs : Any + Additional keyword arguments passed to :func:`stat_maf_column`. + + Returns + ------- + pd.Series + Mutation counts per gene. + """ + return stat_maf_column(maf, column, **kwargs) + + +## --------------------------------------------------------------------- +## GAM generation +## --------------------------------------------------------------------- + +def maf_to_gam( + maf: pd.DataFrame, + sample_col: str = "Tumor_Sample_Barcode", + gene_col: str = "Hugo_Symbol", + value_var: str = "HGVSp_Short", + samples: Optional[Sequence[Any]] = None, + genes: Optional[Sequence[Any]] = None, + fun_aggregate: Any = len, + binarize: bool = True, + fill: Any = np.nan, +) -> pd.DataFrame: + """ + Build a Gene Alteration Matrix (GAM) from a (filtered) MAF dataframe. + + R equivalent: ``maf2gam()``. Note that the R version returns a + ``samples x genes`` matrix; this port returns the transpose, + ``genes x samples``, to match the orientation used throughout the + rest of this package (e.g. the ``M`` matrices consumed by + :func:`selectsim.selectX`). + + Each cell ``(gene, sample)`` is computed by applying + ``fun_aggregate`` to the ``value_var`` values of all MAF rows with + that (gene, sample) combination. Gene/sample combinations that do + not occur anywhere in ``maf`` for a gene (or sample) that *does* + occur elsewhere are left as ``NaN`` -- mirroring R's ``tapply()``, + for which missing cells stay ``NA`` even after the ``> 0`` + binarization comparison (``NA > 0`` is ``NA``, not ``FALSE``, in R). + Callers that want a fully dense 0/1 matrix should call + ``.fillna(0)`` on the result (this is what the reference + ``data_processing`` pipeline does downstream, even though it is not + spelled out explicitly in ``maf2gam()`` itself). + + Parameters + ---------- + maf : pd.DataFrame + A MAF dataframe. + sample_col : str, optional + Column name for sample IDs. Default ``"Tumor_Sample_Barcode"``. + gene_col : str, optional + Column name for gene symbols. Default ``"Hugo_Symbol"``. + value_var : str, optional + Column used as the value to aggregate. Default ``"HGVSp_Short"``. + samples : Sequence[Any], optional + Sample IDs to include in the output (columns). ``None`` (default) + keeps exactly the samples present in ``maf``. Samples requested + here but absent from ``maf`` are added as columns filled with + ``fill``; samples present in ``maf`` but not in this list are + dropped. + genes : Sequence[Any], optional + Gene names to include in the output (rows), with the same + semantics as ``samples``. + fun_aggregate : callable, optional + Aggregation function applied per (gene, sample) cell. Default + ``len`` (mutation count). + binarize : bool, optional + If True (default), convert aggregated values to a binary + presence/absence indicator: ``> 0`` for numeric aggregates, or + ``!= ""`` for string aggregates -- while leaving missing + (``NaN``) cells untouched. + fill : Any, optional + Value used for genes/samples requested via ``genes``/``samples`` + that are absent from ``maf``. Default ``NaN`` (matching R). + + Returns + ------- + pd.DataFrame + A ``genes x samples`` dataframe. + + Examples + -------- + >>> import pandas as pd + >>> maf = pd.DataFrame({ + ... "Tumor_Sample_Barcode": ["s1", "s1", "s2"], + ... "Hugo_Symbol": ["TP53", "KRAS", "TP53"], + ... "HGVSp_Short": ["p.R175H", "p.G12C", "p.R273H"], + ... }) + >>> gam = maf_to_gam(maf, fill=0) + >>> gam.loc["TP53", "s1"] + True + """ + grouped = maf.groupby([gene_col, sample_col])[value_var].agg(fun_aggregate) + gam = grouped.unstack(sample_col) + gam.index.name = gene_col + gam.columns.name = sample_col + + if binarize and gam.size > 0: + flat = gam.to_numpy().ravel() + non_null = [v for v in flat if not (isinstance(v, float) and pd.isna(v)) and v is not None] + is_string_valued = len(non_null) > 0 and all(isinstance(v, str) for v in non_null) + if is_string_valued: + comparison = (gam != "").to_numpy() + else: + comparison = (gam.apply(pd.to_numeric, errors="coerce") > 0).to_numpy() + is_missing = gam.isna().to_numpy() + binarized = np.where(is_missing, np.nan, comparison.astype(float)) + gam = pd.DataFrame(binarized, index=gam.index, columns=gam.columns) + + if genes is not None: + genes = _unique_preserve_order(genes) + gam = gam.reindex(index=genes, fill_value=fill) + if samples is not None: + samples = _unique_preserve_order(samples) + gam = gam.reindex(columns=samples, fill_value=fill) + + return gam diff --git a/selectsim/io.py b/selectsim/io.py new file mode 100644 index 0000000..74b5f85 --- /dev/null +++ b/selectsim/io.py @@ -0,0 +1,155 @@ +""" +I/O utilities for SelectSim. + +This module provides: + +- Thin, documented wrappers around the package's chosen tabular storage + format (Apache Parquet, via pandas/pyarrow) — used mainly so that all + parquet reads/writes in the codebase go through one consistent code + path. +- Helpers for creating and opening on-disk Zarr stores, used by + ``selectsim.null_model.null_model_parallel`` (``store="zarr"``) to + persist large batches of null-model simulations without holding them + all in memory at once. + +``zarr`` (and its ``numcodecs`` dependency) is imported lazily inside the +functions that need it, so it remains an optional ``[storage]`` extra +rather than a hard dependency of the package. +""" + +from typing import Optional, Tuple + +import pandas as pd + + +def read_parquet(path: str) -> pd.DataFrame: + """ + Read a DataFrame from a Parquet file. + + Thin wrapper around :func:`pandas.read_parquet` using the ``pyarrow`` + engine, kept here so the package has a single, documented entry + point for reading its chosen tabular storage format. + + Parameters + ---------- + path : str + Path to the Parquet file. + + Returns + ------- + pd.DataFrame + The loaded DataFrame. + + Examples + -------- + >>> df = read_parquet("genes.parquet") # doctest: +SKIP + """ + return pd.read_parquet(path, engine="pyarrow") + + +def write_parquet(df: pd.DataFrame, path: str, index: bool = True) -> None: + """ + Write a DataFrame to a Parquet file. + + Thin wrapper around :meth:`pandas.DataFrame.to_parquet` using the + ``pyarrow`` engine. + + Parameters + ---------- + df : pd.DataFrame + DataFrame to write. + path : str + Destination path for the Parquet file. + index : bool, optional + Whether to write the DataFrame index as a column. Default True. + + Examples + -------- + >>> write_parquet(df, "genes.parquet") # doctest: +SKIP + """ + df.to_parquet(path, engine="pyarrow", index=index) + + +def create_zarr_null_store( + path: str, + n_permut: int, + n_genes: int, + n_samples: int, + dtype: str = "float64", + chunk_permut: int = 1, +): + """ + Create a new on-disk Zarr array sized to hold null-model simulations. + + Creates a 3D array of shape ``(n_permut, n_genes, n_samples)``, + chunked along the permutation axis (so each simulation's + genes x samples slice can be written/read independently), and + blosc-compressed. + + Parameters + ---------- + path : str + Filesystem path (directory) at which to create the Zarr store. + n_permut : int + Number of permutations (size of the leading axis). + n_genes : int + Number of genes (size of the second axis). + n_samples : int + Number of samples (size of the third axis). + dtype : str, optional + Data type of the stored array. Default "float64". + chunk_permut : int, optional + Chunk size along the permutation axis. Default 1 (one chunk per + simulation). + + Returns + ------- + zarr.Array + The newly created (opened, writable) Zarr array. + """ + import zarr + + compressor = _default_blosc_compressor() + + return zarr.open( + path, + mode="w", + shape=(n_permut, n_genes, n_samples), + chunks=(chunk_permut, n_genes, n_samples), + dtype=dtype, + compressor=compressor, + ) + + +def open_zarr_store(path: str, mode: str = "r"): + """ + Open an existing (or new) on-disk Zarr store. + + Lazily imports ``zarr`` so it remains an optional ``[storage]`` + dependency of the package. + + Parameters + ---------- + path : str + Filesystem path of the Zarr store. + mode : str, optional + Zarr open mode (e.g. "r", "r+", "a", "w"). Default "r". + + Returns + ------- + zarr.Array or zarr.Group + The opened Zarr object. + """ + import zarr + + return zarr.open(path, mode=mode) + + +def _default_blosc_compressor(): + """Build the default blosc compressor used for null-model Zarr stores.""" + try: + from numcodecs import Blosc + + return Blosc(cname="zstd", clevel=5, shuffle=Blosc.SHUFFLE) + except ImportError: # pragma: no cover - numcodecs ships with zarr + return "default" diff --git a/selectsim/null_model.py b/selectsim/null_model.py new file mode 100644 index 0000000..a6ad904 --- /dev/null +++ b/selectsim/null_model.py @@ -0,0 +1,384 @@ +""" +Null model generation functions for SelectSim. + +This module contains functions for generating null model simulations +that preserve gene mutation frequency and sample TMB distribution. +""" + +from typing import Dict, Any, List, Optional, Union, TYPE_CHECKING +import os +import numpy as np +import pandas as pd +from numpy.typing import NDArray +from joblib import Parallel, delayed +from tqdm import tqdm + +from selectsim.alteration_landscape import AlterationLandscape +from selectsim.io import create_zarr_null_store + +if TYPE_CHECKING: + # zarr is an optional dependency (the "storage" extra); only imported + # for real, lazily, inside null_model_parallel when store="zarr". This + # guarded import exists solely so static type checkers and Sphinx's + # autodoc-typehints can resolve the "zarr.Array" forward reference below + # without making zarr a hard runtime dependency of this module. + import zarr + + +# Number of permutations processed per batch when store="zarr". Bounds peak +# memory to O(batch size) instead of O(n_permut) while streaming results to +# disk; also used as the zarr store's chunk_permut so each batch write is +# one chunk operation instead of being split into batch_size tiny ones. +_ZARR_STREAM_BATCH_SIZE = 200 + + +def _simulation_step( + template: NDArray[np.float64] +) -> NDArray[np.float64]: + """ + Perform a single simulation step. + + Generates random residuals by subtracting uniform random numbers + from the template (expected probability) matrix. + + Parameters + ---------- + template : NDArray + Expected probability matrix S. + + Returns + ------- + NDArray + Residual matrix (S - random). + """ + n_values = template.shape[0] * template.shape[1] + r = np.random.uniform(0, 1, n_values).reshape(template.shape) + residuals = template - r + return residuals + + +def _simulation_fixed_ones( + al: AlterationLandscape, + temp_mat: Dict[str, NDArray[np.float64]] +) -> NDArray[np.float64]: + """ + Perform a single null model simulation preserving gene frequencies. + + Algorithm: + 1. For each mutation type, generate residuals = S - random(0,1) + 2. For multiple types, take element-wise max of residuals + 3. Rank each row's residuals descending + 4. Select top-k columns per row (k = observed gene frequency) + 5. Return binary matrix + + Parameters + ---------- + al : AlterationLandscape + AlterationLandscape object. + temp_mat : Dict[str, NDArray] + Template matrices per mutation type. + + Returns + ------- + NDArray + Simulated binary matrix (genes x samples). + """ + n_genes = al.am['full'].shape[0] + n_samples = al.am['full'].shape[1] + + # Get mutation type names (excluding 'full') + gam_names = [name for name in al.am.keys() if name != 'full'] + + # Generate residuals for each mutation type + residuals_list = [] + for name in gam_names: + if name in temp_mat and temp_mat[name] is not None: + S = temp_mat[name] + residuals = _simulation_step(S) + residuals_list.append(residuals) + + if len(residuals_list) == 0: + return np.zeros((n_genes, n_samples)) + + # Combine residuals across mutation types + if len(residuals_list) > 1: + # Take element-wise max (pmax in R) + residual_mtx = np.maximum.reduce(residuals_list) + else: + residual_mtx = residuals_list[0] + + # Get observed gene frequencies + gene_freq = np.sum(al.am['full'].values, axis=1).astype(int) + + # Vectorized top-k-per-row selection (k = gene_freq per row), via a + # double-argsort rank trick: rank[i, j] = rank (0 = highest) of column + # j within row i's descending residual order. Keeping columns whose + # rank is below that gene's frequency is equivalent to the previous + # per-row "sort then threshold-compare" loop, but vectorized across + # all genes at once (no Python loop over n_genes). Rows with + # gene_freq[i] <= 0 come out all-zero for free, since no rank is < 0. + order = np.argsort(-residual_mtx, axis=1) + ranks = np.argsort(order, axis=1) + exp = (ranks < gene_freq[:, np.newaxis]).astype(np.float64) + + return exp + + +def null_model_parallel( + al: AlterationLandscape, + temp_mat: Dict[str, NDArray[np.float64]], + W: pd.DataFrame, + n_cores: int = 1, + n_permut: int = 1000, + seed: int = 42, + verbose: bool = True, + store: str = "memory", + store_path: Optional[str] = None, +) -> Union[List[NDArray[np.float64]], "zarr.Array"]: + """ + Generate null model simulations using rejection sampling. + + Generates n_permut random matrices that preserve the observed gene + mutation frequencies and sample TMB distribution. + + Parameters + ---------- + al : AlterationLandscape + AlterationLandscape object. + temp_mat : Dict[str, NDArray] + Template matrices from template_obj_gen. + W : pd.DataFrame + Weight matrix (not used in simulation, but passed for API consistency). + n_cores : int, optional + Number of parallel workers. Default 1. + n_permut : int, optional + Number of simulations. Default 1000. + seed : int, optional + Random seed for reproducibility. Default 42. + verbose : bool, optional + Whether to show progress bar. Default True. + store : str, optional + Where to materialize results: "memory" (default, unchanged + existing behavior — returns a List[NDArray] held entirely in + RAM) or "zarr" (streams permutations in bounded-size batches + directly into an on-disk, chunked, blosc-compressed 3D zarr + array at ``store_path`` as they're computed, and returns the + opened zarr.Array — at no point are all ``n_permut`` + permutations held in memory simultaneously, only one batch of + up to ``_ZARR_STREAM_BATCH_SIZE`` at a time). ``zarr`` is + imported lazily only when this store is requested, so it + remains an optional dependency. Default "memory". + store_path : str, optional + Filesystem path for the on-disk zarr store. Required when + ``store="zarr"``. Ignored when ``store="memory"``. + + Returns + ------- + List[NDArray] or zarr.Array + List of n_permut simulated binary matrices (store="memory"), or + an opened zarr.Array of shape (n_permut, n_genes, n_samples) + (store="zarr") that supports len() and integer indexing like a + list. + + Examples + -------- + >>> al = AlterationLandscape(am_data) + >>> templates = template_obj_gen(al) + >>> null = null_model_parallel(al, templates['temp_mat'], W, n_permut=100) + >>> len(null) + 100 + """ + if store not in ("memory", "zarr"): + raise ValueError(f"Unknown store {store!r}; expected 'memory' or 'zarr'.") + if store == "zarr" and store_path is None: + raise ValueError("store_path must be provided when store='zarr'.") + + def _numpy_batch(indices) -> List[NDArray[np.float64]]: + """Compute one batch of simulated matrices for the given permutation indices.""" + def _single_simulation(i): + # Different random state per permutation index. + np.random.seed(seed + i) + return _simulation_fixed_ones(al, temp_mat) + + runner = tqdm(indices, desc="Generating null model") if verbose else indices + # n_cores < 0 follows joblib's own convention ("-1" = all cores, + # "-2" = all but one, etc.) -- only n_cores == 1 (or 0) means serial. + if n_cores > 1 or n_cores < 0: + return Parallel(n_jobs=n_cores)(delayed(_single_simulation)(i) for i in runner) + return [_single_simulation(i) for i in runner] + + if store == "memory": + np.random.seed(seed) + results = _numpy_batch(range(n_permut)) + return list(results) + + # store == "zarr": stream in bounded-size batches so peak memory stays + # O(batch_size) instead of O(n_permut) -- each batch is written to disk + # and discarded before the next one is computed. + n_genes = al.am['full'].shape[0] + n_samples = al.am['full'].shape[1] + # Chunk along the permutation axis to match the write-batch size below, + # not the create_zarr_null_store default of 1: each batch is written in + # a single z[start:end, :, :] = ... assignment, and a chunk size of 1 + # would force zarr to split every such assignment into up to + # _ZARR_STREAM_BATCH_SIZE separate per-chunk compress/write calls + # instead of one, which dominated wall time for zarr-backed runs. + z = create_zarr_null_store( + store_path, n_permut, n_genes, n_samples, + chunk_permut=min(n_permut, _ZARR_STREAM_BATCH_SIZE), + ) + + np.random.seed(seed) + batch_size = min(n_permut, _ZARR_STREAM_BATCH_SIZE) + for start in range(0, n_permut, batch_size): + end = min(start + batch_size, n_permut) + batch = _numpy_batch(range(start, end)) + z[start:end, :, :] = np.stack([np.asarray(mat) for mat in batch]) + + return z + + +def _filter_zarr_null_store( + null: "zarr.Array", + keep_idx: NDArray[np.integer], + store_path: str, +) -> "zarr.Array": + """ + Rewrite a zarr-backed null model to retain only ``keep_idx`` permutations. + + Used by :func:`selectsim.selectsim.selectX` after outlier removal, so + that filtering a zarr store never requires materializing every + retained permutation into a Python list (which would defeat the + purpose of ``store="zarr"``). Copies one retained permutation at a + time into a freshly created zarr array at a temporary path, then + atomically replaces ``store_path`` with it -- peak memory stays + O(1 permutation) throughout, and the on-disk artifact at + ``store_path`` ends up containing exactly the retained permutations, + indexed 0..len(keep_idx)-1, so everything downstream of this call can + keep treating ``obj['null']`` as "the list/array of retained sims, + directly indexable" exactly as before. + + Parameters + ---------- + null : zarr.Array + The original (unfiltered) zarr-backed null model. + keep_idx : NDArray[int] + Indices (into ``null``) of the permutations to retain, in order. + store_path : str + Filesystem path of ``null``'s store; the filtered store replaces + it in place at this same path. + + Returns + ------- + zarr.Array + Newly opened zarr.Array at ``store_path``, of shape + ``(len(keep_idx), n_genes, n_samples)``. + """ + import shutil + import tempfile + + from selectsim.io import open_zarr_store + + n_genes, n_samples = null.shape[1], null.shape[2] + tmp_path = tempfile.mkdtemp( + prefix="null_filtered_", dir=os.path.dirname(os.path.abspath(store_path)) or "." + ) + filtered = create_zarr_null_store(tmp_path, len(keep_idx), n_genes, n_samples) + for j, i in enumerate(keep_idx): + filtered[j, :, :] = null[int(i), :, :] + + shutil.rmtree(store_path) + shutil.move(tmp_path, store_path) + + return open_zarr_store(store_path, mode="r+") + + +def retrieve_outliers( + obj: Dict[str, Any], + n_sim: int = 1000 +) -> NDArray[np.bool_]: + """ + Identify outlier simulations based on deviation from observed. + + Computes mean absolute deviation of gene and sample mutation counts + from observed values. Flags simulations in the top 10% of total + deviation as outliers. + + Parameters + ---------- + obj : Dict[str, Any] + SelectX object containing 'al' (AlterationLandscape) and 'null' + (the simulations, either a List[NDArray] as returned by + null_model_parallel(store="memory") or a zarr.Array as returned + by null_model_parallel(store="zarr") — both support len() and + integer indexing and are handled transparently here). + n_sim : int, optional + Number of simulations. Default 1000. + + Returns + ------- + NDArray[bool] + Boolean array where True indicates an outlier simulation. + + Examples + -------- + >>> outliers = retrieve_outliers(obj, n_sim=1000) + >>> np.sum(outliers) # Should be ~10% of n_sim + 100 + """ + al = obj['al'] + null = obj['null'] + + n_genes = al.am['full'].shape[0] + n_samples = al.am['full'].shape[1] + # Process at most n_sim simulations (the first n_sim of `null`), so the + # parameter actually has an effect -- e.g. sub-sampling a large + # zarr-backed null model for a cheaper approximate check. Defaults to + # the previous "always use everything available" behavior whenever + # n_sim >= len(null), which holds for typical default usage. + actual_n_sim = min(n_sim, len(null)) + + # Observed gene and sample mutation counts + ogn = np.sum(al.am['full'].values, axis=1) # genes + osn = np.sum(al.am['full'].values, axis=0) # samples + + # Compute gene and sample counts for each simulation + gn_mut = np.zeros((n_genes, actual_n_sim)) + sn_mut = np.zeros((n_samples, actual_n_sim)) + + # Use explicit index-based access (rather than relying on + # `for sim in null`) so this works transparently whether `null` is a + # plain list of numpy arrays or a zarr.Array (both support len() and + # integer indexing); np.asarray() is a no-op for numpy arrays and + # materializes a plain ndarray for a zarr slice. + for i in range(actual_n_sim): + sim = np.asarray(null[i]) + gn_mut[:, i] = np.sum(sim, axis=1) + sn_mut[:, i] = np.sum(sim, axis=0) + + # Compute deviations from observed + rgn = gn_mut - ogn[:, np.newaxis] + rsn = sn_mut - osn[:, np.newaxis] + + # Mean absolute deviation per simulation + mean_gn = np.mean(np.abs(rgn), axis=0) + mean_sn = np.mean(np.abs(rsn), axis=0) + + # Total deviation + dev = mean_gn + mean_sn + + # Sort deviations + dev_sorted = np.sort(dev) + + # Find 90th percentile threshold. R's `dev2[round(0.90 * length(dev2))]` + # selects the round(0.90*n)-th smallest value from a 1-indexed vector; + # the 0-indexed equivalent is position round(0.90*n) - 1 (previously + # this used round(0.90*n) unadjusted, one rank too strict vs R). + idx_90 = int(round(0.90 * len(dev_sorted))) - 1 + idx_90 = min(max(idx_90, 0), len(dev_sorted) - 1) + maxcut = dev_sorted[idx_90] + + # Flag outliers (top 10%) + outliers = dev >= maxcut + + return outliers diff --git a/selectsim/plotting.py b/selectsim/plotting.py new file mode 100644 index 0000000..10c8ba9 --- /dev/null +++ b/selectsim/plotting.py @@ -0,0 +1,1085 @@ +""" +Plotting utilities for SelectSim. + +Python port of ``R/selectX_plot.R``. The R implementation is built on +``ggplot2``/``ggpubr``/``ggridges``; this module reproduces the same visual +intent using ``matplotlib`` + ``seaborn`` (the library choice made for the +Python port), since no display is assumed to be available. + +Functions +--------- +theme_publication + Matplotlib rcParams equivalent of ``theme_Publication()``. +obs_exp_scatter + Scatter of observed vs. expected (null-model) weighted co-mutation. +overlap_pair_extract + Data-extraction helper: null-model weighted-overlap distribution for a + single gene pair (not a plot). +ridge_plot_ed + Ridge ("joy") plot of the null-model background distribution for a set + of gene pairs, with observed/mean-background markers. +ridge_plot_ed_compare + Same as above but overlaying two ``selectX()`` runs for comparison. + +Notes on the object structure +------------------------------ +The Python ``selectX()`` (see ``selectsim/selectsim.py``) returns +``{'obj': obj, 'result': result_df}`` where ``obj`` is a plain ``dict`` +(rather than R's nested S3 list) with (among others) the keys: + +- ``obj['al']``: an ``AlterationLandscape`` instance; ``obj['al'].am['full']`` + is a genes x samples ``DataFrame`` whose ``.index`` gives the gene/feature + name -> row-position mapping used by the overlap matrices below. +- ``obj['wrobs_co']``: a ``list`` of length ``obj['nSim']`` of unlabeled + ``numpy`` genes x genes matrices -- the per-permutation TMB-weighted + null-model overlap matrices (the analogue of R's ``obj$wrobs.co``). +- ``obj['nSim']``: number of retained null-model permutations. + +``result_df`` is the ``result`` DataFrame (or a filtered subset of it) with +columns documented in the README / ``interaction_table``: ``SFE_1``, +``SFE_2``, ``name``, ``w_overlap``, ``w_r_overlap``, ``type`` (``'CO'`` or +``'ME'``), ``FDR``, etc. +""" + +from __future__ import annotations + +from typing import Any, Dict, List, Optional, Sequence + +import matplotlib + +matplotlib.use("Agg") # headless/non-interactive backend + +import matplotlib.pyplot as plt +from matplotlib.colors import ListedColormap +from matplotlib.figure import Figure +from matplotlib.lines import Line2D +from matplotlib.patches import Patch +import numpy as np +import pandas as pd +from scipy.stats import gaussian_kde + + +__all__ = [ + "theme_publication", + "SIG_COLORS", + "obs_exp_scatter", + "overlap_pair_extract", + "ridge_plot_ed", + "ridge_plot_ed_compare", + "oncoprint_pair", + "oncoprint", + "significance_heatmap", +] + + +# Shared color convention for interaction significance/type, used by every +# plotting function in this module that distinguishes CO (co-occurrence), +# ME (mutual exclusivity), and NS (not significant) pairs: forestgreen for +# CO, purple for ME, light grey for NS. Originally defined inline only in +# `obs_exp_scatter`; pulled out here so new plots (e.g. +# `significance_heatmap`) share one definition rather than redefining it. +SIG_COLORS: Dict[str, str] = {"CO": "forestgreen", "ME": "purple", "NS": "#EDEDED"} + + +# --------------------------------------------------------------------------- +# theme_Publication +# --------------------------------------------------------------------------- + +def theme_publication( + base_size: float = 14, + base_family: str = "sans-serif", + apply: bool = True, +) -> Dict[str, Any]: + """ + Matplotlib equivalent of the R ``theme_Publication()`` ggplot2 theme. + + R's ``theme_Publication`` is a ggplot2 ``theme()`` object, which has no + direct matplotlib equivalent (matplotlib has no "theme" object that + attaches to a single plot after the fact). The idiomatic Python + translation used here is an ``rcParams`` dictionary: a clean white + background, bold axis titles, thin black panel border, light-grey major + gridlines, no minor gridlines, and a legend without a frame -- matching + the visual intent of the R theme value-for-value: + + - ``text``/``base_family``/``base_size`` -> ``font.family``, ``font.size`` + - ``plot.title`` bold, size ``rel(1.2)``, centered -> ``axes.titleweight``, + ``axes.titlesize``, ``axes.titlelocation`` (via manual centering, since + matplotlib always centers titles by default) + - ``panel.background``/``plot.background`` blank -> white figure/axes + facecolor + - ``panel.border`` -> black 1pt axes spines (all four sides visible) + - ``axis.title`` bold, size ``rel(1)`` -> ``axes.labelweight``, + ``axes.labelsize`` + - ``axis.text`` size ``rel(0.8)`` -> ``xtick.labelsize``/``ytick.labelsize`` + - ``panel.grid.major`` colour ``#f0f0f0``, ``panel.grid.minor`` blank -> + ``grid.color``/``axes.grid`` (minor grid left off) + - ``legend.position = "right"`` (R default; ``obs_exp_scatter`` overrides + it to "top") -> ``legend.loc`` + - ``strip.background``/``strip.text`` (facet strips) have no direct + matplotlib analogue and are omitted. + + Parameters + ---------- + base_size : float, optional + Base font size in points (R default 14). + base_family : str, optional + Font family (R default "sans", mapped to matplotlib's + "sans-serif" generic family). + apply : bool, optional + If True (default), immediately applies the settings via + ``plt.rcParams.update()`` so subsequently created figures pick up + the theme -- this mirrors adding ``+ theme_Publication()`` to every + ggplot call. If False, the dict is only returned (e.g. for use as + ``with plt.rc_context(theme_publication(apply=False)): ...``). + + Returns + ------- + dict + The rcParams dictionary describing the theme (always returned, + regardless of ``apply``). + """ + rel = lambda factor: round(base_size * factor, 2) + + rc: Dict[str, Any] = { + "font.family": base_family, + "font.size": base_size, + "figure.facecolor": "white", + "axes.facecolor": "white", + "savefig.facecolor": "white", + "axes.titlesize": rel(1.2), + "axes.titleweight": "bold", + "axes.labelsize": rel(1.0), + "axes.labelweight": "bold", + "axes.edgecolor": "black", + "axes.linewidth": 1.0, + "axes.spines.top": True, + "axes.spines.right": True, + "axes.spines.left": True, + "axes.spines.bottom": True, + "xtick.labelsize": rel(0.8), + "ytick.labelsize": rel(0.8), + "xtick.color": "black", + "ytick.color": "black", + "axes.grid": True, + "axes.grid.which": "major", + "grid.color": "#f0f0f0", + "grid.linewidth": 1.0, + "legend.frameon": False, + "legend.loc": "right", + "legend.fontsize": rel(0.8), + "legend.title_fontsize": rel(0.9), + } + + if apply: + plt.rcParams.update(rc) + + return rc + + +# --------------------------------------------------------------------------- +# obs_exp_scatter +# --------------------------------------------------------------------------- + +def obs_exp_scatter( + result: pd.DataFrame, + title: str = "", + ax: Optional[plt.Axes] = None, +) -> Figure: + """ + Scatter plot of observed vs expected (null-model) weighted co-mutation. + + Python port of R's ``obs_exp_scatter(result, title)``. Each gene pair is + a point; the x-axis is the null-model (random) weighted co-mutation + (log10), the y-axis is the observed weighted co-mutation (log10). + Significant co-mutations ('CO') and mutual exclusivities ('ME') are + coloured; non-significant pairs ('NS') are light grey. A y = x reference + line is drawn. + + Parameters + ---------- + result : pd.DataFrame + Result table from ``selectX()['result']`` (or a subset of it), with + at least the columns ``w_overlap``, ``w_r_overlap``, ``type``, + ``FDR``. + title : str, optional + Plot title. + ax : matplotlib.axes.Axes, optional + Axes to draw into. A new figure/axes is created if not given. + + Returns + ------- + matplotlib.figure.Figure + The figure containing the scatter plot. + """ + required = {"w_overlap", "w_r_overlap", "type", "FDR"} + missing = required - set(result.columns) + if missing: + raise ValueError(f"result is missing required columns: {sorted(missing)}") + + df = result.copy() + df["log_overlap"] = np.log10(df["w_overlap"] + 1) + df["log_r_overlap"] = np.log10(df["w_r_overlap"] + 1) + df["cat"] = np.where(df["FDR"].astype(bool), df["type"], "NS") + + theme_publication(base_size=16, apply=True) + + if ax is None: + fig, ax = plt.subplots(figsize=(7, 7)) + else: + fig = ax.get_figure() + + for cat, sub in df.groupby("cat"): + ax.scatter( + sub["log_r_overlap"], + sub["log_overlap"], + color=SIG_COLORS.get(cat, "grey"), + label=cat, + alpha=1.0, + edgecolors="none", + s=25, + ) + + max_val = float(df["log_overlap"].max()) if len(df) else 1.0 + lims = [0, max(max_val, 1.0)] + ax.plot(lims, lims, color="black", linewidth=1, zorder=0) + + ax.set_xlabel("Random weighted co-mutation (log10)") + ax.set_ylabel("Actual weighted co-mutation(log10)") + ax.set_title(title) + ax.legend(loc="upper center", bbox_to_anchor=(0.5, 1.12), ncol=3, frameon=False) + + fig.tight_layout() + return fig + + +# --------------------------------------------------------------------------- +# significance_heatmap +# --------------------------------------------------------------------------- + +def significance_heatmap( + result: pd.DataFrame, + genes: Optional[Sequence[str]] = None, + ax: Optional[plt.Axes] = None, + title: str = "", +) -> Figure: + """ + Upper-triangular gene x gene heatmap of interaction type and strength. + + Each cell (row gene, column gene) is colored by interaction type -- + CO (forestgreen), ME (purple), or not-significant (light grey, + matching :data:`SIG_COLORS`) -- with color intensity (alpha) scaled + by ``|nES|`` (normalized effect size): stronger effects render more + saturated, and weak-but-significant pairs are floored at alpha 0.2 + so they stay visible rather than fading to invisible. Only the upper + triangle is drawn (diagonal and lower triangle left blank), since the + underlying pairwise matrix is symmetric -- the same visual idiom as + ``maftools::somaticInteractions`` / cBioPortal's co-occurrence panel. + + Parameters + ---------- + result : pd.DataFrame + Result table from ``selectX()['result']`` (or a subset of it), + with at least the columns ``SFE_1``, ``SFE_2``, ``type`` + (``'CO'`` or ``'ME'``), ``FDR``, ``nES``. + genes : sequence of str, optional + Genes to include, and their axis order (both axes share the same + order). Defaults to every gene appearing in ``result``'s + ``SFE_1``/``SFE_2`` columns, ordered by descending number of + tested pairs it appears in. Pass an explicit, frequency-ordered + list (e.g. derived from ``obj['al'].am['full']``, matching + :func:`oncoprint`'s gene ordering) to align with a companion + oncoprint panel. + ax : matplotlib.axes.Axes, optional + Axes to draw into. A new figure/axes is created if not given. + title : str, optional + Plot title. + + Returns + ------- + matplotlib.figure.Figure + + Examples + -------- + >>> fig = significance_heatmap(result["result"]) # doctest: +SKIP + """ + required = {"SFE_1", "SFE_2", "type", "FDR", "nES"} + missing = required - set(result.columns) + if missing: + raise ValueError(f"result is missing required columns: {sorted(missing)}") + + df = result.copy() + df["cat"] = np.where(df["FDR"].astype(bool), df["type"], "NS") + + if genes is None: + genes = pd.concat([df["SFE_1"], df["SFE_2"]]).value_counts().index.tolist() + else: + genes = list(genes) + present = set(df["SFE_1"]) | set(df["SFE_2"]) + missing_genes = [g for g in genes if g not in present] + if missing_genes: + raise KeyError( + f"Gene(s) not found in result's SFE_1/SFE_2 columns: {missing_genes}" + ) + + n = len(genes) + idx = {g: i for i, g in enumerate(genes)} + + # RGBA grid, initialized fully transparent -- diagonal, lower + # triangle, and any pair not tested (or involving a gene outside + # `genes`) stay unpainted. + rgba = np.zeros((n, n, 4)) + + nes_abs = df["nES"].abs() + nes_max = float(nes_abs.max()) if len(nes_abs) and nes_abs.max() > 0 else 1.0 + + for _, row in df.iterrows(): + g1, g2 = row["SFE_1"], row["SFE_2"] + if g1 not in idx or g2 not in idx or g1 == g2: + continue + r, c = sorted((idx[g1], idx[g2])) # upper triangle only (r < c) + + cat = row["cat"] + alpha = 1.0 if cat == "NS" else 0.2 + 0.8 * (abs(row["nES"]) / nes_max) + rgba[r, c] = matplotlib.colors.to_rgba(SIG_COLORS.get(cat, "grey"), alpha=alpha) + + theme_publication(apply=True) + if ax is None: + fig, ax = plt.subplots(figsize=(max(6.0, 0.4 * n), max(6.0, 0.4 * n))) + else: + fig = ax.get_figure() + + ax.imshow(rgba, interpolation="nearest") + ax.set_xticks(np.arange(n)) + ax.set_xticklabels(genes, rotation=90, style="italic") + ax.set_yticks(np.arange(n)) + ax.set_yticklabels(genes, style="italic") + ax.set_xlim(-0.5, n - 0.5) + ax.set_ylim(n - 0.5, -0.5) + ax.grid(False) + for spine in ax.spines.values(): + spine.set_visible(False) + + legend_handles = [ + Patch(facecolor=SIG_COLORS["CO"], label="Co-occurrence (CO)"), + Patch(facecolor=SIG_COLORS["ME"], label="Mutual exclusivity (ME)"), + Patch(facecolor=SIG_COLORS["NS"], label="Not significant"), + ] + ax.legend( + handles=legend_handles, title="Type (intensity ∝ |nES|)", + loc="upper left", bbox_to_anchor=(1.02, 1.0), frameon=False, + ) + + ax.set_title(title) + fig.tight_layout() + return fig + + +# --------------------------------------------------------------------------- +# overlap_pair_extract +# --------------------------------------------------------------------------- + +def overlap_pair_extract(gene1: str, gene2: str, obj: Dict[str, Any]) -> np.ndarray: + """ + Extract the null-model weighted-overlap distribution for a gene pair. + + Python port of R's ``overlap_pair_extract(gene1, gene2, obj)``. This is a + data-extraction helper (not a plot), used internally by the ridge-plot + functions. + + Parameters + ---------- + gene1, gene2 : str + Names of the two genes/alterations (must be present in + ``obj['al'].am['full'].index``). + obj : dict + The ``obj`` sub-dictionary returned by ``selectX()`` (i.e. + ``selectX(...)['obj']``), containing ``'al'`` and ``'wrobs_co'``. + + Returns + ------- + numpy.ndarray + Vector of length ``obj['nSim']`` with the null-model TMB-weighted + overlap values for the pair, one per retained permutation. + """ + features = obj["al"].am["full"].index + try: + idx1 = features.get_loc(gene1) + idx2 = features.get_loc(gene2) + except KeyError as exc: + raise KeyError( + f"Gene(s) not found in obj['al'].am['full'].index: {exc}" + ) from exc + + wrobs_co: List[np.ndarray] = obj["wrobs_co"] + return np.array([mat[idx1, idx2] for mat in wrobs_co], dtype=float) + + +# --------------------------------------------------------------------------- +# Ridge-plot helpers +# --------------------------------------------------------------------------- + +def _kde_density(values: np.ndarray, xs: np.ndarray) -> np.ndarray: + """Gaussian KDE of ``values`` evaluated at ``xs``, robust to zero-variance.""" + if len(values) < 2 or np.std(values) == 0: + density = np.zeros_like(xs) + # Degenerate distribution: draw a thin spike at the (constant) value. + if len(values) > 0: + idx = int(np.argmin(np.abs(xs - values[0]))) + density[idx] = 1.0 + return density + kde = gaussian_kde(values) + return kde(xs) + + +def _ridge_row_geometry( + dist: np.ndarray, xs: np.ndarray, ridge_scale: float +) -> tuple: + """ + Compute the scaled KDE density curve, marker-segment height, and + null-model mean for one ridge row. + + Shared by ``ridge_plot_ed`` and ``ridge_plot_ed_compare`` so the + marker-height logic only lives in one place: both the "actual + overlap" and "mean background" segments are drawn to the ridge's + *peak* density height (``seg_height``), not the local KDE density at + their own x-position -- the latter is near-zero (and the marker + effectively invisible) whenever the observed value falls in the tail + of the null distribution, which is precisely the case for the most + significant/interesting pairs. + + Parameters + ---------- + dist : ndarray + Null-model distribution values for this row (may be empty). + xs : ndarray + Shared x-grid the KDE is evaluated on. + ridge_scale : float + Height (row units) the ridge's peak density is scaled to. + + Returns + ------- + density : ndarray + KDE density curve evaluated at ``xs``, scaled so its peak is + ``ridge_scale`` (all-zero if ``dist`` has no usable data). + seg_height : float + Height marker segments should be drawn to. + mean_bg : float + Mean of ``dist`` (0.0 if ``dist`` is empty). + """ + density = _kde_density(dist, xs) + peak = density.max() + if peak > 0: + density = density / peak * ridge_scale + seg_height = ridge_scale if peak > 0 else 0.0 + mean_bg = float(np.mean(dist)) if len(dist) else 0.0 + return density, seg_height, mean_bg + + +def _row_label(row) -> str: + if "name" in row.index and pd.notna(row["name"]): + return str(row["name"]) + return f"{row['SFE_1']} - {row['SFE_2']}" + + +def ridge_plot_ed( + result_df: pd.DataFrame, + obj: Dict[str, Any], + ridge_scale: float = 0.9, + figsize: Optional[Sequence[float]] = None, +) -> Figure: + """ + Ridge plot of the null-model background distribution for gene pairs. + + Python port of R's ``ridge_plot_ed(result_df, obj)``. For each row + (gene pair) in ``result_df``, plots the null-model TMB-weighted-overlap + distribution (over ``obj['nSim']`` permutations) as one horizontal + "ridge" (a joyplot / overlapping-KDE row), with a red vertical segment + marking the observed weighted overlap and a blue vertical segment + marking the null-model mean background overlap. The y-axis tick labels + are coloured by interaction type: purple for 'ME', forestgreen for 'CO' + (matching the R implementation's ``a <- ifelse(type == "ME", "purple", + "forestgreen")``). + + Implementation note: this is the standard "joyplot"/ridgeline technique + -- per-row Gaussian KDEs evaluated on a shared x-grid and stacked with a + constant y-offset -- rather than ``ggridges::geom_density_ridges``, + since matplotlib/seaborn has no ridge-plot geom built in. + + Parameters + ---------- + result_df : pd.DataFrame + Subset of ``selectX()['result']`` for the pairs to display. Must + contain ``SFE_1``, ``SFE_2``, ``w_overlap``, ``type`` (``name`` is + used for row labels if present). + obj : dict + ``selectX()['obj']``. + ridge_scale : float, optional + Height (in row units) that a ridge's peak density is scaled to. + Values close to 1 make adjacent ridges touch/overlap slightly, as + in a traditional joyplot. Default 0.9. + figsize : sequence of float, optional + Figure size; defaults to a size that scales with the number of rows. + + Returns + ------- + matplotlib.figure.Figure + """ + if len(result_df) == 0: + raise ValueError("result_df is empty; nothing to plot") + + required = {"SFE_1", "SFE_2", "w_overlap", "type"} + missing = required - set(result_df.columns) + if missing: + raise ValueError(f"result_df is missing required columns: {sorted(missing)}") + + result_df = result_df.reset_index(drop=True) + n = len(result_df) + + dists = [ + overlap_pair_extract(row["SFE_1"], row["SFE_2"], obj) + for _, row in result_df.iterrows() + ] + labels = [_row_label(row) for _, row in result_df.iterrows()] + label_colors = ["purple" if t == "ME" else "forestgreen" for t in result_df["type"]] + + # Seed x_max with the observed w_overlap values and a literal fallback + # (rather than deriving it solely from `dists`) so this doesn't raise + # on max() of an empty sequence when every row's null distribution is + # empty (e.g. zero retained permutations). + x_max = max( + [float(np.max(d)) for d in dists if len(d)] + + [float(result_df["w_overlap"].max()), 1.0] + ) + xs = np.linspace(0, x_max * 1.05, 300) + + theme_publication(apply=True) + if figsize is None: + figsize = (8, max(2.5, 0.9 * n + 1.5)) + fig, ax = plt.subplots(figsize=figsize) + + actual_line = mean_line = None + for i, (dist, label) in enumerate(zip(dists, labels)): + y0 = i + density, seg_height, mean_bg = _ridge_row_geometry(dist, xs, ridge_scale) + ax.fill_between( + xs, y0, y0 + density, + color="lightgrey", edgecolor="black", linewidth=0.8, zorder=n - i, + ) + + obs_val = float(result_df.loc[i, "w_overlap"]) + seg_a = ax.vlines(obs_val, y0, y0 + seg_height, color="red", linewidth=1.5, zorder=n + 1) + seg_m = ax.vlines(mean_bg, y0, y0 + seg_height, color="blue", linewidth=1.5, zorder=n + 1) + if actual_line is None: + actual_line = seg_a + mean_line = seg_m + + ax.set_yticks(np.arange(n)) + ax.set_yticklabels(labels) + for tick, color in zip(ax.get_yticklabels(), label_colors): + tick.set_color(color) + + ax.set_xlabel("Weighted overlap") + ax.set_ylabel("") + ax.set_ylim(-0.2, n - 1 + ridge_scale + 0.2) + ax.grid(axis="x", color="#f0f0f0") + ax.grid(axis="y", visible=False) + + legend_handles = [ + Line2D([0], [0], color="red", lw=1.5, label="Actual overlap"), + Line2D([0], [0], color="blue", lw=1.5, label="Mean Background overlap"), + ] + ax.legend(handles=legend_handles, title="Legend", loc="upper right", frameon=False) + + fig.tight_layout() + return fig + + +def ridge_plot_ed_compare( + result_df: pd.DataFrame, + obj1: Dict[str, Any], + obj2: Dict[str, Any], + name1: str, + name2: str, + ridge_scale: float = 0.85, + figsize: Optional[Sequence[float]] = None, +) -> Figure: + """ + Ridge plot comparing null-model distributions from two ``selectX`` runs. + + Python port of R's ``ridge_plot_ed_compare(result_df, obj1, obj2, name1, + name2)``. For each gene pair (row) in ``result_df``, overlays the + null-model weighted-overlap distributions from ``obj1`` and ``obj2`` on + the same ridge row (colour-coded per dataset, ``"#E69F00"`` for + ``name1`` and ``"#56B4E9"`` for ``name2``), with a solid vertical segment + marking each dataset's observed overlap and a dashed vertical segment + marking each dataset's null-model mean background, both in the + dataset's colour. Y-axis tick labels are coloured by interaction type + (purple 'ME', forestgreen 'CO'), as in ``ridge_plot_ed``. + + Note: this diverges slightly from the R source's line-colour/legend + wiring, which swaps ``name1``/``name2`` when building the colour legend + (``density_lines$name <- c(rep(name2, ...), rep(name1, ...))``) -- a + quirk of the original implementation. Here each dataset's markers use + that dataset's own ridge colour, which is the clearer and (we believe) + intended visual pairing. + + Parameters + ---------- + result_df : pd.DataFrame + Gene pairs to display, with columns ``SFE_1``, ``SFE_2``, ``type``, + ``dataset1_w_overlap``, ``dataset2_w_overlap`` (``name`` optional, + used for row labels). + obj1, obj2 : dict + ``selectX()['obj']`` for datasets 1 and 2. + name1, name2 : str + Display labels for datasets 1 and 2. + ridge_scale : float, optional + Height (row units) ridges are scaled to. Default 0.85. + figsize : sequence of float, optional + Figure size; defaults to a size that scales with the number of rows. + + Returns + ------- + matplotlib.figure.Figure + """ + if len(result_df) == 0: + raise ValueError("result_df is empty; nothing to plot") + + required = {"SFE_1", "SFE_2", "type", "dataset1_w_overlap", "dataset2_w_overlap"} + missing = required - set(result_df.columns) + if missing: + raise ValueError(f"result_df is missing required columns: {sorted(missing)}") + + result_df = result_df.reset_index(drop=True) + n = len(result_df) + + dists1 = [overlap_pair_extract(r["SFE_1"], r["SFE_2"], obj1) for _, r in result_df.iterrows()] + dists2 = [overlap_pair_extract(r["SFE_1"], r["SFE_2"], obj2) for _, r in result_df.iterrows()] + labels = [_row_label(row) for _, row in result_df.iterrows()] + label_colors = ["purple" if t == "ME" else "forestgreen" for t in result_df["type"]] + + all_max = [float(np.max(d)) for d in dists1 + dists2 if len(d)] + x_max = max(all_max + [ + float(result_df["dataset1_w_overlap"].max()), + float(result_df["dataset2_w_overlap"].max()), + 1.0, + ]) + xs = np.linspace(0, x_max * 1.05, 300) + + theme_publication(apply=True) + if figsize is None: + figsize = (8, max(2.5, 1.0 * n + 1.5)) + fig, ax = plt.subplots(figsize=figsize) + + dataset_colors = {name1: "#E69F00", name2: "#56B4E9"} + datasets = [ + (name1, dists1, "dataset1_w_overlap"), + (name2, dists2, "dataset2_w_overlap"), + ] + + for i in range(n): + y0 = i + for dname, dists, obs_col in datasets: + color = dataset_colors[dname] + density, seg_height, mean_bg = _ridge_row_geometry(dists[i], xs, ridge_scale) + ax.fill_between( + xs, y0, y0 + density, + color=color, alpha=0.45, edgecolor=color, linewidth=0.8, + zorder=n - i, + ) + + obs_val = float(result_df.loc[i, obs_col]) + ax.vlines(obs_val, y0, y0 + seg_height, color=color, linewidth=1.5, + linestyle="solid", zorder=n + 1) + ax.vlines(mean_bg, y0, y0 + seg_height, color=color, linewidth=1.5, + linestyle="dashed", zorder=n + 1) + + ax.set_yticks(np.arange(n)) + ax.set_yticklabels(labels) + for tick, color in zip(ax.get_yticklabels(), label_colors): + tick.set_color(color) + + ax.set_xlabel("Weighted overlap") + ax.set_ylabel("") + ax.set_ylim(-0.2, n - 1 + ridge_scale + 0.2) + ax.grid(axis="x", color="#f0f0f0") + ax.grid(axis="y", visible=False) + + legend_handles = [ + Patch(facecolor=dataset_colors[name1], alpha=0.45, label=name1), + Patch(facecolor=dataset_colors[name2], alpha=0.45, label=name2), + Line2D([0], [0], color="black", lw=1.5, linestyle="solid", label="Actual overlap"), + Line2D([0], [0], color="black", lw=1.5, linestyle="dashed", label="Mean Background overlap"), + ] + ax.legend(handles=legend_handles, title="Dataset / Legend", loc="upper right", frameon=False) + + fig.tight_layout() + return fig + + +# --------------------------------------------------------------------------- +# oncoprint_pair +# --------------------------------------------------------------------------- + +def oncoprint_pair( + gene1: str, + gene2: str, + obj: Dict[str, Any], + simulation_index: int = 0, + figsize: Optional[Sequence[float]] = None, +) -> Figure: + """ + Two-gene "mini oncoprint" with a per-sample TMB bar, observed vs. one + null-model simulation. + + Not present in the R package -- this is a new visualization illustrating + *why* the TMB-weighted overlap statistic differs from the raw overlap + count for a specific gene pair, by showing, side by side: the real + (observed) co-mutation pattern for ``gene1``/``gene2`` across samples, + and the pattern from one retained null-model permutation + (``obj['null'][simulation_index]``) -- each with a per-sample TMB bar on + top, and the co-mutated ("both genes altered") samples highlighted, with + the co-mutation count labelled inside the highlight. + + Both panels are drawn with the same sample-ordering rule, computed + independently per panel: samples are grouped into (1) co-mutated (both + genes altered -- highlighted, leftmost), (2) the higher-frequency gene + only ("primary", contiguous with group 1), (3) neither gene altered, + (4) the lower-frequency gene only ("secondary", rightmost); each group + internally sorted by descending TMB. Which gene is "primary" is + determined once from the observed data (the null model preserves each + gene's observed mutation frequency exactly, so this is the same gene in + both panels). + + Parameters + ---------- + gene1, gene2 : str + Gene/feature names. Must be present in ``obj['al'].am['full'].index``. + obj : dict + ``selectX()['obj']``. Uses ``obj['al']`` (for observed calls and + TMB) and ``obj['null']`` (retained null-model simulations). + simulation_index : int, optional + Index into ``obj['null']`` selecting which retained permutation to + show as the "Simulation" panel. Default 0. + figsize : sequence of float, optional + Figure size; defaults to a fixed size that fits both panels. + + Returns + ------- + matplotlib.figure.Figure + + Examples + -------- + >>> fig = oncoprint_pair("KRAS", "STK11", result["obj"]) # doctest: +SKIP + """ + al = obj["al"] + am_full = al.am["full"] + for g in (gene1, gene2): + if g not in am_full.index: + raise KeyError(f"{g!r} not found in obj['al'].am['full'].index") + if not obj.get("null"): + raise ValueError("obj['null'] has no retained simulations to compare against") + + samples = am_full.columns.tolist() + n_samples = len(samples) + idx1 = am_full.index.get_loc(gene1) + idx2 = am_full.index.get_loc(gene2) + + obs1 = am_full.loc[gene1].to_numpy() > 0 + obs2 = am_full.loc[gene2].to_numpy() > 0 + + sim_mat = np.asarray(obj["null"][simulation_index]) + sim1 = sim_mat[idx1, :] > 0 + sim2 = sim_mat[idx2, :] > 0 + + tmb = ( + al.tmb["total"].set_index("sample")["mutation"] + .reindex(samples).to_numpy(dtype=float) + ) + + raw_obs = int(np.sum(obs1 & obs2)) + raw_sim = int(np.sum(sim1 & sim2)) + + # Which gene forms the contiguous block: the higher-frequency one. The + # null model preserves each gene's observed frequency exactly, so this + # choice is identical for the observed and simulated panels. + primary_is_1 = obs1.sum() >= obs2.sum() + + def _sample_order(mut1: np.ndarray, mut2: np.ndarray): + # Grouped by mutation status only -- no TMB (or any other) sort + # within a group; each group keeps samples in their original, + # natural column order. + primary, other = (mut1, mut2) if primary_is_1 else (mut2, mut1) + groups = [ + primary & other, # both altered (highlighted) + primary & ~other, # primary-only (contiguous with group 1) + ~primary & ~other, # neither + ~primary & other, # secondary-only (rightmost) + ] + order = np.concatenate([np.flatnonzero(g) for g in groups]) + return order, int(groups[0].sum()) + + obs_order, obs_n_highlight = _sample_order(obs1, obs2) + sim_order, sim_n_highlight = _sample_order(sim1, sim2) + + theme_publication(apply=True) + if figsize is None: + figsize = (10, 5) + fig = plt.figure(figsize=figsize) + outer = fig.add_gridspec(2, 1, hspace=0.55) + + tmb_max = float(np.max(tmb)) * 1.05 if n_samples else 1.0 + # Unmutated cells are transparent (not opaque white) so the thin sample + # track line drawn underneath shows through wherever a gene isn't + # altered, matching the standard oncoprint convention -- only the + # mutated (green) cells are opaque and cover the line. + binary_cmap = ListedColormap([(1.0, 1.0, 1.0, 0.0), "forestgreen"]) + + def _draw_panel(cell, order, mut1, mut2, n_highlight, raw, title, highlight_color): + inner = cell.subgridspec(3, 1, height_ratios=[3, 1, 1], hspace=0.15) + ax_tmb = fig.add_subplot(inner[0]) + ax_g1 = fig.add_subplot(inner[1], sharex=ax_tmb) + ax_g2 = fig.add_subplot(inner[2], sharex=ax_tmb) + + x = np.arange(n_samples) + if n_highlight > 0: + # Translucent fill on the TMB row only -- on the gene rows this + # would tint the opaque green/white track a muddy olive color, + # so those get a clean outline instead (below). + ax_tmb.axvspan(-0.5, n_highlight - 0.5, color=highlight_color, alpha=0.35, zorder=1) + # A single filled step polygon (not one bar-patch per sample) -- + # hundreds of adjacent bar() patches anti-alias into visible seams + # at this sample count/DPI; fill_between renders as one gapless path. + ax_tmb.fill_between(x, 0, tmb[order], step="mid", color="black", linewidth=0, zorder=2) + ax_tmb.set_xlim(-0.5, n_samples - 0.5) + ax_tmb.set_ylim(0, tmb_max) + ax_tmb.set_ylabel("TMB", fontweight="normal") + ax_tmb.set_title(title, loc="left", fontweight="bold") + ax_tmb.set_xticks([]) + ax_tmb.spines["top"].set_visible(False) + ax_tmb.spines["right"].set_visible(False) + # The bottom spine (TMB=0 axis line) spans the full plot width and + # sits directly above the gene-1 track; wherever that sample isn't + # mutated (white background), the spine reads as a stray black + # horizontal line with nothing to visually justify it -- drop it. + ax_tmb.spines["bottom"].set_visible(False) + + for ax, mut, label in ((ax_g1, mut1[order], gene1), (ax_g2, mut2[order], gene2)): + # Thin sample-track baseline, drawn first (so the imshow below + # sits on top of it) -- visible only where the gene isn't + # altered, since mutated cells are opaque green. + ax.axhline(0.5, color="black", linewidth=0.8, zorder=1) + # imshow of a 1-row binary track renders as a single raster, + # pixel-exact regardless of sample count -- avoids the same + # seam/gap artifacts a per-sample bar() would have here too. + ax.imshow( + mut.astype(float)[np.newaxis, :], + aspect="auto", cmap=binary_cmap, vmin=0, vmax=1, + extent=(-0.5, n_samples - 0.5, 0, 1), interpolation="nearest", + zorder=2, + ) + ax.set_ylim(0, 1) + ax.set_yticks([0.5]) + ax.set_yticklabels([label], style="italic") + ax.set_xticks([]) + for spine in ax.spines.values(): + spine.set_visible(False) + + # theme_publication() enables a major gridline at every y tick + # (including the y=0.5 tick used for the gene-row label above), + # which would otherwise render as a stray horizontal line straight + # through each gene track -- turn it off explicitly on all three. + for ax in (ax_tmb, ax_g1, ax_g2): + ax.grid(False) + + if n_highlight > 0: + # Outline (not fill) on the gene rows, so the green/white track + # colors stay clean underneath. + for ax in (ax_g1, ax_g2): + ax.add_patch(plt.Rectangle( + (-0.5, 0), n_highlight, 1, + facecolor="none", edgecolor=highlight_color, linewidth=1.5, zorder=3, + )) + ax_g2.text( + n_highlight / 2 - 0.5, 0.5, str(raw), + ha="center", va="center", fontsize=9, fontweight="bold", zorder=4, + ) + + _draw_panel(outer[0], obs_order, obs1, obs2, obs_n_highlight, raw_obs, "Observed", "#f4d35e") + _draw_panel( + outer[1], sim_order, sim1, sim2, sim_n_highlight, raw_sim, + f"Simulation (permutation #{simulation_index})", "#c0c0c0", + ) + + fig.suptitle(f"{gene1} – {gene2}", fontweight="bold") + return fig + + +# --------------------------------------------------------------------------- +# oncoprint +# --------------------------------------------------------------------------- + +def oncoprint( + genes: Sequence[str], + obj: Dict[str, Any], + simulation_index: int = 0, + title: Optional[str] = None, + figsize: Optional[Sequence[float]] = None, +) -> Figure: + """ + Multi-gene oncoprint with a per-sample TMB bar, observed vs. one + null-model simulation side by side. + + Not present in the R package -- generalizes :func:`oncoprint_pair` from + two genes to an arbitrary list, in the same visual style: a per-sample + TMB bar above a genes x samples binary track per gene, with the + "Observed" panel and one retained null-model permutation + (``obj['null'][simulation_index]``) drawn side by side for comparison. + + Genes are ordered top-to-bottom by decreasing observed mutation + frequency (the standard oncoprint convention) -- the same order is used + for both panels, since the null model preserves each gene's observed + frequency exactly. Within each panel, samples are ordered by a "memo + sort": grouped by their mutation pattern across genes (in the same + gene-frequency order, most-mutated-gene first), most-altered pattern + leftmost, ties broken by original column order. As in + :func:`oncoprint_pair`, no TMB (or any other) sort is applied anywhere. + + Parameters + ---------- + genes : sequence of str + Gene/feature names, in any order (re-ordered internally by + frequency). Must all be present in ``obj['al'].am['full'].index``. + obj : dict + ``selectX()['obj']``. Uses ``obj['al']`` (for observed calls and + TMB) and ``obj['null']`` (retained null-model simulations). + simulation_index : int, optional + Index into ``obj['null']`` selecting which retained permutation to + show as the "Simulation" panel. Default 0. + title : str, optional + Figure title. Defaults to ``f"Oncoprint (n={n_samples})"``; pass + something more specific (e.g. ``"LUAD oncoprint (n=502)"``) if + useful for your dataset -- this function has no way to know the + cohort's name on its own. + figsize : sequence of float, optional + Figure size; defaults to a size that scales with the number of + genes. + + Returns + ------- + matplotlib.figure.Figure + + Examples + -------- + >>> fig = oncoprint(["KRAS", "STK11", "TP53", "EGFR"], result["obj"]) # doctest: +SKIP + """ + al = obj["al"] + am_full = al.am["full"] + genes = list(genes) + missing = [g for g in genes if g not in am_full.index] + if missing: + raise KeyError(f"Gene(s) not found in obj['al'].am['full'].index: {missing}") + if not obj.get("null"): + raise ValueError("obj['null'] has no retained simulations to compare against") + + samples = am_full.columns.tolist() + n_samples = len(samples) + n_genes = len(genes) + gene_idx = [am_full.index.get_loc(g) for g in genes] + + obs_mat = am_full.loc[genes].to_numpy() > 0 # (n_genes, n_samples), given gene order + sim_mat_full = np.asarray(obj["null"][simulation_index]) + sim_mat = sim_mat_full[gene_idx, :] > 0 + + tmb = ( + al.tmb["total"].set_index("sample")["mutation"] + .reindex(samples).to_numpy(dtype=float) + ) + + # Genes ordered by decreasing observed mutation frequency -- shared by + # both panels, since the null model preserves each gene's observed + # frequency exactly. + freq_order = np.argsort(-obs_mat.sum(axis=1), kind="stable") + genes_ordered = [genes[i] for i in freq_order] + obs_mat = obs_mat[freq_order] + sim_mat = sim_mat[freq_order] + + def _sample_order(mat: np.ndarray) -> np.ndarray: + # "Memo sort": order samples by their mutation pattern across genes + # (gene-frequency order, most-mutated gene first), most-altered + # pattern first -- generalizes oncoprint_pair's 2-gene grouping. + # No TMB or other sort is used; ties keep their natural column + # order (stable sort). + weights = (1 << np.arange(n_genes - 1, -1, -1))[:, None] + pattern = (mat.astype(np.int64) * weights).sum(axis=0) + return np.argsort(-pattern, kind="stable") + + obs_order = _sample_order(obs_mat) + sim_order = _sample_order(sim_mat) + + theme_publication(apply=True) + if figsize is None: + figsize = (12, 1.8 + 0.5 * n_genes) + fig = plt.figure(figsize=figsize) + outer = fig.add_gridspec(1, 2, wspace=0.12, top=0.78) + + tmb_max = float(np.max(tmb)) * 1.05 if n_samples else 1.0 + binary_cmap = ListedColormap([(1.0, 1.0, 1.0, 0.0), "forestgreen"]) + + def _draw_panel(cell, order, mat, panel_title, show_gene_labels): + # One row per gene (its own axes, like oncoprint_pair) rather than + # one combined heatmap image -- gives real, controllable vertical + # spacing between gene tracks via hspace. + inner = cell.subgridspec( + 1 + n_genes, 1, height_ratios=[3] + [1] * n_genes, hspace=0.15, + ) + ax_tmb = fig.add_subplot(inner[0]) + + x = np.arange(n_samples) + ax_tmb.fill_between(x, 0, tmb[order], step="mid", color="black", linewidth=0) + ax_tmb.set_xlim(-0.5, n_samples - 0.5) + ax_tmb.set_ylim(0, tmb_max) + # Same shared TMB scale/units in both panels -- only label it once, + # on the Observed (left) panel, same reasoning as the gene labels. + ax_tmb.set_ylabel("TMB" if show_gene_labels else "", fontweight="normal") + ax_tmb.set_title(panel_title, loc="left", fontweight="bold") + ax_tmb.set_xticks([]) + ax_tmb.grid(False) + for side in ("top", "right", "bottom"): + ax_tmb.spines[side].set_visible(False) + + for i, gene in enumerate(genes_ordered): + ax_g = fig.add_subplot(inner[1 + i], sharex=ax_tmb) + # Thin sample-track baseline, drawn under the mutation track so + # it only shows through where this gene isn't altered. + ax_g.axhline(0.5, color="black", linewidth=0.8, zorder=1) + ax_g.imshow( + mat[i, order].astype(float)[np.newaxis, :], + aspect="auto", cmap=binary_cmap, vmin=0, vmax=1, + extent=(-0.5, n_samples - 0.5, 0, 1), interpolation="nearest", + zorder=2, + ) + ax_g.set_ylim(0, 1) + ax_g.set_xticks([]) + if show_gene_labels: + ax_g.set_yticks([0.5]) + ax_g.set_yticklabels([gene], style="italic") + else: + ax_g.set_yticks([]) + ax_g.grid(False) + for spine in ax_g.spines.values(): + spine.set_visible(False) + + _draw_panel(outer[0], obs_order, obs_mat, "Observed", show_gene_labels=True) + # Gene identity/order is shared and already labelled on the left + # (Observed) panel -- repeating it on the right, immediately adjacent, + # would sit on top of the Observed panel's plot area. + _draw_panel( + outer[1], sim_order, sim_mat, f"Simulation (permutation #{simulation_index})", + show_gene_labels=False, + ) + + if title is None: + title = f"Oncoprint (n={n_samples})" + fig.suptitle(title, fontweight="bold") + return fig diff --git a/selectsim/selectsim.py b/selectsim/selectsim.py index f1fe4b5..5c56c7b 100644 --- a/selectsim/selectsim.py +++ b/selectsim/selectsim.py @@ -1,110 +1,334 @@ +""" +Main SelectSim module. + +This module contains the main selectX function that orchestrates +the complete analysis pipeline. +""" + +from typing import Dict, Any, Optional import numpy as np import pandas as pd +import os + from selectsim.alteration_landscape import AlterationLandscape +from selectsim.template import template_obj_gen +from selectsim.weights import generate_w_block +from selectsim.null_model import ( + null_model_parallel, + retrieve_outliers, + _filter_zarr_null_store, +) +from selectsim.stats import ( + al_stats, + al_pairwise_alteration_stats, + r_am_pairwise_alteration_overlap, + w_r_am_pairwise_alteration_overlap, + interaction_table +) + + +def _resolve_memory_budget_bytes(memory_budget_gb: Optional[float]) -> float: + """ + Resolve the usable memory budget (in bytes) for ``store="auto"``. + + If ``memory_budget_gb`` is given, use it directly (as an absolute + budget, not a fraction of RAM). Otherwise autodetect total physical + RAM via ``os.sysconf`` (POSIX; Linux/Mac) and use 50% of it as the + usable budget, leaving headroom for the OS and other processes. Falls + back to a hardcoded 4GB total (2GB usable) if ``sysconf`` is + unavailable (e.g. Windows) -- no new dependency is introduced to + detect RAM more portably than this. + """ + if memory_budget_gb is not None: + return memory_budget_gb * (1024 ** 3) + try: + total_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') + except (ValueError, AttributeError, OSError): + total_bytes = 4 * (1024 ** 3) + return 0.5 * total_bytes + -def selectX(M, sample_class, alteration_class, n_cores=1, min_freq=10, n_permut=1000,lambda_var=0.3, tao=1, save_object=False, folder="./", verbose=True, estimate_pairwise=False, maxFDR=0.25, seed=42): +def selectX( + M: Dict[str, Any], + sample_class: Dict[str, str], + alteration_class: Dict[str, str], + n_cores: int = 1, + min_freq: int = 10, + n_permut: int = 1000, + lambda_var: float = 0.3, + tao: float = 1.0, + save_object: bool = False, + folder: str = "./", + verbose: bool = True, + estimate_pairwise: bool = False, + max_fdr: float = 0.25, + seed: int = 42, + store: str = "memory", + store_path: Optional[str] = None, + memory_budget_gb: Optional[float] = None, +) -> Dict[str, Any]: """ - A python version of the `selectX` function implemneting SelectSim algorithm. + Main SelectSim analysis pipeline. - Parameters: + Analyzes gene alteration data to identify co-mutation (CO) and mutual + exclusivity (ME) patterns between genes, accounting for tumor mutation + burden (TMB) heterogeneity. + + Parameters ---------- - M : dict - GAM and TMB data. - sample_class : dict - Sample covariates. - alteration_class : dict - Alteration covariates. - n_cores : int - Number of cores. - min_freq : int - Minimum frequency of gene to be mutated to do the analysis. - n_permut : int - Number of simulations. - lambda_var : float - Weight factor. - tao : float - Fold change factor. - save_object : bool - Whether to save the result object. - folder : str - Folder to save results. - verbose : bool - Whether to print steps. - estimate_pairwise : bool - Whether to compute pairwise p-values. - maxFDR : float - Maximum false discovery rate. - seed : int - Random seed. - - Returns: + M : Dict[str, Any] + Input data dictionary containing: + - 'M': Dict of mutation type -> binary gene x sample DataFrames + - 'tmb': Dict of mutation type -> DataFrame with 'sample' and 'mutation' columns + sample_class : Dict[str, str] + Sample covariates mapping sample IDs to categories (e.g., tumor types). + alteration_class : Dict[str, str] + Alteration covariates mapping gene names to categories. + n_cores : int, optional + Number of parallel cores. Default 1. + min_freq : int, optional + Minimum samples a gene must be mutated in. Default 10. + n_permut : int, optional + Number of null model simulations. Default 1000. + lambda_var : float, optional + TMB penalty weight factor. Default 0.3. + tao : float, optional + Fold-change threshold for TMB penalty. Default 1.0. + save_object : bool, optional + Whether to save results to disk. Default False. When the null + model is zarr-backed (``store="zarr"`` or ``store="auto"`` + resolving to zarr), pickling ``obj`` is cheap regardless of + cohort size or ``n_permut`` -- a zarr.Array's pickle is a path + reference plus metadata (chunks are read lazily on access), not + the underlying array data. + folder : str, optional + Output folder path. Default "./". + verbose : bool, optional + Whether to print progress messages. Default True. + estimate_pairwise : bool, optional + Whether to compute pairwise p-values. Default False. + max_fdr : float, optional + FDR threshold for significance. Default 0.25. + seed : int, optional + Random seed for reproducibility. Default 42. + store : str, optional + Where to materialize null-model simulations: "memory" (default, + unchanged existing behavior -- holds every permutation in RAM as + a list), "zarr" (streams permutations to an on-disk, chunked, + compressed zarr store at ``store_path`` in bounded-size batches, + so peak memory stays roughly constant regardless of cohort size + or ``n_permut``), or "auto" (estimate the null model's memory + footprint from ``n_genes * n_samples * n_permut`` and pick + "memory" or "zarr" automatically against a detected/configured + budget -- see ``memory_budget_gb``). Use "auto" to make a single + call scale safely across machines of different sizes without the + caller needing to know cohort dimensions in advance. Default + "memory". + store_path : str, optional + Filesystem path for the on-disk zarr store, used when + ``store="zarr"`` or when ``store="auto"`` resolves to zarr. + Defaults to ``os.path.join(folder, "null_model.zarr")`` if not + given. + memory_budget_gb : float, optional + Usable memory budget (in GB) for the ``store="auto"`` decision. + If not given, autodetects total physical RAM and uses 50% of it. + Ignored unless ``store="auto"``. + + Returns ------- - dict - Result object and analysis results. + Dict[str, Any] + Dictionary containing: + - 'obj': SelectX object with AL, W, T, null model, and statistics + - 'result': DataFrame with interaction results + + Examples + -------- + >>> M = { + ... "M": {"missense": gam_df, "truncating": gam_df2}, + ... "tmb": {"missense": tmb_df, "truncating": tmb_df2} + ... } + >>> sample_class = {"sample1": "LUAD", "sample2": "LUSC"} + >>> alteration_class = {"TP53": "TSG", "KRAS": "Oncogene"} + >>> result = selectX(M, sample_class, alteration_class, n_permut=100) + >>> result['result'].head() """ + # Set random seed np.random.seed(seed) + # Step 1: Create Alteration Landscape object if verbose: print("#### Creating SelectX object ####") print("Step 1 -> Parsing and Filtering GAM...") - al = AlterationLandscape(M, alteration_class, sample_class, min_freq, verbose) + al = AlterationLandscape( + M, + feat_covariates=alteration_class, + sample_covariates=sample_class, + min_freq=min_freq, + verbose=verbose + ) if verbose: print("-> Alteration Landscape object created") - #print("Step 2 -> Generating Template object...") - # temp_data = template_obj_gen(al) + # Step 2: Generate template matrices + if verbose: + print("Step 2 -> Generating Template object...") + + temp_data = template_obj_gen(al) + + if verbose: + print("-> Template object created") + + # Step 3: Generate weight matrix + if verbose: + print("Step 3 -> Generating sample weight matrix...") + + W = generate_w_block(al, lambda_=lambda_var, tao=tao) + + if verbose: + print("-> Weight Matrix created") + + # Step 4: Generate null model + if verbose: + print("Step 4 -> Generating null model...") + + if store not in ("memory", "zarr", "auto"): + raise ValueError(f"Unknown store {store!r}; expected 'memory', 'zarr', or 'auto'.") + + resolved_store = store + resolved_store_path = store_path + if store == "auto": + n_genes = al.am['full'].shape[0] + n_samples = al.am['full'].shape[1] + # x5 safety factor is empirically grounded: measured peak RSS for + # a real LUAD run (40 genes x 1,572 samples x 1,000 permutations) + # was ~2.1GB against a raw-array estimate of ~500MB (n_genes * + # n_samples * n_permut * 8 bytes), i.e. ~4.2x -- the gap covers + # the null-model list itself plus in-flight residual/template + # matrices and joblib's inter-process copies. + estimated_bytes = n_genes * n_samples * n_permut * 8 * 5 + budget_bytes = _resolve_memory_budget_bytes(memory_budget_gb) + resolved_store = "zarr" if estimated_bytes > budget_bytes else "memory" + if verbose: + print( + f"-> store='auto' selected store={resolved_store!r} " + f"(estimated null-model size ~{estimated_bytes / 1e9:.2f}GB, " + f"budget ~{budget_bytes / 1e9:.2f}GB)" + ) + + if resolved_store == "zarr": + if resolved_store_path is None: + resolved_store_path = os.path.join(folder, "null_model.zarr") + # create_zarr_null_store opens in mode="w" -- the parent directory + # must exist first (it's normally created later, only when + # save_object=True, so it can't be assumed to exist yet here). + os.makedirs(os.path.dirname(os.path.abspath(resolved_store_path)), exist_ok=True) + + sim = null_model_parallel( + al, + temp_data['temp_mat'], + W['W'], + n_cores=n_cores, + n_permut=n_permut, + seed=seed, + verbose=verbose, + store=resolved_store, + store_path=resolved_store_path, + ) - # if verbose: - # print("-> Template object created") - # print("Step 3 -> Generating penalty matrix...") + # Create object + obj = { + 'al': al, + 'W': W, + 'T': temp_data, + 'null': sim, + 'nSim': n_permut + } - # W = generateW_block(al, lambda_, tao) + # Step 5: Remove outliers + if verbose: + print("-> Removing outliers from null model...") + + outliers = retrieve_outliers(obj, n_sim=n_permut) + n_outliers = np.sum(outliers) - # if verbose: - # print("-> Penalty Matrix created") - # print("Step 4 -> Generating null model...") + if n_outliers == 0: + if verbose: + print(f"Removed null-matrix: {n_outliers}") + else: + if resolved_store == "zarr": + # Stream a filtered copy on disk rather than materializing the + # retained permutations into a Python list -- keeps peak + # memory O(1 permutation) even for a zarr-backed null model. + keep_idx = np.where(~outliers)[0] + obj['null'] = _filter_zarr_null_store(obj['null'], keep_idx, resolved_store_path) + else: + obj['null'] = [obj['null'][i] for i in range(len(outliers)) if not outliers[i]] + obj['nSim'] = len(obj['null']) + if verbose: + print(f"Removed null-matrix: {n_outliers}") + print("Updated the null-model and nSim variables...") - # sim = null_model_parallel(al, temp_data["temp_mat"], W["W"], n_cores, n_permut) - obj = {"al": al} + if verbose: + print("-> Null model generated") + print("### SelectSim object created ###") + + # Step 6: Compute statistics + if verbose: + print("#### Computing EDs on the dataset ####") - # if verbose: - # print("-> Removing outliers from null model...") + # Univariate stats + als = al_stats(obj) - # outliers = retrieve_outliers(obj, n_permut) - # if np.sum(outliers) == 0: - # if verbose: - # print(f"Removed null-matrix: {np.sum(outliers)}") - # else: - # obj["null"] = [obj["null"][i] for i in range(len(outliers)) if not outliers[i]] - # obj["nSim"] = len(obj["null"]) - # if verbose: - # print(f"Removed null-matrix: {np.sum(outliers)}") - # print("Updated the null-model and nSim variables...") + # Pairwise stats + alp = al_pairwise_alteration_stats(obj, als, do_blocks=False) + als['alteration_pairwise'] = alp - # als = al_stats(obj) - # alp = al_pairwise_alteration_stats(obj, als, do_blocks=False) - # als["alteration.pairwise"] = alp + # Get observed overlaps + obs_co = alp['overlap'] + wobs_co = alp['w_overlap'] - # obs_co = np.zeros_like(al.am["full"]) - # wobs_co = np.zeros_like(al.am["full"]) - # robs_co = np.zeros_like(al.am["full"]) - # wrobs_co = np.zeros_like(al.am["full"]) + # Compute expected overlaps from null model + W_aligned = W['W'][al.am['full'].columns.tolist()].values + robs_co = r_am_pairwise_alteration_overlap(obj['null']) + wrobs_co = w_r_am_pairwise_alteration_overlap(obj['null'], W_aligned) + + # Step 7: Generate interaction table + selectX_result = interaction_table( + al, + als, + obs_co, + wobs_co, + robs_co, + wrobs_co, + obj['null'], + max_fdr=max_fdr, + n_cores=n_cores, + estimate_pairwise=estimate_pairwise, + n_permut=obj['nSim'] + ) + + # Store overlap matrices in object + obj['robs_co'] = robs_co + obj['wrobs_co'] = wrobs_co + + if verbose: + print("#### EDs computed ####") - # selectX_result = interaction_table( - # al, als, obs_co, wobs_co, robs_co, wrobs_co, obj["null"], maxFDR, n_cores, estimate_pairwise, obj["nSim"] - # ) + # Save results if requested + if save_object: + os.makedirs(folder, exist_ok=True) - # obj["robs.co"] = robs_co - # obj["wrobs.co"] = wrobs_co + # Save object as pickle + import pickle + with open(os.path.join(folder, 'selectsim_object.pkl'), 'wb') as f: + pickle.dump(obj, f) - # if verbose: - # print("#### EDs computed ####") + # Save results as CSV + selectX_result.to_csv(os.path.join(folder, 'selectsim_results.csv'), index=False) - # if save_object: - # os.makedirs(folder, exist_ok=True) - # save_rds(obj, os.path.join(folder, "selectX_object.pkl")) - # save_rds(selectX_result, os.path.join(folder, "selectX_results.pkl")) + if verbose: + print(f"Results saved to {folder}") - return {"obj": obj} \ No newline at end of file + return {'obj': obj, 'result': selectX_result} diff --git a/selectsim/stats.py b/selectsim/stats.py new file mode 100644 index 0000000..e03c8e2 --- /dev/null +++ b/selectsim/stats.py @@ -0,0 +1,736 @@ +""" +Statistics computation functions for SelectSim. + +This module contains functions for computing pairwise overlap statistics, +effect sizes, FDR estimation, and the final interaction table. +""" + +from typing import Dict, Any, List, Optional +import numpy as np +import pandas as pd +from numpy.typing import NDArray + +from selectsim.alteration_landscape import AlterationLandscape +from selectsim.utils import add, matrix_to_pairwise_vector, create_pair_template + + +def am_stats(am: NDArray[np.float64]) -> Dict[str, Any]: + """ + Compute basic statistics on an alteration matrix. + + Parameters + ---------- + am : NDArray + Binary alteration matrix (genes x samples). + + Returns + ------- + Dict[str, Any] + Dictionary containing: + - 'n_samples': Number of samples + - 'n_alterations': Number of genes/features + - 'n_occurrences': Total number of mutations + - 'alterations_per_sample': Mutations per sample + - 'alteration_count': Mutations per gene + """ + am = am * 1.0 # Ensure float + + sample_alt_n = np.sum(am, axis=0) # Column sums + feat_alt_n = np.sum(am, axis=1) # Row sums + num_of_edges = np.sum(np.abs(am)) + + return { + 'n_samples': am.shape[1], + 'n_alterations': am.shape[0], + 'n_occurrences': num_of_edges, + 'alterations_per_sample': sample_alt_n, + 'alteration_count': feat_alt_n + } + + +def al_stats(obj: Dict[str, Any]) -> Dict[str, Any]: + """ + Compute statistics on an AlterationLandscape with block-wise breakdown. + + Parameters + ---------- + obj : Dict[str, Any] + SelectX object containing 'al' (AlterationLandscape). + + Returns + ------- + Dict[str, Any] + Statistics object with overall and per-block statistics. + """ + al = obj['al'] + full_am = al.am['full'].values + + # Overall stats + als = am_stats(full_am) + + # Get blocks + blocks = al.get_blocks() + + # Stats per sample block + als['sample_blocks'] = {} + for block_name, sample_list in blocks['sample.blocks'].items(): + # Get column indices for this block + all_cols = al.am['full'].columns.tolist() + col_indices = [all_cols.index(s) for s in sample_list if s in all_cols] + + if len(col_indices) > 0: + sub_m = full_am[:, col_indices] + als['sample_blocks'][block_name] = am_stats(sub_m) + + # Stats per alteration block + als['alteration_blocks'] = {} + for block_name, gene_list in blocks['alteration.blocks'].items(): + # Get row indices for this block + all_rows = al.am['full'].index.tolist() + row_indices = [all_rows.index(g) for g in gene_list if g in all_rows] + + if len(row_indices) > 0: + sub_m = full_am[row_indices, :] + als['alteration_blocks'][block_name] = am_stats(sub_m) + + return als + + +def am_pairwise_alteration_overlap(am: NDArray[np.float64]) -> NDArray[np.float64]: + """ + Compute pairwise overlap matrix. + + Calculates A @ A.T where element [i,j] is the number of samples + with both gene i and gene j mutated. + + Parameters + ---------- + am : NDArray + Binary alteration matrix (genes x samples). + + Returns + ------- + NDArray + Symmetric overlap matrix (genes x genes). + """ + A = am * 1.0 + overlap = A @ A.T + return overlap + + +def am_weight_pairwise_alteration_overlap( + am: NDArray[np.float64], + W: NDArray[np.float64] +) -> NDArray[np.float64]: + """ + Compute TMB-weighted pairwise overlap matrix. + + Calculates (W * A) @ A.T where W contains sample weights. + + Parameters + ---------- + am : NDArray + Binary alteration matrix (genes x samples). + W : NDArray + Weight matrix (genes x samples). + + Returns + ------- + NDArray + Weighted overlap matrix (genes x genes). + """ + A = am * 1.0 + weighted_A = W * A + overlap = weighted_A @ A.T + return overlap + + +def am_pairwise_alteration_coverage( + overlap_m: NDArray[np.float64], + m_stats: Dict[str, Any], + w_overlap_m: NDArray[np.float64] +) -> Dict[str, Any]: + """ + Compute coverage statistics for pairwise overlaps. + + Parameters + ---------- + overlap_m : NDArray + Overlap matrix. + m_stats : Dict[str, Any] + Matrix statistics from am_stats. + w_overlap_m : NDArray + Weighted overlap matrix. + + Returns + ------- + Dict[str, Any] + Dictionary with 'overlap' and 'w_overlap' matrices. + """ + overlap_m = np.asarray(overlap_m) + w_overlap_m = np.asarray(w_overlap_m) + + # Set diagonal to alteration counts + np.fill_diagonal(overlap_m, m_stats['alteration_count']) + + return { + 'overlap': overlap_m, + 'w_overlap': w_overlap_m + } + + +def al_pairwise_alteration_stats( + obj: Dict[str, Any], + als: Optional[Dict[str, Any]] = None, + do_blocks: bool = False +) -> Dict[str, Any]: + """ + Compute all pairwise overlap statistics. + + Parameters + ---------- + obj : Dict[str, Any] + SelectX object containing 'al' and 'W'. + als : Dict[str, Any], optional + Pre-computed alteration stats. If None, computed automatically. + do_blocks : bool, optional + Whether to compute block-wise statistics. Default False. + + Returns + ------- + Dict[str, Any] + Pairwise statistics including overlap matrices. + """ + al = obj['al'] + + if als is None: + als = al_stats(obj) + + # Get full AM and weight matrix + full_am = al.am['full'].values + W = obj['W']['W'] + + # Ensure W has same column order as AM + col_order = al.am['full'].columns.tolist() + W_aligned = W[col_order].values + + # Compute overlaps + m_overlap = am_pairwise_alteration_overlap(full_am) + m_woverlap = am_weight_pairwise_alteration_overlap(full_am, W_aligned) + + # Package results + m_pairwise = am_pairwise_alteration_coverage(m_overlap, als, m_woverlap) + + if do_blocks: + blocks = al.get_blocks() + pairwise_blocks = {} + + for block_name, sample_list in blocks['sample.blocks'].items(): + # Get column indices + all_cols = al.am['full'].columns.tolist() + col_indices = [all_cols.index(s) for s in sample_list if s in all_cols] + + if len(col_indices) > 0: + sub_m = full_am[:, col_indices] + sub_w = W_aligned[:, col_indices] + + m_overlap_block = am_pairwise_alteration_overlap(sub_m) + m_woverlap_block = am_weight_pairwise_alteration_overlap(sub_m, sub_w) + + block_stats = als['sample_blocks'].get(block_name, am_stats(sub_m)) + m_pairwise_block = am_pairwise_alteration_coverage( + m_overlap_block, block_stats, m_woverlap_block + ) + pairwise_blocks[block_name] = m_pairwise_block + + m_pairwise['sample_blocks'] = pairwise_blocks + + return m_pairwise + + +def r_am_pairwise_alteration_overlap( + null: List[NDArray[np.float64]] +) -> List[NDArray[np.float64]]: + """ + Compute overlap matrices for all null model simulations. + + Parameters + ---------- + null : List[NDArray] + List of null model matrices. + + Returns + ------- + List[NDArray] + List of overlap matrices. + """ + return [am_pairwise_alteration_overlap(m) for m in null] + + +def w_r_am_pairwise_alteration_overlap( + null: List[NDArray[np.float64]], + W: NDArray[np.float64] +) -> List[NDArray[np.float64]]: + """ + Compute weighted overlap matrices for all null model simulations. + + Parameters + ---------- + null : List[NDArray] + List of null model matrices. + W : NDArray + Weight matrix. + + Returns + ------- + List[NDArray] + List of weighted overlap matrices. + """ + return [am_weight_pairwise_alteration_overlap(m, W) for m in null] + + +def effect_size( + obs: NDArray[np.float64], + exp: NDArray[np.float64] +) -> NDArray[np.float64]: + """ + Compute effect size between observed and expected values. + + Formula: ES = (obs - exp) * sin(pi/4) = (obs - exp) * 0.707 + + Parameters + ---------- + obs : NDArray + Observed values. + exp : NDArray + Expected values. + + Returns + ------- + NDArray + Effect sizes. + """ + return (obs - exp) * np.sin(np.pi / 4) + + +def r_effect_size( + null_overlap: List[NDArray[np.float64]], + mean_mat: NDArray[np.float64], + n_permut: int = 1000 +) -> List[NDArray[np.float64]]: + """ + Compute effect sizes for null model overlaps. + + Parameters + ---------- + null_overlap : List[NDArray] + List of null model overlap matrices. + mean_mat : NDArray + Mean overlap matrix. + n_permut : int, optional + Number of permutations. Default 1000. + + Returns + ------- + List[NDArray] + List of effect size vectors (pairwise). + """ + results = [] + for overlap in null_overlap: + es = (overlap - mean_mat) * np.sin(np.pi / 4) + es_vec = matrix_to_pairwise_vector(es) + results.append(es_vec) + return results + + +def binary_yule( + overlap: NDArray[np.float64], + mat: NDArray[np.float64] +) -> NDArray[np.float64]: + """ + Compute Yule's Q coefficient for pairwise associations. + + Yule = (sqrt(OR) - 1) / (sqrt(OR) + 1) + where OR = (v00 * v11) / (v01 * v10) + + Parameters + ---------- + overlap : NDArray + Overlap matrix. + mat : NDArray + Original alteration matrix. + + Returns + ------- + NDArray + Yule coefficient matrix. + """ + overlap = np.asarray(overlap) + n_samples = mat.shape[1] + + # Marginals + marginal_f1 = np.tile(np.diag(overlap), (overlap.shape[1], 1)).T + marginal_f2 = marginal_f1.T + + # Contingency table values + v_11 = overlap + v_10 = marginal_f1 - v_11 + v_01 = marginal_f2 - v_11 + v_00 = n_samples - v_11 - v_01 - v_10 + + # Odds ratio (with small epsilon to avoid division by zero) + eps = 1e-10 + OR = (v_00 * v_11) / (v_10 * v_01 + eps) + + # Yule coefficient + sqrt_OR = np.sqrt(np.maximum(OR, eps)) + Yule = (sqrt_OR - 1) / (sqrt_OR + 1) + + return Yule + + +def estimate_fdr2( + obs: NDArray[np.float64], + exp: NDArray[np.float64], + n_sim: int, + max_fdr: float = 0.25 +) -> NDArray[np.float64]: + """ + Estimate False Discovery Rate using sorted scanning algorithm. + + Parameters + ---------- + obs : NDArray + Observed values (1D array). + exp : NDArray + Expected values from null model (1D array, all simulations concatenated). + n_sim : int + Number of simulations. + max_fdr : float, optional + Maximum FDR threshold to scan. Default 0.25. + + Returns + ------- + NDArray + FDR values for each observation. + """ + all_fdr = np.ones(len(obs)) + orig_obs = obs.copy() + + # Sort in decreasing order + obs_sorted = np.sort(obs)[::-1] + exp_sorted = np.sort(exp)[::-1] + + obs_pos = 0 + exp_pos = 0 + scan = True + + while scan: + if obs_pos >= len(obs_sorted): + scan = False + else: + value = obs_sorted[obs_pos] + + # Count expected values >= current value + while exp_pos < len(exp_sorted) and exp_sorted[exp_pos] >= value: + exp_pos += 1 + + # False positive rate + fp = exp_pos / n_sim + + # Count observed values >= current value + n_obs_ge = np.sum(obs >= value) + + # FDR + fdr = min(fp / max(n_obs_ge, 1), 1.0) + + # Assign FDR to all observations with this value + all_fdr[orig_obs == value] = fdr + + if fdr >= max_fdr: + scan = False + + obs_pos += 1 + + return all_fdr + + +def estimate_p_val( + robs_co: List[NDArray[np.float64]], + obs_co: NDArray[np.float64], + gene1: str, + gene2: str, + gene_index: Dict[str, int] +) -> float: + """ + Compute empirical p-value for a gene pair. + + Parameters + ---------- + robs_co : List[NDArray] + List of null model overlap matrices. + obs_co : NDArray + Observed overlap matrix. + gene1 : str + First gene name. + gene2 : str + Second gene name. + gene_index : Dict[str, int] + Mapping from gene name to its row/col index, e.g. + ``{name: i for i, name in enumerate(gene_names)}``. Takes a + prebuilt mapping (rather than a gene name list to search) so + callers looking up many pairs -- as :func:`estimate_pairwise_p` + does -- build the O(n_genes) mapping once instead of paying an + O(n_genes) list scan per gene per pair. + + Returns + ------- + float + Two-tailed p-value. + """ + idx1 = gene_index[gene1] + idx2 = gene_index[gene2] + + # Get background distribution + background = [m[idx1, idx2] for m in robs_co] + + # Observed value + actual_ratio = obs_co[idx1, idx2] + + # Empirical CDF + background = np.array(background) + n = len(background) + p_lower = np.sum(background <= actual_ratio) / n + p_upper = 1 - p_lower + + # Two-tailed p-value + p_val = 2 * min(p_lower, p_upper) + + return p_val + + +def estimate_pairwise_p( + obs: NDArray[np.float64], + exp: List[NDArray[np.float64]], + results: pd.DataFrame, + n_sim: int, + gene_names: List[str] +) -> NDArray[np.float64]: + """ + Compute pairwise p-values for all gene pairs in results. + + Parameters + ---------- + obs : NDArray + Observed overlap matrix. + exp : List[NDArray] + List of null model overlap matrices. + results : pd.DataFrame + Results dataframe with SFE_1 and SFE_2 columns. + n_sim : int + Number of simulations. + gene_names : List[str] + List of gene names. + + Returns + ------- + NDArray + P-values for each gene pair. + """ + gene_index = {name: i for i, name in enumerate(gene_names)} + + p_vals = [] + for _, row in results.iterrows(): + gene1 = row['SFE_1'] + gene2 = row['SFE_2'] + p_val = estimate_p_val(exp, obs, gene1, gene2, gene_index) + p_vals.append(p_val) + + return np.array(p_vals) + + +def interaction_table( + al: AlterationLandscape, + als: Dict[str, Any], + obs: NDArray[np.float64], + wobs: NDArray[np.float64], + r_obs: List[NDArray[np.float64]], + r_wobs: List[NDArray[np.float64]], + null: List[NDArray[np.float64]], + max_fdr: float = 0.25, + n_cores: int = 1, + estimate_pairwise: bool = False, + n_permut: int = 1000 +) -> pd.DataFrame: + """ + Generate final results table with all statistics. + + Creates a comprehensive table with 23 columns including effect sizes, + FDR values, and CO/ME classification. + + Parameters + ---------- + al : AlterationLandscape + AlterationLandscape object. + als : Dict[str, Any] + Alteration statistics. + obs : NDArray + Observed overlap matrix. + wobs : NDArray + Weighted observed overlap matrix. + r_obs : List[NDArray] + List of null model overlap matrices. + r_wobs : List[NDArray] + List of null model weighted overlap matrices. + null : List[NDArray] + List of null model matrices. + max_fdr : float, optional + FDR threshold. Default 0.25. + n_cores : int, optional + Number of cores. Default 1. + estimate_pairwise : bool, optional + Whether to compute pairwise p-values. Default False. + n_permut : int, optional + Number of permutations. Default 1000. + + Returns + ------- + pd.DataFrame + Results table with columns: + SFE_1, SFE_2, name, support_1, support_2, freq_1, freq_2, + overlap, w_overlap, max_overlap, freq_overlap, r_overlap, + w_r_overlap, wES, wFDR, nES, mean_r_nES, nFDR, nFDR2, + cum_freq, type, FDR + """ + features = al.am['full'].index.tolist() + n_features = len(features) + n_samples = al.am['full'].shape[1] + + # Create pair template + idx1, idx2, pair_names = create_pair_template(features) + n_pairs = len(pair_names) + + # Initialize results dataframe + results = pd.DataFrame({ + 'SFE_1': [features[i] for i in idx1], + 'SFE_2': [features[j] for j in idx2], + 'name': pair_names + }) + + # Support and frequency + alteration_count = als['alteration_count'] + results['support_1'] = [alteration_count[i] for i in idx1] + results['support_2'] = [alteration_count[j] for j in idx2] + results['freq_1'] = results['support_1'] / n_samples + results['freq_2'] = results['support_2'] / n_samples + + # Overlap values + results['overlap'] = matrix_to_pairwise_vector(obs) + results['w_overlap'] = matrix_to_pairwise_vector(wobs) + + # Max possible overlap per block + if 'sample_blocks' in als and als['sample_blocks']: + max_overlap_mat = np.zeros((n_features, n_features)) + + for block_name, block_stats in als['sample_blocks'].items(): + block_count = block_stats['alteration_count'] + + # Create pairwise min matrix + a = np.tile(block_count, (n_features, 1)) + b = a.T + c = np.minimum(a, b) + max_overlap_mat += c + + results['max_overlap'] = matrix_to_pairwise_vector(max_overlap_mat) + else: + # Fallback: use overall min + a = np.tile(alteration_count, (n_features, 1)) + b = a.T + results['max_overlap'] = matrix_to_pairwise_vector(np.minimum(a, b)) + + results['freq_overlap'] = results['overlap'] / (results['max_overlap'] + 1e-10) + + # Expected overlap from null model + mean_r_obs = add(r_obs) / len(r_obs) + mean_r_wobs = add(r_wobs) / len(r_wobs) + + results['r_overlap'] = matrix_to_pairwise_vector(mean_r_obs) + results['w_r_overlap'] = matrix_to_pairwise_vector(mean_r_wobs) + + # Pairwise p-values (optional) + if estimate_pairwise: + results['pairwise_p'] = estimate_pairwise_p( + obs, r_obs, results, n_permut, features + ) + + # Weighted effect size + exp_es = (results['w_overlap'].values - results['w_r_overlap'].values) * np.sin(np.pi / 4) + results['wES'] = exp_es + + # Compute FDR on wES + exp_r_es = r_effect_size(r_wobs, mean_r_wobs, n_permut) + exp_es_flat = np.concatenate(exp_r_es) + + results['wFDR'] = estimate_fdr2( + np.abs(results['wES'].values), + np.abs(exp_es_flat), + n_permut, + max_fdr + ) + + # Normalized effect size + es_mtx = np.abs(np.array(exp_r_es)) # shape: (n_permut, n_pairs) + mean_es = np.mean(es_mtx, axis=0) + + n_es = np.abs(results['wES'].values) - mean_es + n_es = np.maximum(n_es, 0) + results['nES'] = np.sign(results['wES'].values) * n_es + results['mean_r_nES'] = np.sign(results['wES'].values) * mean_es + + # Compute FDR on nES + exp_r_es_norm = np.abs(es_mtx) - mean_es + exp_r_es_norm = np.maximum(exp_r_es_norm, 0) + exp_r_es_norm = np.sign(es_mtx) * exp_r_es_norm + exp_es_norm_flat = exp_r_es_norm.flatten() + + results['nFDR'] = estimate_fdr2( + np.abs(results['nES'].values), + np.abs(exp_es_norm_flat), + n_permut, + max_fdr + ) + + # Frequency-specific FDR (nFDR2) + results['cum_freq'] = results['support_1'] + results['support_2'] + freq_cats = [0, 0.02, 0.05, 0.10, 1.0] + results['nFDR2'] = 1.0 + + for i in range(1, len(freq_cats)): + lower = freq_cats[i - 1] * 2 * n_samples + upper = freq_cats[i] * 2 * n_samples + + select = (results['cum_freq'] > lower) & (results['cum_freq'] <= upper) + + if select.sum() > 1: + obs_subset = np.abs(results.loc[select, 'nES'].values) + # exp_r_es_norm has shape (n_permut, n_pairs), select along axis 1 (pairs) + select_arr = select.values if hasattr(select, 'values') else select + exp_subset = np.abs(exp_r_es_norm[:, select_arr].flatten()) + + fdr_subset = estimate_fdr2(obs_subset, exp_subset, n_permut, max_fdr) + results.loc[select, 'nFDR2'] = fdr_subset + + # Sort by absolute nES (descending) + results = results.sort_values('nES', key=abs, ascending=False) + + # Classification + results['type'] = 'ME' + results.loc[results['nES'] > 0, 'type'] = 'CO' + + # FDR significance flag + results['FDR'] = results['nFDR2'] <= max_fdr + + # Reset index + results = results.reset_index(drop=True) + + return results diff --git a/selectsim/template.py b/selectsim/template.py new file mode 100644 index 0000000..8c2d437 --- /dev/null +++ b/selectsim/template.py @@ -0,0 +1,157 @@ +""" +Template generation functions for SelectSim. + +This module contains functions for generating the expected mutation probability +matrices (S matrices) used in null model simulation. +""" + +from typing import Dict, Any +import numpy as np +import pandas as pd +from numpy.typing import NDArray + +from selectsim.alteration_landscape import AlterationLandscape + + +def generate_s( + gam: NDArray[np.float64], + sample_weights: NDArray[np.float64], + upper_bound: float = 1.0 +) -> NDArray[np.float64]: + """ + Generate expected mutation probability matrix S. + + Computes S[i,j] = gene_freq[i] * sample_weight[j], where gene_freq + is the probability of each gene being mutated and sample_weights + are TMB-normalized weights. + + Parameters + ---------- + gam : NDArray + Gene alteration matrix (genes x samples), binary. + sample_weights : NDArray + TMB-based sample weights, normalized to sum to n_samples. + upper_bound : float, optional + Maximum probability value (clipped). Default is 1.0. + + Returns + ------- + NDArray + Expected probability matrix S with same shape as gam. + + Examples + -------- + >>> gam = np.array([[1, 0, 1], [0, 1, 1]]) # 2 genes, 3 samples + >>> weights = np.array([0.8, 1.0, 1.2]) + >>> S = generate_s(gam, weights) + >>> S.shape + (2, 3) + """ + n_samples = gam.shape[1] + + # Compute gene frequency (probability per gene) + gene_freq = np.sum(gam, axis=1) / n_samples + + # Outer product: S = gene_freq[:, None] @ sample_weights[None, :] + sim_gam = np.outer(gene_freq, sample_weights) + + # Clip values > upper_bound + sim_gam[sim_gam > upper_bound] = upper_bound + + return sim_gam + + +def template_obj_gen(al: AlterationLandscape) -> Dict[str, Any]: + """ + Generate template matrices for null model simulation. + + Creates S matrices for each mutation type and sample block, computing + sample weights from TMB distribution. These templates are used for + rejection sampling in the null model generation. + + Parameters + ---------- + al : AlterationLandscape + AlterationLandscape object containing mutation matrices and TMB data. + + Returns + ------- + Dict[str, Any] + Dictionary with: + - 'template_obj': Nested dict of S matrices by mutation_type and block + - 'temp_mat': Dict of combined S matrices per mutation type + + Examples + -------- + >>> al = AlterationLandscape(am_data) + >>> templates = template_obj_gen(al) + >>> 'template_obj' in templates + True + >>> 'temp_mat' in templates + True + """ + template_obj = {} + temp_mat = {} + + # Get mutation type categories (excluding 'full') + categories = [name for name in al.am.keys() if name != 'full'] + + # Initialize template structures + for name in categories: + template_obj[name] = {} + temp_mat[name] = None + + # Get sample blocks + blocks = al.get_blocks() + sample_blocks = blocks['sample.blocks'] + + # Generate templates for each block and mutation type + for block_name, sample_list in sample_blocks.items(): + for category in categories: + # Get the GAM for this category + gam_df = al.am[category] + + # Get samples in this block that exist in the GAM + block_samples = [s for s in sample_list if s in gam_df.columns] + + if len(block_samples) == 0: + continue + + # Get TMB data for this category + tmb_df = al.tmb[category] + + # Get TMB values for samples in this block + tmb_mask = tmb_df['sample'].isin(block_samples) + block_tmb = tmb_df[tmb_mask].set_index('sample')['mutation'] + + # Reorder to match block_samples + block_tmb = block_tmb.reindex(block_samples) + + # Compute sample weights: n_samples * tmb / sum(tmb) + total_tmb = block_tmb.sum() + if total_tmb > 0: + sample_weights = len(block_samples) * block_tmb.values / total_tmb + else: + sample_weights = np.ones(len(block_samples)) + + # Get GAM subset for this block + gam_subset = gam_df[block_samples].values + + # Generate S matrix + S = generate_s(gam_subset, sample_weights) + + # Store in template_obj + template_obj[category][block_name] = S + template_obj[category][f"{block_name}_weight"] = sample_weights + template_obj[category][f"{block_name}_samples"] = block_samples + + # Concatenate to temp_mat + if temp_mat[category] is None: + temp_mat[category] = S + else: + temp_mat[category] = np.hstack([temp_mat[category], S]) + + return { + 'template_obj': template_obj, + 'temp_mat': temp_mat + } diff --git a/selectsim/utils.py b/selectsim/utils.py new file mode 100644 index 0000000..a9cca33 --- /dev/null +++ b/selectsim/utils.py @@ -0,0 +1,160 @@ +""" +Utility functions for SelectSim. + +This module contains helper functions used across the SelectSim package +for matrix operations and pairwise computations. +""" + +from typing import List, Tuple +import numpy as np +from numpy.typing import NDArray + + +def add(matrices: List[NDArray[np.float64]]) -> NDArray[np.float64]: + """ + Sum a list of matrices element-wise. + + Equivalent to R's Reduce("+", x). + + Parameters + ---------- + matrices : List[NDArray] + List of matrices with the same shape to sum. + + Returns + ------- + NDArray + Element-wise sum of all matrices. + + Examples + -------- + >>> m1 = np.array([[1, 2], [3, 4]]) + >>> m2 = np.array([[5, 6], [7, 8]]) + >>> add([m1, m2]) + array([[ 6, 8], + [10, 12]]) + """ + if len(matrices) == 0: + raise ValueError("Cannot sum empty list of matrices") + + result = matrices[0].copy() + for mat in matrices[1:]: + result += mat + return result + + +def pairwise_indices(n: int) -> Tuple[NDArray[np.int64], NDArray[np.int64]]: + """ + Generate upper-triangle indices for pairwise comparisons. + + For n features, generates all unique pairs (i, j) where i < j. + + Parameters + ---------- + n : int + Number of features. + + Returns + ------- + Tuple[NDArray, NDArray] + (i_indices, j_indices) arrays for upper triangle. + + Examples + -------- + >>> i, j = pairwise_indices(3) + >>> list(zip(i, j)) + [(0, 1), (0, 2), (1, 2)] + """ + i_indices, j_indices = np.triu_indices(n, k=1) + return i_indices, j_indices + + +def matrix_to_pairwise_vector(mat: NDArray[np.float64]) -> NDArray[np.float64]: + """ + Extract upper-triangle values from a symmetric matrix as a vector. + + Equivalent to R's as.vector(as.dist(mat)). + + Parameters + ---------- + mat : NDArray + Square symmetric matrix. + + Returns + ------- + NDArray + 1D array of upper-triangle values. + + Examples + -------- + >>> mat = np.array([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) + >>> matrix_to_pairwise_vector(mat) + array([2, 3, 5]) + """ + n = mat.shape[0] + i_indices, j_indices = np.triu_indices(n, k=1) + return mat[i_indices, j_indices] + + +def pairwise_vector_to_matrix(vec: NDArray[np.float64], n: int) -> NDArray[np.float64]: + """ + Convert a pairwise vector back to a symmetric matrix. + + Parameters + ---------- + vec : NDArray + 1D array of pairwise values. + n : int + Number of features (matrix dimension). + + Returns + ------- + NDArray + Symmetric n x n matrix. + + Examples + -------- + >>> vec = np.array([2, 3, 5]) + >>> pairwise_vector_to_matrix(vec, 3) + array([[0., 2., 3.], + [2., 0., 5.], + [3., 5., 0.]]) + """ + mat = np.zeros((n, n)) + i_indices, j_indices = np.triu_indices(n, k=1) + mat[i_indices, j_indices] = vec + mat[j_indices, i_indices] = vec + return mat + + +def create_pair_template(features: List[str]) -> Tuple[NDArray, NDArray, List[str]]: + """ + Create template arrays for pairwise feature combinations. + + Parameters + ---------- + features : List[str] + List of feature names. + + Returns + ------- + Tuple[NDArray, NDArray, List[str]] + (idx1, idx2, pair_names) where idx1/idx2 are indices and + pair_names are formatted as "feature1 - feature2". + + Examples + -------- + >>> features = ['A', 'B', 'C'] + >>> idx1, idx2, names = create_pair_template(features) + >>> names + ['A - B', 'A - C', 'B - C'] + """ + n = len(features) + i_indices, j_indices = np.triu_indices(n, k=1) + + pair_names = [ + f"{features[i]} - {features[j]}" + for i, j in zip(i_indices, j_indices) + ] + + return i_indices, j_indices, pair_names diff --git a/selectsim/weights.py b/selectsim/weights.py new file mode 100644 index 0000000..defe861 --- /dev/null +++ b/selectsim/weights.py @@ -0,0 +1,190 @@ +""" +Weight matrix generation functions for SelectSim. + +This module contains functions for generating TMB-based weight matrices +that penalize samples with high tumor mutation burden. +""" + +from typing import Dict, Any +import numpy as np +import pandas as pd +from numpy.typing import NDArray + +from selectsim.alteration_landscape import AlterationLandscape + + +def generate_w_mean_tmb( + tmb: NDArray[np.float64], + mean_tmb: float, + n_genes: int, + lambda_: float = 0.3, + tao: float = 1.0, + discrete: bool = True +) -> NDArray[np.float64]: + """ + Generate weight matrix based on TMB fold-change. + + Computes weights that penalize samples with high TMB relative to the mean. + The formula is: w = 1 / (1 + lambda * (ceil(FC) - tao)) + + Parameters + ---------- + tmb : NDArray + TMB values per sample. + mean_tmb : float + Mean TMB across all blocks (reference value). + n_genes : int + Number of genes (rows in weight matrix). + lambda_ : float, optional + Weight penalty factor. Higher values give stronger penalty. Default 0.3. + tao : float, optional + Fold-change threshold. FC values <= tao are not penalized. Default 1.0. + discrete : bool, optional + If True, use ceiling function for FC. If False, use continuous FC. + Default True. + + Returns + ------- + NDArray + Weight matrix of shape (n_genes, n_samples). + + Examples + -------- + >>> tmb = np.array([100, 200, 50]) + >>> W = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=10) + >>> W.shape + (10, 3) + """ + # Calculate fold-change + exp_tmb = mean_tmb + tmb_fc = tmb / exp_tmb + + # Cap FC at tao (values below tao get no penalty) + tmb_fc = np.maximum(tmb_fc, tao) + + # Calculate weights + if discrete: + w = 1.0 / (1.0 + lambda_ * (np.ceil(tmb_fc) - tao)) + else: + w = 1.0 / (1.0 + lambda_ * (tmb_fc - tao)) + + # Create weight matrix (same weight for all genes per sample) + W = np.tile(w, (n_genes, 1)) + + return W + + +def generate_w_block( + al: AlterationLandscape, + lambda_: float = 0.3, + tao: float = 1.0 +) -> Dict[str, Any]: + """ + Generate block-wise weight matrices. + + Computes median TMB per sample block, then the mean of medians as + reference. Generates weight matrix for each block based on TMB + fold-change from this reference. + + Parameters + ---------- + al : AlterationLandscape + AlterationLandscape object with TMB data. + lambda_ : float, optional + Weight penalty factor. Default 0.3. + tao : float, optional + Fold-change threshold. Default 1.0. + + Returns + ------- + Dict[str, Any] + Dictionary containing: + - 'W_block': Dict of weight matrices per block + - 'W': Combined weight matrix (all blocks concatenated) + - 'W_median': Dict of median TMB per block + - 'mean_TMB': Mean of median TMBs (reference value) + + Examples + -------- + >>> al = AlterationLandscape(am_data) + >>> weights = generate_w_block(al, lambda_=0.3, tao=1.0) + >>> 'W' in weights + True + """ + blocks = al.get_blocks() + sample_blocks = blocks['sample.blocks'] + + W_block = {} + W_combined = None + median_tmb = {} + mean_tmb = 0.0 + + # Get total TMB + total_tmb_df = al.tmb['total'] + total_tmb = total_tmb_df.set_index('sample')['mutation'] + + # Get feature names and count + feature_names = al.am['full'].index.tolist() + n_genes = len(feature_names) + + # First pass: calculate median TMB per block + for block_name, sample_list in sample_blocks.items(): + # Get TMB values for samples in this block + block_tmb = total_tmb.reindex(sample_list).dropna() + + if len(block_tmb) > 0: + median_val = np.median(block_tmb.values) + else: + median_val = 0.0 + + median_tmb[block_name] = median_val + mean_tmb += median_val + + # Calculate mean of medians + n_blocks = len(sample_blocks) + if n_blocks > 0: + mean_tmb = mean_tmb / n_blocks + else: + mean_tmb = 1.0 # Avoid division by zero + + # Second pass: generate weight matrices + for block_name, sample_list in sample_blocks.items(): + # Get TMB values for samples in this block + block_tmb = total_tmb.reindex(sample_list).dropna() + valid_samples = block_tmb.index.tolist() + + if len(valid_samples) == 0: + continue + + # Generate weight matrix for this block + W = generate_w_mean_tmb( + block_tmb.values, + mean_tmb, + n_genes, + lambda_=lambda_, + tao=tao + ) + + # Create DataFrame with proper labels + W_df = pd.DataFrame(W, index=feature_names, columns=valid_samples) + W_block[block_name] = W_df + + # Concatenate to combined matrix + if W_combined is None: + W_combined = W_df + else: + W_combined = pd.concat([W_combined, W_df], axis=1) + + # Ensure column order matches the full AM + if W_combined is not None: + full_samples = al.am['full'].columns.tolist() + # Only keep samples that are in W_combined + common_samples = [s for s in full_samples if s in W_combined.columns] + W_combined = W_combined[common_samples] + + return { + 'W_block': W_block, + 'W': W_combined, + 'W_median': median_tmb, + 'mean_TMB': mean_tmb + } diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..cf11f5c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,184 @@ +""" +Pytest configuration and fixtures for SelectSim tests. + +This module provides fixtures for loading test data, including bundled +real LUAD (lung adenocarcinoma) data exported from the R package's +example datasets. +""" + +import pytest +import numpy as np +import pandas as pd +from pathlib import Path + + +# Path to bundled test data (parquet exports of the R package's example +# LUAD datasets). Self-contained within this repo -- no dependency on a +# sibling checkout of the R package. +DATA_PATH = Path(__file__).parent / "data" + + +@pytest.fixture +def simple_test_data(): + """ + Create simple synthetic test data for unit tests. + + Returns a small dataset with known properties for testing + individual functions without requiring the full R data. + """ + np.random.seed(42) + + n_genes = 10 + n_samples = 20 + + # Create binary mutation matrices + missense = pd.DataFrame( + np.random.randint(0, 2, (n_genes, n_samples)), + index=[f"gene{i}" for i in range(n_genes)], + columns=[f"sample{j}" for j in range(n_samples)] + ) + + truncating = pd.DataFrame( + np.random.randint(0, 2, (n_genes, n_samples)), + index=[f"gene{i}" for i in range(n_genes)], + columns=[f"sample{j}" for j in range(n_samples)] + ) + + # Create TMB data + tmb_missense = pd.DataFrame({ + 'sample': [f"sample{j}" for j in range(n_samples)], + 'mutation': np.random.randint(10, 100, n_samples) + }) + + tmb_truncating = pd.DataFrame({ + 'sample': [f"sample{j}" for j in range(n_samples)], + 'mutation': np.random.randint(5, 50, n_samples) + }) + + # Create input structure + M = { + "M": { + "missense": missense, + "truncating": truncating + }, + "tmb": { + "missense": tmb_missense, + "truncating": tmb_truncating + } + } + + # Sample covariates + sample_class = { + f"sample{j}": "TypeA" if j < 10 else "TypeB" + for j in range(n_samples) + } + + # Alteration covariates + alteration_class = { + f"gene{i}": "MUT" for i in range(n_genes) + } + + return { + "M": M, + "sample_class": sample_class, + "alteration_class": alteration_class + } + + +@pytest.fixture(scope="session") +def luad_run_data(): + """ + Load the bundled real LUAD input data and assemble it into the exact + input structure ``selectX()`` expects (see README quick-start / the + ``selectX`` docstring in ``selectsim/selectsim.py``): + + { + "M": { + "M": {"missense": genes x samples binary df, + "truncating": genes x samples binary df}, + "tmb": {"missense": df with 'sample'/'mutation' cols, + "truncating": df with 'sample'/'mutation' cols}, + }, + "sample_class": {sample_id: class, ...}, + "alteration_class": {gene: class, ...}, + } + + The underlying parquet files are exports of the R package's bundled + LUAD example data (396 genes x 502 samples), so results are directly + comparable to a real R ``SelectSim::selectX()`` run (see + ``luad_expected_result``). + """ + missense = pd.read_parquet(DATA_PATH / "luad_missense.parquet") + truncating = pd.read_parquet(DATA_PATH / "luad_truncating.parquet") + tmb_missense = pd.read_parquet(DATA_PATH / "luad_tmb_missense.parquet") + tmb_truncating = pd.read_parquet(DATA_PATH / "luad_tmb_truncating.parquet") + sample_class_df = pd.read_parquet(DATA_PATH / "luad_sample_class.parquet") + alteration_class_df = pd.read_parquet(DATA_PATH / "luad_alteration_class.parquet") + + M = { + "M": { + "missense": missense, + "truncating": truncating, + }, + "tmb": { + "missense": tmb_missense, + "truncating": tmb_truncating, + }, + } + + sample_class = dict(zip(sample_class_df["sample"], sample_class_df["sample_class"])) + alteration_class = dict(zip(alteration_class_df["gene"], alteration_class_df["alteration_class"])) + + return { + "M": M, + "sample_class": sample_class, + "alteration_class": alteration_class, + } + + +@pytest.fixture(scope="session") +def luad_expected_result(): + """ + Load the reference R ``SelectSim::selectX()`` output for the bundled + LUAD data, produced by running (in R): + + result <- selectX( + M = luad_run_data$M, sample.class = luad_run_data$sample.class, + alteration.class = luad_run_data$alteration.class, + n.cores = 1, min.freq = 10, n.permut = 1000, + lambda = 0.3, tau = 1, maxFDR = 0.25 + ) + + 253 rows x 22 columns; see ``TestSelectXWithRData`` for how this is + used to validate the Python port. + """ + return pd.read_parquet(DATA_PATH / "luad_result.parquet") + + +@pytest.fixture(scope="session") +def luad_selectx_result(luad_run_data): + """ + Run the Python ``selectX()`` on the bundled LUAD data with the same + parameters used to produce the R reference result (see + ``luad_expected_result``), so the (slow-ish) analysis only runs once + per test session and can be reused by multiple assertions. + + ``n_permut=1000`` on this 23-gene (post min_freq filter) x 502-sample + dataset runs in ~1-2s in this implementation, so there is no need to + shrink it for test speed. + """ + from selectsim import selectX + + return selectX( + luad_run_data["M"], + luad_run_data["sample_class"], + luad_run_data["alteration_class"], + n_cores=1, + min_freq=10, + n_permut=1000, + lambda_var=0.3, + tao=1.0, + max_fdr=0.25, + verbose=False, + seed=42, + ) diff --git a/tests/data/luad_alteration_class.parquet b/tests/data/luad_alteration_class.parquet new file mode 100644 index 0000000..fb3400e Binary files /dev/null and b/tests/data/luad_alteration_class.parquet differ diff --git a/tests/data/luad_maf.parquet b/tests/data/luad_maf.parquet new file mode 100644 index 0000000..e822961 Binary files /dev/null and b/tests/data/luad_maf.parquet differ diff --git a/tests/data/luad_missense.parquet b/tests/data/luad_missense.parquet new file mode 100644 index 0000000..c850316 Binary files /dev/null and b/tests/data/luad_missense.parquet differ diff --git a/tests/data/luad_result.parquet b/tests/data/luad_result.parquet new file mode 100644 index 0000000..2707307 Binary files /dev/null and b/tests/data/luad_result.parquet differ diff --git a/tests/data/luad_sample_class.parquet b/tests/data/luad_sample_class.parquet new file mode 100644 index 0000000..ba7465c Binary files /dev/null and b/tests/data/luad_sample_class.parquet differ diff --git a/tests/data/luad_tmb_missense.parquet b/tests/data/luad_tmb_missense.parquet new file mode 100644 index 0000000..27797f3 Binary files /dev/null and b/tests/data/luad_tmb_missense.parquet differ diff --git a/tests/data/luad_tmb_truncating.parquet b/tests/data/luad_tmb_truncating.parquet new file mode 100644 index 0000000..a46431b Binary files /dev/null and b/tests/data/luad_tmb_truncating.parquet differ diff --git a/tests/data/luad_truncating.parquet b/tests/data/luad_truncating.parquet new file mode 100644 index 0000000..925f822 Binary files /dev/null and b/tests/data/luad_truncating.parquet differ diff --git a/tests/data/oncokb_genes.parquet b/tests/data/oncokb_genes.parquet new file mode 100644 index 0000000..4eeeb5e Binary files /dev/null and b/tests/data/oncokb_genes.parquet differ diff --git a/tests/data/oncokb_truncating_genes.parquet b/tests/data/oncokb_truncating_genes.parquet new file mode 100644 index 0000000..ed62efb Binary files /dev/null and b/tests/data/oncokb_truncating_genes.parquet differ diff --git a/tests/data/variant_catalogue.parquet b/tests/data/variant_catalogue.parquet new file mode 100644 index 0000000..fc50d9b Binary files /dev/null and b/tests/data/variant_catalogue.parquet differ diff --git a/tests/test_alteration_landscape.py b/tests/test_alteration_landscape.py new file mode 100644 index 0000000..2cfa766 --- /dev/null +++ b/tests/test_alteration_landscape.py @@ -0,0 +1,107 @@ +""" +Tests for AlterationLandscape class. +""" + +import pytest +import numpy as np +import pandas as pd + +from selectsim import AlterationLandscape + + +class TestAlterationLandscape: + """Tests for the AlterationLandscape class.""" + + def test_init_basic(self, simple_test_data): + """Test basic initialization.""" + al = AlterationLandscape( + simple_test_data["M"], + feat_covariates=simple_test_data["alteration_class"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + assert al is not None + assert 'full' in al.am + assert 'total' in al.tmb + + def test_full_matrix_creation(self, simple_test_data): + """Test that full matrix is correctly created and binarized.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + # Full matrix should be binarized + full_values = al.am['full'].values + assert np.all((full_values == 0) | (full_values == 1)) + + def test_min_freq_filtering(self, simple_test_data): + """Test that min_freq filtering works correctly.""" + # No filtering + al_no_filter = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + n_genes_no_filter = al_no_filter.am['full'].shape[0] + + # With filtering (min_freq=5) + al_with_filter = AlterationLandscape( + simple_test_data["M"], + min_freq=5, + verbose=False + ) + n_genes_with_filter = al_with_filter.am['full'].shape[0] + + # Filtering should reduce or keep same number of genes + assert n_genes_with_filter <= n_genes_no_filter + + def test_get_blocks(self, simple_test_data): + """Test get_blocks method.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + blocks = al.get_blocks() + + assert 'sample.blocks' in blocks + assert 'alteration.blocks' in blocks + assert 'TypeA' in blocks['sample.blocks'] or 'TypeB' in blocks['sample.blocks'] + + def test_tmb_aggregation(self, simple_test_data): + """Test that TMB is correctly aggregated.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + # Total TMB should be sum of individual TMBs + assert 'total' in al.tmb + assert isinstance(al.tmb['total'], pd.DataFrame) + assert 'sample' in al.tmb['total'].columns + assert 'mutation' in al.tmb['total'].columns + + def test_repr(self, simple_test_data): + """Test string representation.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + repr_str = repr(al) + assert "AlterationLandscape" in repr_str + assert "n_features" in repr_str + assert "n_samples" in repr_str + + def test_invalid_input_raises_error(self): + """Test that invalid input raises ValueError.""" + with pytest.raises(ValueError): + AlterationLandscape({"M": None}, min_freq=0) diff --git a/tests/test_gam.py b/tests/test_gam.py new file mode 100644 index 0000000..88854ac --- /dev/null +++ b/tests/test_gam.py @@ -0,0 +1,579 @@ +""" +Tests for the MAF ingestion / GAM-building utilities in ``selectsim.gam``. + +Covers: + +- Unit tests for each ``filter_maf_*`` / ``stat_maf_*`` function against + small synthetic MAF dataframes. +- Unit tests for ``maf_to_gam`` against a small synthetic MAF. +- An end-to-end integration test that reproduces the pipeline described + in ``SelectSim/vignettes/data_processing.Rmd`` on the bundled real + LUAD MAF (``tests/data/luad_maf.parquet``) and checks the resulting + GAMs and TMB tables against the bundled ground-truth exports of the R + package's ``luad_run_data`` object. +""" + +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from selectsim.gam import ( + GENIE_maf_schema, + TCGA_maf_schema, + filter_maf_column, + filter_maf_complex, + filter_maf_gene_name, + filter_maf_ignore, + filter_maf_missense, + filter_maf_mutation_type, + filter_maf_mutations, + filter_maf_sample, + filter_maf_schema, + filter_maf_truncating, + maf_to_gam, + mutation_type, + stat_maf_column, + stat_maf_gene, + stat_maf_sample, +) + +DATA_PATH = Path(__file__).parent / "data" + + +## --------------------------------------------------------------------- +## Fixtures +## --------------------------------------------------------------------- + +@pytest.fixture +def toy_maf() -> pd.DataFrame: + """A small, hand-built synthetic MAF dataframe for unit tests.""" + return pd.DataFrame( + { + "Hugo_Symbol": ["TP53", "TP53", "KRAS", "KRAS", "EGFR", "EGFR", "STK11"], + "Tumor_Sample_Barcode": ["s1", "s2", "s1", "s3", "s2", "s2", "s3"], + "Variant_Classification": [ + "Missense_Mutation", + "Nonsense_Mutation", + "Missense_Mutation", + "Silent", + "Frame_Shift_Del", + "Missense_Mutation", + "Splice_Site", + ], + "HGVSp_Short": [ + "p.R175H", + "p.R213*", + "p.G12C", + "p.P72P", + "p.E746_A750del", + "p.L858R", + "p.X100_splice", + ], + } + ) + + +## --------------------------------------------------------------------- +## filter_maf_column +## --------------------------------------------------------------------- + +class TestFilterMafColumn: + def test_inclusive_exact_match(self, toy_maf): + out = filter_maf_column(toy_maf, values="Missense_Mutation", column="Variant_Classification") + assert set(out["Variant_Classification"]) == {"Missense_Mutation"} + assert len(out) == 3 + + def test_exclusive_exact_match(self, toy_maf): + out = filter_maf_column( + toy_maf, values="Missense_Mutation", column="Variant_Classification", inclusive=False + ) + assert "Missense_Mutation" not in set(out["Variant_Classification"]) + assert len(out) == len(toy_maf) - 3 + + def test_list_of_values(self, toy_maf): + out = filter_maf_column(toy_maf, values=["TP53", "KRAS"], column="Hugo_Symbol") + assert set(out["Hugo_Symbol"]) == {"TP53", "KRAS"} + + def test_invalid_column_raises(self, toy_maf): + with pytest.raises(ValueError): + filter_maf_column(toy_maf, values="x", column="not_a_column") + + def test_deduplicates(self, toy_maf): + dup_maf = pd.concat([toy_maf, toy_maf.iloc[[0]]], ignore_index=True) + out = filter_maf_column(dup_maf, values="TP53", column="Hugo_Symbol") + assert len(out) == 2 # duplicate row collapsed + + def test_fixed_false_substring_match(self, toy_maf): + out = filter_maf_column( + toy_maf, values="Frame_Shift", column="Variant_Classification", fixed=False + ) + assert set(out["Variant_Classification"]) == {"Frame_Shift_Del"} + + +## --------------------------------------------------------------------- +## filter_maf_complex / filter_maf_mutations +## --------------------------------------------------------------------- + +class TestFilterMafComplex: + def test_join_on_combo(self, toy_maf): + combos = pd.DataFrame( + {"Hugo_Symbol": ["TP53"], "Variant_Classification": ["Missense_Mutation"]} + ) + out = filter_maf_complex( + toy_maf, combos, on=["Hugo_Symbol", "Variant_Classification"] + ) + assert len(out) == 1 + assert out.iloc[0]["Hugo_Symbol"] == "TP53" + + def test_no_match_gives_empty(self, toy_maf): + combos = pd.DataFrame({"Hugo_Symbol": ["NOTAGENE"], "Variant_Classification": ["Silent"]}) + out = filter_maf_complex(toy_maf, combos, on=["Hugo_Symbol", "Variant_Classification"]) + assert len(out) == 0 + + +class TestFilterMafMutations: + def test_gene_mutation_combo(self, toy_maf): + allowed = pd.DataFrame({"gene": ["TP53", "KRAS"], "mut": ["p.R175H", "p.G12C"]}) + out = filter_maf_mutations( + toy_maf, allowed, maf_col=["Hugo_Symbol", "HGVSp_Short"], values_col=["gene", "mut"] + ) + assert len(out) == 2 + assert set(out["Hugo_Symbol"]) == {"TP53", "KRAS"} + + def test_default_columns(self, toy_maf): + allowed = pd.DataFrame({"Hugo_Symbol": ["TP53"], "HGVSp_Short": ["p.R175H"]}) + out = filter_maf_mutations(toy_maf, allowed) + assert len(out) == 1 + + +## --------------------------------------------------------------------- +## filter_maf_sample / filter_maf_gene_name / filter_maf_mutation_type +## --------------------------------------------------------------------- + +class TestFilterMafSample: + def test_filters_by_sample(self, toy_maf): + out = filter_maf_sample(toy_maf, samples=["s1"]) + assert set(out["Tumor_Sample_Barcode"]) == {"s1"} + assert len(out) == 2 + + def test_custom_sample_col(self, toy_maf): + renamed = toy_maf.rename(columns={"Tumor_Sample_Barcode": "sample"}) + out = filter_maf_sample(renamed, samples=["s2"], sample_col="sample") + assert set(out["sample"]) == {"s2"} + + +class TestFilterMafGeneName: + def test_filters_by_gene(self, toy_maf): + out = filter_maf_gene_name(toy_maf, genes=["TP53", "EGFR"]) + assert set(out["Hugo_Symbol"]) == {"TP53", "EGFR"} + assert len(out) == 4 + + +class TestFilterMafMutationType: + def test_filters_by_variant(self, toy_maf): + out = filter_maf_mutation_type(toy_maf, variants="Missense_Mutation") + assert len(out) == 3 + + +## --------------------------------------------------------------------- +## Schema-driven filters +## --------------------------------------------------------------------- + +class TestFilterMafSchema: + def test_filters_via_schema_column(self, toy_maf): + out = filter_maf_schema( + toy_maf, + TCGA_maf_schema, + column="mutation.type", + values=TCGA_maf_schema["mutation.type"]["truncating"], + ) + assert set(out["Variant_Classification"]).issubset( + set(TCGA_maf_schema["mutation.type"]["truncating"]) + ) + + def test_default_schema(self, toy_maf): + out = filter_maf_schema(toy_maf, column="gene", values=["TP53"]) + assert set(out["Hugo_Symbol"]) == {"TP53"} + + +class TestFilterMafTruncating: + def test_default_tcga_schema(self, toy_maf): + out = filter_maf_truncating(toy_maf) + # Nonsense_Mutation, Frame_Shift_Del and Splice_Site are truncating + # per TCGA_maf_schema (Splice_Site is deliberately in both the + # truncating and missense buckets, mirroring the R schema). + assert set(out["Variant_Classification"]) == { + "Nonsense_Mutation", + "Frame_Shift_Del", + "Splice_Site", + } + + def test_custom_schema(self, toy_maf): + custom_schema = { + "column": {"mutation.type": "Variant_Classification"}, + "mutation.type": { + "truncating": ["Splice_Site"], + "missense": ["Missense_Mutation"], + "ignore": ["Silent"], + }, + } + out = filter_maf_truncating(toy_maf, schema=custom_schema) + assert set(out["Variant_Classification"]) == {"Splice_Site"} + + +class TestFilterMafMissense: + def test_default_tcga_schema(self, toy_maf): + out = filter_maf_missense(toy_maf) + # Missense_Mutation and Splice_Site are both in the missense bucket + # per TCGA_maf_schema. + assert set(out["Variant_Classification"]) == {"Missense_Mutation", "Splice_Site"} + assert len(out) == 4 + + +class TestFilterMafIgnore: + def test_default_tcga_schema(self, toy_maf): + out = filter_maf_ignore(toy_maf) + assert set(out["Variant_Classification"]) == {"Silent"} + + def test_inclusive_false_keeps_functional(self, toy_maf): + out = filter_maf_ignore(toy_maf, inclusive=False) + assert "Silent" not in set(out["Variant_Classification"]) + assert len(out) == len(toy_maf) - 1 + + +## --------------------------------------------------------------------- +## Schema / mutation_type constants +## --------------------------------------------------------------------- + +class TestSchemaConstants: + def test_mutation_type_keys(self): + assert set(mutation_type.keys()) == {"truncating", "missense", "ignore"} + assert "Nonsense_Mutation" in mutation_type["truncating"] + assert "Missense_Mutation" in mutation_type["missense"] + assert "Silent" in mutation_type["ignore"] + + def test_tcga_schema_structure(self): + assert TCGA_maf_schema["column"]["gene"] == "Hugo_Symbol" + assert TCGA_maf_schema["column"]["sample"] == "Tumor_Sample_Barcode" + assert TCGA_maf_schema["column"]["mutation.type"] == "Variant_Classification" + assert "truncating" in TCGA_maf_schema["mutation.type"] + assert "missense" in TCGA_maf_schema["mutation.type"] + assert "ignore" in TCGA_maf_schema["mutation.type"] + + def test_genie_schema_structure(self): + assert GENIE_maf_schema["column"]["gene"] == "Hugo_Symbol" + assert "truncating" in GENIE_maf_schema["mutation.type"] + assert set(GENIE_maf_schema["mutation.type"]["missense"]) == { + "Missense_Mutation", + "Splice_Site", + } + + +## --------------------------------------------------------------------- +## stat_maf_* +## --------------------------------------------------------------------- + +class TestStatMafColumn: + def test_counts(self, toy_maf): + out = stat_maf_column(toy_maf, column="Hugo_Symbol") + assert out["TP53"] == 2 + assert out["KRAS"] == 2 + assert out["EGFR"] == 2 + assert out["STK11"] == 1 + + def test_sorted_by_index(self, toy_maf): + out = stat_maf_column(toy_maf, column="Hugo_Symbol") + assert list(out.index) == sorted(out.index) + + def test_invalid_column_raises(self, toy_maf): + with pytest.raises(ValueError): + stat_maf_column(toy_maf, column="nope") + + +class TestStatMafSample: + def test_default_column(self, toy_maf): + out = stat_maf_sample(toy_maf) + assert out["s1"] == 2 + assert out["s2"] == 3 + assert out["s3"] == 2 + + +class TestStatMafGene: + def test_default_column(self, toy_maf): + out = stat_maf_gene(toy_maf) + assert out["TP53"] == 2 + + +## --------------------------------------------------------------------- +## maf_to_gam (unit-level) +## --------------------------------------------------------------------- + +class TestMafToGam: + def test_basic_shape_and_orientation(self, toy_maf): + gam = maf_to_gam(toy_maf, fill=0) + # genes x samples orientation + assert set(gam.index) == set(toy_maf["Hugo_Symbol"].unique()) + assert set(gam.columns) == set(toy_maf["Tumor_Sample_Barcode"].unique()) + + def test_binarized_values(self, toy_maf): + gam = maf_to_gam(toy_maf, fill=0) + assert gam.loc["TP53", "s1"] == 1 + assert gam.loc["TP53", "s2"] == 1 + assert gam.loc["KRAS", "s3"] == 1 + # KRAS/s2 never occurs -> 0 after fillna, since s2 and KRAS both + # appear elsewhere in the maf but not together + assert gam.fillna(0).loc["KRAS", "s2"] == 0 + + def test_missing_gene_sample_pair_is_nan_before_fill(self, toy_maf): + gam = maf_to_gam(toy_maf) + assert pd.isna(gam.loc["KRAS", "s2"]) + + def test_extra_requested_genes_filled(self, toy_maf): + gam = maf_to_gam(toy_maf, genes=["TP53", "KRAS", "BRAND_NEW_GENE"], fill=0) + assert "BRAND_NEW_GENE" in gam.index + assert (gam.loc["BRAND_NEW_GENE"] == 0).all() + + def test_extra_requested_samples_filled(self, toy_maf): + gam = maf_to_gam(toy_maf, samples=["s1", "s2", "s3", "s_new"], fill=0) + assert "s_new" in gam.columns + assert (gam["s_new"] == 0).all() + + def test_restricting_genes_drops_others(self, toy_maf): + gam = maf_to_gam(toy_maf, genes=["TP53"], fill=0) + assert list(gam.index) == ["TP53"] + + def test_not_binarized_returns_counts(self, toy_maf): + gam = maf_to_gam(toy_maf, binarize=False, fill=0) + # TP53 has exactly one mutation row for s1 + assert gam.loc["TP53", "s1"] == 1 + + def test_no_binarize_multiple_mutations_gives_count(self): + maf = pd.DataFrame( + { + "Hugo_Symbol": ["TP53", "TP53"], + "Tumor_Sample_Barcode": ["s1", "s1"], + "HGVSp_Short": ["p.R175H", "p.Y220C"], + } + ) + gam = maf_to_gam(maf, binarize=False) + assert gam.loc["TP53", "s1"] == 2 + gam_bin = maf_to_gam(maf, binarize=True) + assert gam_bin.loc["TP53", "s1"] == 1 + + +## --------------------------------------------------------------------- +## End-to-end integration: reproduce data_processing.Rmd on real LUAD data +## --------------------------------------------------------------------- + +def _build_custom_maf_schema() -> dict: + """ + The (non-default) schema used in ``SelectSim/vignettes/data_processing.Rmd`` + for the bundled LUAD MAF: it uses the short ``sample`` column (rather + than the full ``Tumor_Sample_Barcode``) and a slightly different + truncating/missense/ignore bucketing than the package's built-in + ``TCGA_maf_schema``. + """ + custom_mutation_type = { + "ignore": ["Silent", "Intron", "RNA", "3'UTR", "5'UTR", "5'Flank", "3'Flank", "IGR"], + "truncating": [ + "Frame_Shift_Del", + "Frame_Shift_Ins", + "In_Frame_Del", + "In_Frame_Ins", + "Nonsense_Mutation", + "Nonstop_Mutation", + "Splice_Region", + "Splice_Site", + "Translation_Start_Site", + ], + "missense": ["Missense_Mutation"], + } + return { + "name": "custom_maf", + "column": { + "gene": "Hugo_Symbol", + "gene.name": "Hugo_Symbol", + "sample": "sample", + "sample.name": "sample", + "mutation.type": "Variant_Classification", + "mutation": "HGVSp_Short", + }, + "mutation.type": custom_mutation_type, + } + + +@pytest.fixture(scope="module") +def luad_pipeline_inputs(): + input_maf = pd.read_parquet(DATA_PATH / "luad_maf.parquet") + oncokb_genes = pd.read_parquet(DATA_PATH / "oncokb_genes.parquet")["gene"].tolist() + variant_catalogue = pd.read_parquet(DATA_PATH / "variant_catalogue.parquet") + return input_maf, oncokb_genes, variant_catalogue + + +class TestLuadIntegration: + """ + Reproduces the pipeline in ``SelectSim/vignettes/data_processing.Rmd``: + + 1. Restrict to oncokb cancer genes. + 2. Build the truncating GAM + TMB. + 3. Build the missense (hotspot) GAM + TMB. + 4. Compare against the bundled ground-truth exports of R's + ``luad_run_data`` object. + + This was independently verified against a live run of the R package + (installed SelectSim + its bundled .rda datasets): the vignette + pipeline reproduces ``luad_run_data`` bit-for-bit *once the GAM's + internal ``NaN`` gaps (samples/genes that co-occur in the filtered + MAF elsewhere, but never together) are filled with 0*. The vignette + text itself doesn't spell out that final ``fillna(0)`` step -- it + notes it is an illustrative example and points to a separate, + unpublished pipeline for the exact production processing -- but our + R-side check (see task notes) confirmed 0 mismatches out of 198,792 + cells in both GAMs with this one extra step. + """ + + def _compute_tmb(self, maf: pd.DataFrame, all_samples, sample_col: str) -> pd.DataFrame: + counts = maf.groupby(sample_col).size() + tmb = pd.DataFrame({"sample": all_samples}) + tmb["mutation"] = tmb["sample"].map(counts).fillna(0).astype(int) + return tmb + + def test_truncating_gam_matches_ground_truth(self, luad_pipeline_inputs): + input_maf, oncokb_genes, _ = luad_pipeline_inputs + schema = _build_custom_maf_schema() + sample_col = schema["column"]["sample"] + mut_samples = input_maf[sample_col].unique().tolist() + + maf_genes = filter_maf_gene_name( + input_maf, genes=oncokb_genes, gene_col=schema["column"]["gene"] + ) + maf_trunc = filter_maf_truncating(maf_genes, schema=schema) + + trunc_gam = maf_to_gam( + maf_trunc, + sample_col=sample_col, + gene_col=schema["column"]["gene"], + value_var="Variant_Classification", + samples=mut_samples, + genes=oncokb_genes, + fun_aggregate=len, + binarize=True, + fill=0, + ).fillna(0).astype(int) + + ground_truth = pd.read_parquet(DATA_PATH / "luad_truncating.parquet") + trunc_gam = trunc_gam.loc[ground_truth.index, ground_truth.columns] + + assert trunc_gam.shape == ground_truth.shape + pd.testing.assert_frame_equal(trunc_gam, ground_truth, check_dtype=False) + + def test_missense_gam_matches_ground_truth(self, luad_pipeline_inputs): + input_maf, oncokb_genes, variant_catalogue = luad_pipeline_inputs + schema = _build_custom_maf_schema() + sample_col = schema["column"]["sample"] + gene_col = schema["column"]["gene"] + mutation_col = schema["column"]["mutation"] + mut_samples = input_maf[sample_col].unique().tolist() + + # All non-"ignore" mutations (missense + truncating + other), all genes + maf_valid = filter_maf_schema( + input_maf, + schema=schema, + column="mutation.type", + values=schema["mutation.type"]["ignore"], + inclusive=False, + ).copy() + + # Strip the leading "p." and trailing ref/alt amino acid letters to + # get a bare "position" key, e.g. "p.R175H" -> "R175" + stripped = maf_valid[mutation_col].str.slice(2) + maf_valid["HGVSp_Short_fixed"] = stripped.str.replace(r"[A-Z]*$", "", regex=True) + + maf_hotspot = filter_maf_mutations( + maf_valid, + variant_catalogue, + maf_col=[gene_col, "HGVSp_Short_fixed"], + values_col=["gene", "mut"], + ) + + missense_gam = maf_to_gam( + maf_hotspot, + sample_col=sample_col, + gene_col=gene_col, + value_var="Variant_Classification", + samples=mut_samples, + genes=oncokb_genes, + fun_aggregate=len, + binarize=True, + fill=0, + ).fillna(0).astype(int) + + ground_truth = pd.read_parquet(DATA_PATH / "luad_missense.parquet") + missense_gam = missense_gam.loc[ground_truth.index, ground_truth.columns] + + assert missense_gam.shape == ground_truth.shape + pd.testing.assert_frame_equal(missense_gam, ground_truth, check_dtype=False) + + def _assert_tmb_close(self, tmb: pd.DataFrame, ground_truth: pd.DataFrame, max_mismatch_frac: float) -> None: + # The ground-truth row order comes from an R-side quirk of maf2gam() + # (samples with >=1 mutation first, in raw-MAF order, then samples + # with zero mutations appended at the end) that isn't semantically + # meaningful downstream (AlterationLandscape only needs tmb rows + # aligned with gam columns by sample, not any particular order) -- + # so we align on 'sample' before comparing values. + aligned = tmb.set_index("sample").loc[ground_truth["sample"]].reset_index() + diff = (aligned["mutation"].to_numpy() - ground_truth["mutation"].to_numpy()) + mismatch_frac = np.mean(diff != 0) + assert mismatch_frac <= max_mismatch_frac, ( + f"{mismatch_frac:.1%} of samples mismatch (allowed {max_mismatch_frac:.1%})" + ) + # Any mismatches should be tiny (off-by-one), not a systematic bug. + assert np.all(np.abs(diff) <= 1) + + def test_truncating_tmb_matches_ground_truth(self, luad_pipeline_inputs): + """ + Compares against the bundled ``luad_tmb_truncating.parquet`` ground + truth. This does *not* come out bit-identical: re-running the + ``data_processing.Rmd`` vignette's own TMB-counting code (``%>% + count(sample)`` on ``filter_maf_truncating(input_maf, schema)``) + against a live install of the R ``SelectSim`` package and its + bundled ``.rda`` data reproduces the exact same 22/502 (~4.4%) + samples, each off by exactly 1, relative to the bundled + ``luad_run_data`` ground truth -- i.e. this is a pre-existing + discrepancy between the vignette's illustrative pipeline and + whatever produced the bundled ground truth (the vignette itself + notes it is *not* the exact production pipeline used to generate + the package's real published data; see the "NOTE" pointing to the + separate ``SelectSim_analysis`` repository), not a porting bug. + """ + input_maf, _, _ = luad_pipeline_inputs + schema = _build_custom_maf_schema() + sample_col = schema["column"]["sample"] + mut_samples = input_maf[sample_col].unique().tolist() + + input_maf_trunc = filter_maf_truncating(input_maf, schema=schema) + tmb = self._compute_tmb(input_maf_trunc, mut_samples, sample_col) + + ground_truth = pd.read_parquet(DATA_PATH / "luad_tmb_truncating.parquet") + self._assert_tmb_close(tmb, ground_truth, max_mismatch_frac=0.05) + + def test_missense_tmb_matches_ground_truth(self, luad_pipeline_inputs): + """ + Same caveat as ``test_truncating_tmb_matches_ground_truth``: the R + vignette's own code reproduces the same single off-by-one mismatch + (1/502 samples) against the bundled ground truth. + """ + input_maf, _, _ = luad_pipeline_inputs + schema = _build_custom_maf_schema() + sample_col = schema["column"]["sample"] + mut_samples = input_maf[sample_col].unique().tolist() + + missense_maf = filter_maf_mutation_type( + input_maf, variants="Missense_Mutation", variant_col=schema["column"]["mutation.type"] + ) + tmb = self._compute_tmb(missense_maf, mut_samples, sample_col) + + ground_truth = pd.read_parquet(DATA_PATH / "luad_tmb_missense.parquet") + self._assert_tmb_close(tmb, ground_truth, max_mismatch_frac=0.01) diff --git a/tests/test_io.py b/tests/test_io.py new file mode 100644 index 0000000..776b6fb --- /dev/null +++ b/tests/test_io.py @@ -0,0 +1,94 @@ +""" +Tests for selectsim.io helpers (parquet wrappers, zarr store helpers). +""" + +import numpy as np +import pandas as pd +import pytest + +from selectsim.io import ( + read_parquet, + write_parquet, + create_zarr_null_store, + open_zarr_store, +) + + +class TestParquetRoundTrip: + """Tests for read_parquet / write_parquet.""" + + def test_round_trip_preserves_data(self, tmp_path): + df = pd.DataFrame( + { + "gene": ["gene0", "gene1", "gene2"], + "count": [3, 7, 1], + "score": [0.1, 0.2, 0.3], + } + ).set_index("gene") + + path = str(tmp_path / "test.parquet") + write_parquet(df, path) + result = read_parquet(path) + + pd.testing.assert_frame_equal(result, df) + + def test_round_trip_without_index(self, tmp_path): + df = pd.DataFrame({"a": [1, 2, 3], "b": ["x", "y", "z"]}) + + path = str(tmp_path / "test_noindex.parquet") + write_parquet(df, path, index=False) + result = read_parquet(path) + + pd.testing.assert_frame_equal(result, df) + + def test_write_parquet_creates_file(self, tmp_path): + df = pd.DataFrame({"a": [1, 2]}) + path = tmp_path / "created.parquet" + + assert not path.exists() + write_parquet(df, str(path)) + assert path.exists() + + +class TestZarrStoreHelpers: + """Tests for create_zarr_null_store / open_zarr_store.""" + + def test_create_zarr_null_store_shape_and_dtype(self, tmp_path): + zarr = pytest.importorskip("zarr", reason="zarr not installed") + + path = str(tmp_path / "store.zarr") + z = create_zarr_null_store( + path, n_permut=4, n_genes=6, n_samples=9, chunk_permut=2 + ) + + assert isinstance(z, zarr.core.Array) + assert z.shape == (4, 6, 9) + assert z.dtype == np.dtype("float64") + assert z.chunks == (2, 6, 9) + + def test_create_and_write_then_reopen(self, tmp_path): + pytest.importorskip("zarr", reason="zarr not installed") + + path = str(tmp_path / "store2.zarr") + z = create_zarr_null_store(path, n_permut=3, n_genes=2, n_samples=5) + + rng = np.random.default_rng(0) + data = rng.integers(0, 2, size=(3, 2, 5)).astype(np.float64) + for i in range(3): + z[i, :, :] = data[i] + + reopened = open_zarr_store(path, mode="r") + assert reopened.shape == (3, 2, 5) + for i in range(3): + assert np.allclose(np.asarray(reopened[i]), data[i]) + + def test_open_zarr_store_lazy_import_does_not_require_zarr_at_module_import(self): + # Importing selectsim.io should never require zarr to be installed + # (it's an optional [storage] extra); only calling the zarr-backed + # functions should trigger the import. + import importlib + import selectsim.io as io_module + + importlib.reload(io_module) + assert hasattr(io_module, "create_zarr_null_store") + assert hasattr(io_module, "open_zarr_store") diff --git a/tests/test_null_model.py b/tests/test_null_model.py new file mode 100644 index 0000000..aa7e09b --- /dev/null +++ b/tests/test_null_model.py @@ -0,0 +1,297 @@ +""" +Tests for null model generation functions. +""" + +import pytest +import numpy as np + +from selectsim import ( + AlterationLandscape, + template_obj_gen, + generate_w_block, + null_model_parallel, + retrieve_outliers +) + + +class TestNullModelParallel: + """Tests for null_model_parallel function.""" + + def test_returns_list(self, simple_test_data): + """Test that function returns list of matrices.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=10, seed=42, verbose=False + ) + + assert isinstance(null, list) + assert len(null) == 10 + + def test_matrix_shape(self, simple_test_data): + """Test that simulated matrices have correct shape.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=5, seed=42, verbose=False + ) + + expected_shape = al.am['full'].shape + for mat in null: + assert mat.shape == expected_shape + + def test_binary_values(self, simple_test_data): + """Test that simulated matrices are binary.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=5, seed=42, verbose=False + ) + + for mat in null: + assert np.all((mat == 0) | (mat == 1)) + + def test_reproducibility(self, simple_test_data): + """Test that same seed gives same results.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null1 = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=5, seed=42, verbose=False + ) + + null2 = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=5, seed=42, verbose=False + ) + + for m1, m2 in zip(null1, null2): + assert np.allclose(m1, m2) + + def test_row_sums_preserved(self, simple_test_data): + """Test that gene frequencies are approximately preserved.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=10, seed=42, verbose=False + ) + + observed_row_sums = np.sum(al.am['full'].values, axis=1) + + for mat in null: + sim_row_sums = np.sum(mat, axis=1) + # Row sums should be exactly preserved + assert np.allclose(sim_row_sums, observed_row_sums) + + +class TestRetrieveOutliers: + """Tests for retrieve_outliers function.""" + + def test_returns_boolean_array(self, simple_test_data): + """Test that function returns boolean array.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=20, seed=42, verbose=False + ) + + obj = {'al': al, 'null': null} + + outliers = retrieve_outliers(obj, n_sim=20) + + assert isinstance(outliers, np.ndarray) + assert outliers.dtype == bool + assert len(outliers) == 20 + + def test_outlier_fraction(self, simple_test_data): + """Test that approximately 10% are marked as outliers.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_cores=1, n_permut=100, seed=42, verbose=False + ) + + obj = {'al': al, 'null': null} + + outliers = retrieve_outliers(obj, n_sim=100) + + # Should have approximately 10% outliers (with some tolerance) + # Due to discrete thresholding, may be slightly higher + outlier_fraction = np.sum(outliers) / len(outliers) + assert 0.05 <= outlier_fraction <= 0.20 + + +class TestNullModelParallelZarrStore: + """Tests for null_model_parallel(store='zarr').""" + + def test_zarr_store_returns_zarr_array(self, simple_test_data, tmp_path): + """store='zarr' should write to disk and return a zarr.Array.""" + zarr = pytest.importorskip("zarr", reason="zarr not installed") + + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + store_path = str(tmp_path / "null_store.zarr") + null = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=6, seed=42, verbose=False, + store="zarr", store_path=store_path + ) + + assert isinstance(null, zarr.core.Array) + assert len(null) == 6 + expected_shape = al.am['full'].shape + assert null.shape == (6,) + expected_shape + + # Re-open from disk independently to confirm persistence. + reopened = zarr.open(store_path, mode="r") + assert reopened.shape == null.shape + assert np.allclose(np.asarray(reopened[0]), np.asarray(null[0])) + + def test_zarr_store_matches_memory_store_numerically(self, simple_test_data, tmp_path): + """With the same seed/backend, zarr-stored and in-memory results + should be numerically identical (same numpy backend RNG stream).""" + pytest.importorskip("zarr", reason="zarr not installed") + + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + null_memory = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=5, seed=42, verbose=False, store="memory" + ) + + store_path = str(tmp_path / "null_store2.zarr") + null_zarr = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=5, seed=42, verbose=False, + store="zarr", store_path=store_path + ) + + for i in range(5): + assert np.allclose(null_memory[i], np.asarray(null_zarr[i])) + + def test_zarr_store_works_with_retrieve_outliers(self, simple_test_data, tmp_path): + """retrieve_outliers should transparently accept a zarr.Array as + the 'null' entry, not just a Python list.""" + pytest.importorskip("zarr", reason="zarr not installed") + + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + store_path = str(tmp_path / "null_store3.zarr") + null_zarr = null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=30, seed=42, verbose=False, + store="zarr", store_path=store_path + ) + + obj = {'al': al, 'null': null_zarr} + outliers = retrieve_outliers(obj, n_sim=30) + + assert isinstance(outliers, np.ndarray) + assert outliers.dtype == bool + assert len(outliers) == 30 + + def test_zarr_store_requires_store_path(self, simple_test_data): + """store='zarr' without store_path should raise a clear error.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + with pytest.raises(ValueError): + null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=2, seed=42, verbose=False, store="zarr" + ) + + def test_invalid_store_raises(self, simple_test_data): + """An unknown store name should raise a clear error.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + temp_data = template_obj_gen(al) + W = generate_w_block(al) + + with pytest.raises(ValueError): + null_model_parallel( + al, temp_data['temp_mat'], W['W'], + n_permut=2, seed=42, verbose=False, store="not_a_store" + ) diff --git a/tests/test_plotting.py b/tests/test_plotting.py new file mode 100644 index 0000000..e1067c0 --- /dev/null +++ b/tests/test_plotting.py @@ -0,0 +1,538 @@ +""" +Tests for selectsim/plotting.py. + +Uses small, synthetic selectX()-shaped objects (rather than running the +full algorithm) to smoke-test each plotting function: theme_publication, +obs_exp_scatter, overlap_pair_extract, ridge_plot_ed, ridge_plot_ed_compare. +""" + +from itertools import combinations +from types import SimpleNamespace +from unittest import mock + +import matplotlib + +matplotlib.use("Agg") + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import pytest + +from selectsim.plotting import ( + theme_publication, + SIG_COLORS, + obs_exp_scatter, + overlap_pair_extract, + ridge_plot_ed, + ridge_plot_ed_compare, + oncoprint_pair, + oncoprint, + significance_heatmap, +) + + +GENES = ["G1", "G2", "G3", "G4", "G5"] +SAMPLES = [f"s{i}" for i in range(12)] +N_SIM = 25 + + +def _make_obj(seed=0): + """Build a small synthetic selectX()['obj']-shaped dict.""" + rng = np.random.default_rng(seed) + n = len(GENES) + + al_full = pd.DataFrame( + rng.integers(0, 2, size=(n, len(SAMPLES))), + index=GENES, + columns=SAMPLES, + ) + al = SimpleNamespace(am={"full": al_full}) + + # Per-permutation symmetric "weighted overlap" matrices with a known + # baseline mean so overlap_pair_extract's output is checkable. + wrobs_co = [] + for _ in range(N_SIM): + m = rng.normal(loc=5.0, scale=1.0, size=(n, n)) + m = (m + m.T) / 2.0 + wrobs_co.append(m) + + return {"al": al, "wrobs_co": wrobs_co, "nSim": N_SIM} + + +def _make_result_df(obj): + """Build a synthetic result DataFrame for all gene pairs in GENES.""" + rows = [] + for g1, g2 in combinations(GENES, 2): + vals = overlap_pair_extract(g1, g2, obj) + w_overlap = float(np.mean(vals)) + 3.0 # "observed" above background + w_r_overlap = float(np.mean(vals)) + rows.append( + { + "SFE_1": g1, + "SFE_2": g2, + "name": f"{g1} - {g2}", + "w_overlap": w_overlap, + "w_r_overlap": w_r_overlap, + "type": "CO", + "FDR": True, + } + ) + df = pd.DataFrame(rows) + # Make a couple of pairs ME / non-significant for colour-path coverage. + df.loc[0, "type"] = "ME" + df.loc[1, "FDR"] = False + return df + + +@pytest.fixture +def obj(): + return _make_obj() + + +@pytest.fixture +def result_df(obj): + return _make_result_df(obj) + + +# --------------------------------------------------------------------------- +# theme_publication +# --------------------------------------------------------------------------- + +def test_theme_publication_returns_dict(): + rc = theme_publication(base_size=14, apply=False) + assert isinstance(rc, dict) + assert rc["font.size"] == 14 + assert rc["axes.labelweight"] == "bold" + assert rc["figure.facecolor"] == "white" + + +def test_theme_publication_applies_rcparams(): + original = plt.rcParams["font.size"] + try: + theme_publication(base_size=21, apply=True) + assert plt.rcParams["font.size"] == 21 + finally: + plt.rcParams["font.size"] = original + + +# --------------------------------------------------------------------------- +# obs_exp_scatter +# --------------------------------------------------------------------------- + +def test_obs_exp_scatter_returns_figure(result_df): + fig = obs_exp_scatter(result_df, title="Synthetic") + assert isinstance(fig, matplotlib.figure.Figure) + + ax = fig.axes[0] + assert ax.get_xlabel() == "Random weighted co-mutation (log10)" + assert ax.get_ylabel() == "Actual weighted co-mutation(log10)" + assert ax.get_title() == "Synthetic" + + # Number of scatter points across all colour groups equals input rows. + n_points = sum(coll.get_offsets().shape[0] for coll in ax.collections) + assert n_points == len(result_df) + + +def test_obs_exp_scatter_missing_columns_raises(): + bad_df = pd.DataFrame({"foo": [1, 2, 3]}) + with pytest.raises(ValueError): + obs_exp_scatter(bad_df, title="bad") + + +# --------------------------------------------------------------------------- +# significance_heatmap +# --------------------------------------------------------------------------- + +def _make_heatmap_result_df(): + """ + A tiny, known CO/ME/NS trio (plus the untested pairs a full 4-gene + combination set implies) for exact colour/alpha assertions. + """ + return pd.DataFrame( + [ + {"SFE_1": "G1", "SFE_2": "G2", "type": "CO", "FDR": True, "nES": 2.0}, + {"SFE_1": "G1", "SFE_2": "G3", "type": "ME", "FDR": True, "nES": -4.0}, + {"SFE_1": "G2", "SFE_2": "G3", "type": "CO", "FDR": False, "nES": 0.5}, + {"SFE_1": "G1", "SFE_2": "G4", "type": "ME", "FDR": False, "nES": -0.1}, + ] + ) + + +def test_significance_heatmap_returns_figure(): + df = _make_heatmap_result_df() + fig = significance_heatmap(df, title="Synthetic") + assert isinstance(fig, matplotlib.figure.Figure) + assert fig.axes[0].get_title() == "Synthetic" + + +def test_significance_heatmap_missing_columns_raises(): + bad_df = pd.DataFrame({"foo": [1, 2, 3]}) + with pytest.raises(ValueError): + significance_heatmap(bad_df) + + +def test_significance_heatmap_unknown_gene_raises(): + df = _make_heatmap_result_df() + with pytest.raises(KeyError): + significance_heatmap(df, genes=["G1", "G2", "NOT_A_GENE"]) + + +def test_significance_heatmap_cell_colors_match_sig_colors(): + df = _make_heatmap_result_df() + genes = ["G1", "G2", "G3", "G4"] + fig = significance_heatmap(df, genes=genes) + ax = fig.axes[0] + rgba = ax.get_images()[0].get_array() + + idx = {g: i for i, g in enumerate(genes)} + max_abs_nes = df["nES"].abs().max() + + def expected_alpha(nes): + return 0.2 + 0.8 * (abs(nes) / max_abs_nes) + + # CO pair (significant): forestgreen at full-strength alpha (nES=2.0 is + # the largest |nES| in this fixture, so alpha == 1.0). + r, c = sorted((idx["G1"], idx["G2"])) + np.testing.assert_allclose( + rgba[r, c], matplotlib.colors.to_rgba("forestgreen", alpha=expected_alpha(2.0)) + ) + + # ME pair (significant, strongest effect): purple. + r, c = sorted((idx["G1"], idx["G3"])) + np.testing.assert_allclose( + rgba[r, c], matplotlib.colors.to_rgba("purple", alpha=expected_alpha(-4.0)) + ) + + # Non-significant pair: neutral fill at full alpha regardless of nES. + r, c = sorted((idx["G2"], idx["G3"])) + np.testing.assert_allclose( + rgba[r, c], matplotlib.colors.to_rgba(SIG_COLORS["NS"], alpha=1.0) + ) + + # Diagonal and lower-triangle cells stay fully transparent (blank). + np.testing.assert_allclose(rgba[0, 0], [0.0, 0.0, 0.0, 0.0]) + np.testing.assert_allclose(rgba[idx["G3"], idx["G1"]], [0.0, 0.0, 0.0, 0.0]) + + +def test_significance_heatmap_defaults_gene_order_by_frequency(): + df = _make_heatmap_result_df() + fig = significance_heatmap(df) + ax = fig.axes[0] + labels = [t.get_text() for t in ax.get_xticklabels()] + # G1 appears in 3 of the 4 rows -- most frequent, so it should sort first. + assert labels[0] == "G1" + + +# --------------------------------------------------------------------------- +# overlap_pair_extract +# --------------------------------------------------------------------------- + +def test_overlap_pair_extract_length_and_values(obj): + vals = overlap_pair_extract("G1", "G2", obj) + assert isinstance(vals, np.ndarray) + assert len(vals) == N_SIM + + expected = np.array([m[0, 1] for m in obj["wrobs_co"]]) + np.testing.assert_allclose(vals, expected) + + +def test_overlap_pair_extract_symmetric(obj): + v12 = overlap_pair_extract("G1", "G2", obj) + v21 = overlap_pair_extract("G2", "G1", obj) + np.testing.assert_allclose(v12, v21) + + +def test_overlap_pair_extract_unknown_gene_raises(obj): + with pytest.raises(KeyError): + overlap_pair_extract("G1", "NOT_A_GENE", obj) + + +# --------------------------------------------------------------------------- +# ridge_plot_ed +# --------------------------------------------------------------------------- + +def test_ridge_plot_ed_returns_figure(obj, result_df): + subset = result_df.head(3) + fig = ridge_plot_ed(subset, obj) + assert isinstance(fig, matplotlib.figure.Figure) + + ax = fig.axes[0] + assert ax.get_xlabel() == "Weighted overlap" + assert len(ax.get_yticklabels()) == len(subset) + assert [t.get_text() for t in ax.get_yticklabels()] == subset["name"].tolist() + + +def test_ridge_plot_ed_colours_ME_pairs_purple(obj, result_df): + subset = result_df.head(3) + assert subset.iloc[0]["type"] == "ME" + fig = ridge_plot_ed(subset, obj) + ax = fig.axes[0] + colors = [t.get_color() for t in ax.get_yticklabels()] + assert colors[0] == "purple" + assert colors[1] == "forestgreen" + + +def test_ridge_plot_ed_empty_raises(obj, result_df): + with pytest.raises(ValueError): + ridge_plot_ed(result_df.iloc[0:0], obj) + + +def test_ridge_plot_ed_savefig(obj, result_df, tmp_path): + fig = ridge_plot_ed(result_df.head(4), obj) + out_path = tmp_path / "ridge_plot_ed.png" + fig.savefig(out_path) + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +# --------------------------------------------------------------------------- +# ridge_plot_ed_compare +# --------------------------------------------------------------------------- + +def test_ridge_plot_ed_compare_returns_figure(result_df): + obj1 = _make_obj(seed=1) + obj2 = _make_obj(seed=2) + + compare_df = result_df.head(3).copy() + compare_df["dataset1_w_overlap"] = compare_df["w_overlap"] + compare_df["dataset2_w_overlap"] = compare_df["w_overlap"] - 1.0 + + fig = ridge_plot_ed_compare(compare_df, obj1, obj2, "RunA", "RunB") + assert isinstance(fig, matplotlib.figure.Figure) + + ax = fig.axes[0] + assert ax.get_xlabel() == "Weighted overlap" + assert len(ax.get_yticklabels()) == len(compare_df) + + legend_labels = [t.get_text() for t in ax.get_legend().get_texts()] + assert "RunA" in legend_labels + assert "RunB" in legend_labels + + +def test_ridge_plot_ed_compare_savefig(result_df, tmp_path): + obj1 = _make_obj(seed=3) + obj2 = _make_obj(seed=4) + + compare_df = result_df.head(3).copy() + compare_df["dataset1_w_overlap"] = compare_df["w_overlap"] + compare_df["dataset2_w_overlap"] = compare_df["w_overlap"] - 1.0 + + fig = ridge_plot_ed_compare(compare_df, obj1, obj2, "RunA", "RunB") + out_path = tmp_path / "ridge_plot_ed_compare.png" + fig.savefig(out_path) + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +# --------------------------------------------------------------------------- +# oncoprint_pair +# --------------------------------------------------------------------------- + +def _make_oncoprint_obj(seed=0): + """Build a synthetic selectX()['obj']-shaped dict with everything + oncoprint_pair needs: al.am['full'], al.tmb['total'], obj['null'], + obj['W']['W'].""" + rng = np.random.default_rng(seed) + n_genes, n_samples = len(GENES), len(SAMPLES) + + al_full = pd.DataFrame( + rng.integers(0, 2, size=(n_genes, n_samples)), + index=GENES, columns=SAMPLES, + ) + tmb_total = pd.DataFrame({ + "sample": SAMPLES, + "mutation": rng.integers(10, 200, size=n_samples).astype(float), + }) + al = SimpleNamespace(am={"full": al_full}, tmb={"total": tmb_total}) + + null = [ + rng.integers(0, 2, size=(n_genes, n_samples)).astype(float) + for _ in range(5) + ] + W = pd.DataFrame( + rng.uniform(0.5, 1.5, size=(n_genes, n_samples)), index=GENES, columns=SAMPLES, + ) + + return {"al": al, "null": null, "W": {"W": W}} + + +def test_oncoprint_pair_returns_figure(): + obj = _make_oncoprint_obj(seed=0) + fig = oncoprint_pair("G1", "G2", obj, simulation_index=0) + assert isinstance(fig, matplotlib.figure.Figure) + # 2 panels (Observed, Simulation) x 3 rows (TMB, gene1, gene2) = 6 axes + assert len(fig.axes) == 6 + assert fig._suptitle.get_text() == "G1 – G2" + + +def test_oncoprint_pair_highlight_count_matches_raw_overlap(): + obj = _make_oncoprint_obj(seed=1) + al_full = obj["al"].am["full"] + obs1 = al_full.loc["G1"].to_numpy() > 0 + obs2 = al_full.loc["G3"].to_numpy() > 0 + expected = int(np.sum(obs1 & obs2)) + + fig = oncoprint_pair("G1", "G3", obj, simulation_index=0) + ax_g2_observed = fig.axes[2] # Observed panel: TMB(0), gene1(1), gene2(2) + texts = [t.get_text() for t in ax_g2_observed.texts] + if expected > 0: + assert texts == [str(expected)] + else: + assert texts == [] + + +def test_oncoprint_pair_does_not_sort_by_tmb(): + # Samples are grouped by mutation status only; within a group they keep + # their original column order -- no TMB (or any other) sort is applied + # anywhere. Spy on np.argsort/np.sort to confirm no sort call happens. + obj = _make_oncoprint_obj(seed=7) + with mock.patch("numpy.argsort", wraps=np.argsort) as spy_argsort, \ + mock.patch("numpy.sort", wraps=np.sort) as spy_sort: + oncoprint_pair("G1", "G2", obj, simulation_index=0) + spy_argsort.assert_not_called() + spy_sort.assert_not_called() + + +def test_oncoprint_pair_preserves_natural_order_within_group(): + # Within the "both altered" group, samples must appear in their + # original column order, not reordered by TMB (assigned here in the + # opposite order of the natural column order, as a trap). + n_genes, n_samples = len(GENES), len(SAMPLES) + al_full = pd.DataFrame(0, index=GENES, columns=SAMPLES) + al_full.loc["G1", :] = 1 # primary (higher frequency): altered everywhere + both_samples = [SAMPLES[1], SAMPLES[3], SAMPLES[7]] # natural order: 1 < 3 < 7 + al_full.loc["G2", both_samples] = 1 + # TMB deliberately decreasing with natural sample index, so a + # TMB-descending sort within the group would reverse this to [7, 3, 1]. + tmb_total = pd.DataFrame({ + "sample": SAMPLES, + "mutation": np.linspace(200, 10, n_samples), + }) + al = SimpleNamespace(am={"full": al_full}, tmb={"total": tmb_total}) + null = [np.zeros((n_genes, n_samples))] + obj = {"al": al, "null": null} + + fig = oncoprint_pair("G1", "G2", obj, simulation_index=0) + ax_tmb_observed = fig.axes[0] + highlight = ax_tmb_observed.patches[0] # axvspan over the "both" group + # The "both" group is the first 3 plotted samples regardless of order; + # what we're checking is that TMB was never consulted to reorder them + # (verified directly above by spying on argsort/sort) -- here just + # sanity-check the highlight still covers exactly those 3 samples. + assert highlight.get_x() == pytest.approx(-0.5) + assert highlight.get_width() == pytest.approx(3) + + +def test_oncoprint_pair_no_gridlines_on_gene_tracks(): + # Regression test: theme_publication's major gridline at the y=0.5 tick + # used for the gene-row label must not render as a stray horizontal + # line through the gene tracks. + obj = _make_oncoprint_obj(seed=5) + fig = oncoprint_pair("G1", "G2", obj, simulation_index=0) + for ax in fig.axes[1:3]: # observed panel's gene1/gene2 tracks + assert all(not line.get_visible() for line in ax.yaxis.get_gridlines()) + + +def test_oncoprint_pair_raises_for_missing_gene(): + obj = _make_oncoprint_obj(seed=2) + with pytest.raises(KeyError): + oncoprint_pair("NOT_A_GENE", "G1", obj) + + +def test_oncoprint_pair_savefig(tmp_path): + obj = _make_oncoprint_obj(seed=3) + fig = oncoprint_pair("G2", "G4", obj) + out_path = tmp_path / "oncoprint_pair.png" + fig.savefig(out_path) + assert out_path.exists() + assert out_path.stat().st_size > 0 + + +# --------------------------------------------------------------------------- +# oncoprint (multi-gene) +# --------------------------------------------------------------------------- + +def test_oncoprint_returns_figure(): + obj = _make_oncoprint_obj(seed=0) + fig = oncoprint(GENES, obj, simulation_index=0) + assert isinstance(fig, matplotlib.figure.Figure) + # 2 panels x (1 TMB axis + 1 axis per gene) = 2 * (1 + len(GENES)) + assert len(fig.axes) == 2 * (1 + len(GENES)) + assert fig._suptitle.get_text() == f"Oncoprint (n={len(SAMPLES)})" + + +def test_oncoprint_custom_title(): + obj = _make_oncoprint_obj(seed=6) + fig = oncoprint(GENES, obj, simulation_index=0, title="LUAD oncoprint (n=502)") + assert fig._suptitle.get_text() == "LUAD oncoprint (n=502)" + + +def test_oncoprint_genes_ordered_by_decreasing_frequency(): + obj = _make_oncoprint_obj(seed=1) + al_full = obj["al"].am["full"] + freqs = (al_full.loc[GENES].to_numpy() > 0).sum(axis=1) + expected_order = [GENES[i] for i in np.argsort(-freqs, kind="stable")] + + fig = oncoprint(GENES, obj, simulation_index=0) + # Observed panel's gene axes: index 0 is TMB, 1..len(GENES) are the + # per-gene rows, top-to-bottom == decreasing-frequency order. + gene_axes = fig.axes[1:1 + len(GENES)] + plotted_order = [ax.get_yticklabels()[0].get_text() for ax in gene_axes] + assert plotted_order == expected_order + + +def test_oncoprint_simulation_panel_has_no_gene_labels(): + # Gene identity/order is shared and already labelled on the Observed + # (left) panel; the Simulation panel shouldn't repeat it. + obj = _make_oncoprint_obj(seed=7) + fig = oncoprint(GENES, obj, simulation_index=0) + n = len(GENES) + sim_gene_axes = fig.axes[1 + n + 1:1 + n + 1 + n] + for ax in sim_gene_axes: + assert ax.get_yticklabels() == [] + + +def test_oncoprint_does_not_sort_by_tmb(): + # argsort IS used (for gene-frequency and mutation-pattern ordering), + # but never with an array derived from tmb -- np.sort is never used at + # all, since even gene/pattern ordering uses argsort. + obj = _make_oncoprint_obj(seed=2) + tmb = obj["al"].tmb["total"]["mutation"].to_numpy() + + with mock.patch("numpy.sort", wraps=np.sort) as spy_sort, \ + mock.patch("numpy.argsort", wraps=np.argsort) as spy_argsort: + oncoprint(GENES, obj, simulation_index=0) + + spy_sort.assert_not_called() + for call in spy_argsort.call_args_list: + arg = call.args[0] if call.args else call.kwargs.get("a") + is_tmb_derived = ( + isinstance(arg, np.ndarray) + and arg.shape == tmb.shape + and np.issubdtype(arg.dtype, np.floating) + and (np.array_equal(arg, tmb) or np.array_equal(arg, -tmb)) + ) + assert not is_tmb_derived + + +def test_oncoprint_raises_for_missing_gene(): + obj = _make_oncoprint_obj(seed=3) + with pytest.raises(KeyError): + oncoprint(["NOT_A_GENE", "G1"], obj) + + +def test_oncoprint_raises_for_empty_null(): + obj = _make_oncoprint_obj(seed=4) + obj["null"] = [] + with pytest.raises(ValueError): + oncoprint(GENES, obj) + + +def test_oncoprint_savefig(tmp_path): + obj = _make_oncoprint_obj(seed=5) + fig = oncoprint(GENES[:3], obj, simulation_index=0) + out_path = tmp_path / "oncoprint.png" + fig.savefig(out_path) + assert out_path.exists() + assert out_path.stat().st_size > 0 diff --git a/tests/test_selectsim.py b/tests/test_selectsim.py new file mode 100644 index 0000000..48c18ae --- /dev/null +++ b/tests/test_selectsim.py @@ -0,0 +1,498 @@ +""" +Integration tests for the selectX function. + +These tests verify the complete pipeline works correctly. +""" + +import pytest +import numpy as np +import pandas as pd + +from selectsim import selectX, AlterationLandscape + + +class TestSelectXIntegration: + """Integration tests for selectX function.""" + + def test_basic_run(self, simple_test_data): + """Test that selectX runs without errors.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + assert 'obj' in result + assert 'result' in result + + def test_result_structure(self, simple_test_data): + """Test that result has expected columns.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + expected_columns = [ + 'SFE_1', 'SFE_2', 'name', + 'support_1', 'support_2', + 'freq_1', 'freq_2', + 'overlap', 'w_overlap', + 'max_overlap', 'freq_overlap', + 'r_overlap', 'w_r_overlap', + 'wES', 'wFDR', + 'nES', 'mean_r_nES', + 'nFDR', 'nFDR2', + 'cum_freq', 'type', 'FDR' + ] + + result_df = result['result'] + + for col in expected_columns: + assert col in result_df.columns, f"Missing column: {col}" + + def test_object_structure(self, simple_test_data): + """Test that object has expected components.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + obj = result['obj'] + + assert 'al' in obj + assert 'W' in obj + assert 'T' in obj + assert 'null' in obj + assert 'nSim' in obj + + def test_reproducibility(self, simple_test_data): + """Test that same seed gives same results.""" + result1 = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=20, + verbose=False, + seed=42 + ) + + result2 = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=20, + verbose=False, + seed=42 + ) + + # Results should be identical + pd.testing.assert_frame_equal( + result1['result'].reset_index(drop=True), + result2['result'].reset_index(drop=True) + ) + + def test_co_me_classification(self, simple_test_data): + """Test that pairs are classified as CO or ME.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + result_df = result['result'] + + # All types should be CO or ME + assert set(result_df['type'].unique()).issubset({'CO', 'ME'}) + + def test_fdr_boolean(self, simple_test_data): + """Test that FDR column is boolean.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + result_df = result['result'] + + assert result_df['FDR'].dtype == bool + + def test_min_freq_filtering(self, simple_test_data): + """Test that min_freq parameter filters genes.""" + result_no_filter = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + result_with_filter = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=5, + n_permut=10, + verbose=False, + seed=42 + ) + + # With filtering, should have fewer or same number of pairs + assert len(result_with_filter['result']) <= len(result_no_filter['result']) + + def test_n_permut_effect(self, simple_test_data): + """Test that n_permut affects number of null simulations.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=50, + verbose=False, + seed=42 + ) + + obj = result['obj'] + + # After outlier removal, should have ~90% of n_permut + assert 40 <= obj['nSim'] <= 50 + + def test_sorted_by_effect_size(self, simple_test_data): + """Test that results are sorted by absolute nES.""" + result = selectX( + simple_test_data["M"], + simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, + min_freq=0, + n_permut=10, + verbose=False, + seed=42 + ) + + result_df = result['result'] + + # Should be sorted by absolute nES descending + abs_nes = np.abs(result_df['nES'].values) + assert all(abs_nes[i] >= abs_nes[i+1] for i in range(len(abs_nes)-1)) + + +class TestSelectXWithRData: + """ + Integration tests comparing the Python port against a real R + ``SelectSim::selectX()`` reference run on the bundled LUAD dataset + (see ``tests/data/luad_result.parquet`` and the ``luad_*`` fixtures + in ``conftest.py``). + + IMPORTANT numeric-parity note + ------------------------------ + R and Python use different random number generators, so the null + model's permutations are NOT bit-identical between the two + implementations even when "the same" seed is used. This means: + + - Columns computed directly from the *observed* data (independent of + the null model / permutations) are expected to match R exactly + (to floating point precision): ``support_1``, ``support_2``, + ``freq_1``, ``freq_2``, ``overlap``, ``w_overlap``, + ``max_overlap``, ``freq_overlap``. These are checked with tight + numeric tolerances in ``test_luad_dimensions`` / + ``test_luad_significant_pairs``. + + - Columns derived from the null model permutations (``nES``, ``wES``, + ``FDR``, ``nFDR``, ``nFDR2``, ``type``, ...) will differ numerically + between R and Python by design. These are checked only for + *statistical/directional* agreement: same set of significant gene + pairs is plausible, top hits agree in sign/CO-vs-ME classification, + and the two implementations' effect sizes are highly correlated. + """ + + # Deterministic (null-model-independent) columns that must match the + # R reference to high numeric precision. + DETERMINISTIC_COLS = [ + "support_1", "support_2", "freq_1", "freq_2", + "overlap", "w_overlap", "max_overlap", "freq_overlap", + ] + + @staticmethod + def _pair_key(gene_a, gene_b): + """Order-independent string key for a gene pair. + + A plain string (rather than a tuple) avoids pandas interpreting + the key as a multi-axis ``(row, column)`` indexer when used with + ``.loc``. + """ + a, b = sorted((gene_a, gene_b)) + return f"{a}||{b}" + + @classmethod + def _keyed(cls, df): + """Return df indexed by an order-independent gene-pair key.""" + out = df.copy() + out["_key"] = [cls._pair_key(a, b) for a, b in zip(df["SFE_1"], df["SFE_2"])] + return out.set_index("_key") + + def test_luad_dimensions(self, luad_run_data, luad_expected_result, luad_selectx_result): + """ + Verify the input GAM shapes and the post-min_freq-filter gene + count / pair count match what produced the R reference result. + + min_freq=10 filtering is fully deterministic (depends only on the + observed alteration counts, not on the null model), so the + resulting gene set size and the number of pairwise rows in the + output are expected to match R exactly. + """ + # Raw input shapes: 396 genes x 502 samples, per the bundled data. + missense = luad_run_data["M"]["M"]["missense"] + truncating = luad_run_data["M"]["M"]["truncating"] + assert missense.shape == (396, 502) + assert truncating.shape == (396, 502) + assert missense.shape == truncating.shape + + al = luad_selectx_result["obj"]["al"] + + # After min_freq=10 filtering, 23 genes remain in this dataset, + # which combinatorially yields exactly C(23, 2) = 253 pairs -- + # matching the number of rows in the R reference result. + n_genes_filtered = al.am["full"].shape[0] + assert n_genes_filtered == 23 + assert n_genes_filtered * (n_genes_filtered - 1) // 2 == len(luad_expected_result) + + result_df = luad_selectx_result["result"] + assert len(result_df) == len(luad_expected_result) == 253 + assert result_df.shape[1] == luad_expected_result.shape[1] == 22 + + # Same set of genes appears in Python's output as in R's. + py_genes = set(result_df["SFE_1"]) | set(result_df["SFE_2"]) + r_genes = set(luad_expected_result["SFE_1"]) | set(luad_expected_result["SFE_2"]) + assert py_genes == r_genes + assert len(py_genes) == 23 + + # Every pair in R's output has a corresponding pair in Python's + # output (order-independent match on the two gene names). + py_keys = set(self._keyed(result_df).index) + r_keys = set(self._keyed(luad_expected_result).index) + assert py_keys == r_keys + + def test_luad_significant_pairs(self, luad_expected_result, luad_selectx_result): + """ + Validate numeric agreement with the R reference, split into the + two tiers described in the class docstring: exact checks for + deterministic columns, statistical/directional checks for + permutation-dependent columns. + """ + result_df = luad_selectx_result["result"] + + py = self._keyed(result_df) + r = self._keyed(luad_expected_result) + + # Both implementations must have produced the same set of gene + # pairs to be comparable row-for-row below. + assert set(py.index) == set(r.index) + r = r.loc[py.index] + + # --- Tier 1: deterministic columns must match R closely. --- + # These are computed straight from the observed alteration + # matrices (support/frequency counts, raw & weighted overlap), + # so they do not depend on the null-model permutations at all + # and should agree with R to within floating point noise. + # + # support_1/2 and freq_1/2 are gene-position-dependent (SFE_1 vs + # SFE_2 may be swapped between R and Python for a given pair), so + # compare them as the *set* of {gene: value} rather than by + # column position. + py_support = {} + py_freq = {} + for _, row in result_df.iterrows(): + py_support[row["SFE_1"]] = row["support_1"] + py_support[row["SFE_2"]] = row["support_2"] + py_freq[row["SFE_1"]] = row["freq_1"] + py_freq[row["SFE_2"]] = row["freq_2"] + + r_support = {} + r_freq = {} + for _, row in luad_expected_result.iterrows(): + r_support[row["SFE_1"]] = row["support_1"] + r_support[row["SFE_2"]] = row["support_2"] + r_freq[row["SFE_1"]] = row["freq_1"] + r_freq[row["SFE_2"]] = row["freq_2"] + + assert set(py_support) == set(r_support) + for gene in py_support: + assert py_support[gene] == pytest.approx(r_support[gene], abs=5e-5) + assert py_freq[gene] == pytest.approx(r_freq[gene], abs=5e-5) + + # Pair-symmetric deterministic columns (independent of SFE_1/2 + # ordering) should match essentially exactly. + for col in ["overlap", "w_overlap", "max_overlap", "freq_overlap"]: + diffs = np.abs(py[col].values - r[col].values) + assert np.allclose(py[col].values, r[col].values, atol=5e-5), ( + f"Deterministic column {col!r} diverged from R reference " + f"(max abs diff={diffs.max()})" + ) + + # --- Tier 2: permutation-dependent columns, statistical checks only. --- + # nES is the (permutation-derived) normalized effect size; do not + # expect exact agreement, but both implementations are estimating + # the same underlying effect from the same observed data, so they + # should be strongly correlated. + nes_corr = np.corrcoef(py["nES"].values, r["nES"].values)[0, 1] + assert nes_corr > 0.8, f"nES correlation with R too low: {nes_corr}" + + # Python's own significant calls (FDR == True) should have + # effect sizes broadly in the same range as R's significant calls. + py_sig_nes = py.loc[py["FDR"], "nES"].abs() + r_sig_nes = r.loc[r["FDR"], "nES"].abs() + assert len(py_sig_nes) > 0 + assert len(r_sig_nes) > 0 + # Same order of magnitude -- not an exact match, just a sanity + # bound so a badly-miscalibrated port would be caught. + assert py_sig_nes.median() > 0.5 * r_sig_nes.median() + + # For R's top-N most significant pairs (by |nES|), Python's CO/ME + # classification and the *sign* of nES should agree with R's -- + # this is the key directional-agreement check, since both + # implementations are extracting the same signal (co-mutation vs + # mutual exclusivity) from the same observed data. + top_n = 10 + r_top = luad_expected_result.reindex( + luad_expected_result["nES"].abs().sort_values(ascending=False).index + ).head(top_n) + + mismatches = [] + for _, row in r_top.iterrows(): + key = self._pair_key(row["SFE_1"], row["SFE_2"]) + py_row = py.loc[key] + same_sign = np.sign(py_row["nES"]) == np.sign(row["nES"]) + same_type = py_row["type"] == row["type"] + if not (same_sign and same_type): + mismatches.append((key, row["type"], row["nES"], py_row["type"], py_row["nES"])) + + assert not mismatches, ( + f"Top-{top_n} R pairs disagree with Python on CO/ME type or " + f"nES sign: {mismatches}" + ) + + +class TestSelectXZarrStore: + """ + Tests for selectX's store="zarr"/"auto" support (bounded-memory + null-model persistence), added alongside the memory/speed + optimization work. store="memory" (the default, exercised by every + other test in this file/TestSelectXWithRData's LUAD fixtures) must + stay byte-for-byte unchanged; these tests cover the new paths only. + """ + + def test_zarr_store_matches_memory_store(self, simple_test_data, tmp_path): + """store='zarr' must produce numerically identical results to + store='memory' given the same seed.""" + kwargs = dict( + n_cores=1, min_freq=0, n_permut=50, verbose=False, seed=42, + ) + r_mem = selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], store="memory", **kwargs + ) + r_zarr = selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], store="zarr", + store_path=str(tmp_path / "null.zarr"), **kwargs + ) + + assert isinstance(r_mem["obj"]["null"], list) + import zarr + assert isinstance(r_zarr["obj"]["null"], zarr.core.Array) + + pd.testing.assert_frame_equal( + r_mem["result"].reset_index(drop=True), + r_zarr["result"].reset_index(drop=True), + ) + + def test_zarr_outlier_removal_shrinks_store_in_place(self, simple_test_data, tmp_path): + """After outlier removal, a zarr-backed null model's length should + equal nSim (outliers actually dropped), not the original n_permut, + and the store should remain a valid, re-openable zarr.Array at the + same store_path (proving the store was filtered on disk via + _filter_zarr_null_store rather than left untouched).""" + store_path = tmp_path / "null.zarr" + r = selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, min_freq=0, n_permut=50, verbose=False, seed=42, + store="zarr", store_path=str(store_path), + ) + + import zarr + null = r["obj"]["null"] + assert isinstance(null, zarr.core.Array) + assert len(null) == r["obj"]["nSim"] + assert len(null) <= 50 + + reopened = zarr.open(str(store_path), mode="r") + assert len(reopened) == len(null) + + def test_store_auto_picks_memory_for_small_cohort(self, simple_test_data, tmp_path): + """store='auto' should resolve to 'memory' for a small synthetic + cohort (well under any reasonable memory budget).""" + r = selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, min_freq=0, n_permut=50, verbose=False, seed=42, + store="auto", folder=str(tmp_path), + ) + assert isinstance(r["obj"]["null"], list) + + def test_store_auto_picks_zarr_under_tight_budget(self, simple_test_data, tmp_path): + """store='auto' should resolve to 'zarr' when memory_budget_gb is + set low enough that even this small cohort's estimate exceeds it.""" + r = selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, min_freq=0, n_permut=50, verbose=False, seed=42, + store="auto", memory_budget_gb=1e-6, folder=str(tmp_path), + ) + import zarr + assert isinstance(r["obj"]["null"], zarr.core.Array) + + def test_invalid_store_raises(self, simple_test_data): + with pytest.raises(ValueError): + selectX( + simple_test_data["M"], simple_test_data["sample_class"], + simple_test_data["alteration_class"], + n_cores=1, min_freq=0, n_permut=10, verbose=False, + store="not-a-real-store", + ) diff --git a/tests/test_stats.py b/tests/test_stats.py new file mode 100644 index 0000000..831b4ff --- /dev/null +++ b/tests/test_stats.py @@ -0,0 +1,206 @@ +""" +Tests for statistics computation functions. +""" + +import pytest +import numpy as np + +from selectsim import ( + am_stats, + am_pairwise_alteration_overlap, + am_weight_pairwise_alteration_overlap, + effect_size, + estimate_fdr2, + binary_yule, + add, + matrix_to_pairwise_vector +) + + +class TestAmStats: + """Tests for am_stats function.""" + + def test_basic_stats(self): + """Test basic statistics computation.""" + am = np.array([[1, 0, 1], [0, 1, 1], [1, 1, 0]]) + + stats = am_stats(am) + + assert stats['n_samples'] == 3 + assert stats['n_alterations'] == 3 + assert stats['n_occurrences'] == 6 + + def test_alterations_per_sample(self): + """Test alterations per sample calculation.""" + am = np.array([[1, 0, 1], [1, 1, 0]]) + + stats = am_stats(am) + + expected = np.array([2, 1, 1]) + assert np.allclose(stats['alterations_per_sample'], expected) + + def test_alteration_count(self): + """Test alteration count per gene.""" + am = np.array([[1, 0, 1], [1, 1, 1]]) + + stats = am_stats(am) + + expected = np.array([2, 3]) + assert np.allclose(stats['alteration_count'], expected) + + +class TestPairwiseOverlap: + """Tests for pairwise overlap functions.""" + + def test_overlap_computation(self): + """Test basic overlap computation.""" + am = np.array([[1, 1, 0], [1, 0, 1]]) + + overlap = am_pairwise_alteration_overlap(am) + + # Gene 0 and Gene 1 overlap in sample 0 + assert overlap[0, 1] == 1 + assert overlap[1, 0] == 1 + + def test_overlap_symmetry(self): + """Test that overlap matrix is symmetric.""" + am = np.random.randint(0, 2, (5, 10)) + + overlap = am_pairwise_alteration_overlap(am) + + assert np.allclose(overlap, overlap.T) + + def test_weighted_overlap(self): + """Test weighted overlap computation.""" + am = np.array([[1, 1, 0], [1, 0, 1]]) + W = np.array([[0.5, 1.0, 0.8], [0.5, 1.0, 0.8]]) + + overlap = am_weight_pairwise_alteration_overlap(am, W) + + # Should be weighted by W values + assert overlap.shape == (2, 2) + + def test_weighted_vs_unweighted(self): + """Test that weights of 1 give same result as unweighted.""" + am = np.random.randint(0, 2, (3, 5)).astype(float) + W = np.ones_like(am) + + overlap_unweighted = am_pairwise_alteration_overlap(am) + overlap_weighted = am_weight_pairwise_alteration_overlap(am, W) + + assert np.allclose(overlap_unweighted, overlap_weighted) + + +class TestEffectSize: + """Tests for effect_size function.""" + + def test_positive_effect(self): + """Test positive effect size (co-occurrence).""" + obs = np.array([10]) + exp = np.array([5]) + + es = effect_size(obs, exp) + + assert es[0] > 0 + + def test_negative_effect(self): + """Test negative effect size (mutual exclusivity).""" + obs = np.array([5]) + exp = np.array([10]) + + es = effect_size(obs, exp) + + assert es[0] < 0 + + def test_zero_effect(self): + """Test zero effect size when obs equals exp.""" + obs = np.array([10]) + exp = np.array([10]) + + es = effect_size(obs, exp) + + assert np.isclose(es[0], 0) + + def test_scaling_factor(self): + """Test that sin(pi/4) scaling is applied.""" + obs = np.array([10]) + exp = np.array([0]) + + es = effect_size(obs, exp) + + # sin(pi/4) = sqrt(2)/2 ≈ 0.707 + assert np.isclose(es[0], 10 * np.sin(np.pi / 4)) + + +class TestEstimateFdr: + """Tests for estimate_fdr2 function.""" + + def test_fdr_bounds(self): + """Test that FDR values are between 0 and 1.""" + np.random.seed(42) + obs = np.random.uniform(0, 10, 100) + exp = np.random.uniform(0, 10, 1000) + + fdr = estimate_fdr2(obs, exp, n_sim=10, max_fdr=0.5) + + assert np.all(fdr >= 0) + assert np.all(fdr <= 1) + + def test_significant_values(self): + """Test that very high values get low FDR.""" + obs = np.array([100, 50, 10, 5, 1]) + exp = np.array([10, 8, 6, 4, 2]) # Much lower than highest obs + + fdr = estimate_fdr2(obs, exp, n_sim=1, max_fdr=1.0) + + # Highest observed value should have lowest FDR + assert fdr[0] <= fdr[4] + + +class TestBinaryYule: + """Tests for binary_yule function.""" + + def test_yule_range(self): + """Test that Yule coefficient is in valid range.""" + overlap = np.array([[5, 2], [2, 5]]) + mat = np.random.randint(0, 2, (2, 10)) + + yule = binary_yule(overlap, mat) + + # Yule should be between -1 and 1 + assert np.all(yule >= -1) + assert np.all(yule <= 1) + + def test_perfect_cooccurrence(self): + """Test Yule for perfect co-occurrence.""" + # Perfect overlap: whenever gene 0 is mutated, gene 1 is too + mat = np.array([[1, 1, 0, 0], [1, 1, 0, 0]]) + overlap = am_pairwise_alteration_overlap(mat) + + yule = binary_yule(overlap, mat) + + # Should be 1 for perfect positive association + assert yule[0, 1] > 0.9 + + +class TestUtilityFunctions: + """Tests for utility functions.""" + + def test_add_matrices(self): + """Test add function.""" + m1 = np.array([[1, 2], [3, 4]]) + m2 = np.array([[5, 6], [7, 8]]) + + result = add([m1, m2]) + + expected = np.array([[6, 8], [10, 12]]) + assert np.allclose(result, expected) + + def test_matrix_to_vector(self): + """Test matrix_to_pairwise_vector function.""" + mat = np.array([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) + + vec = matrix_to_pairwise_vector(mat) + + # Upper triangle: (0,1)=2, (0,2)=3, (1,2)=5 + assert np.allclose(vec, [2, 3, 5]) diff --git a/tests/test_template.py b/tests/test_template.py new file mode 100644 index 0000000..dad1e53 --- /dev/null +++ b/tests/test_template.py @@ -0,0 +1,102 @@ +""" +Tests for template generation functions. +""" + +import pytest +import numpy as np + +from selectsim import AlterationLandscape, generate_s, template_obj_gen + + +class TestGenerateS: + """Tests for generate_s function.""" + + def test_output_shape(self): + """Test that output has same shape as input.""" + gam = np.array([[1, 0, 1, 0], [0, 1, 1, 1], [1, 1, 0, 0]]) + weights = np.array([0.8, 1.0, 1.2, 1.0]) + + S = generate_s(gam, weights) + + assert S.shape == gam.shape + + def test_upper_bound_clipping(self): + """Test that values are clipped to upper bound.""" + gam = np.ones((2, 3)) # All 1s -> gene_freq = 1.0 + weights = np.array([2.0, 2.0, 2.0]) # High weights + + S = generate_s(gam, weights, upper_bound=1.0) + + assert np.all(S <= 1.0) + + def test_probability_values(self): + """Test that output values are probabilities.""" + gam = np.array([[1, 0, 1, 0], [0, 1, 0, 1]]) + weights = np.array([1.0, 1.0, 1.0, 1.0]) + + S = generate_s(gam, weights) + + assert np.all(S >= 0) + assert np.all(S <= 1) + + def test_gene_frequency_calculation(self): + """Test that gene frequencies are correctly computed.""" + # Gene 0 is mutated in 50% of samples + gam = np.array([[1, 1, 0, 0], [1, 1, 1, 1]]) + weights = np.ones(4) + + S = generate_s(gam, weights) + + # With uniform weights, S values should equal gene frequency + assert np.isclose(S[0, 0], 0.5) + assert np.isclose(S[1, 0], 1.0) + + +class TestTemplateObjGen: + """Tests for template_obj_gen function.""" + + def test_returns_dict(self, simple_test_data): + """Test that function returns correct structure.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = template_obj_gen(al) + + assert isinstance(result, dict) + assert 'template_obj' in result + assert 'temp_mat' in result + + def test_template_obj_structure(self, simple_test_data): + """Test template_obj structure.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = template_obj_gen(al) + template_obj = result['template_obj'] + + # Should have entries for each mutation type (excluding 'full') + for key in simple_test_data["M"]["M"].keys(): + assert key in template_obj + + def test_temp_mat_dimensions(self, simple_test_data): + """Test that temp_mat has correct dimensions.""" + al = AlterationLandscape( + simple_test_data["M"], + min_freq=0, + verbose=False + ) + + result = template_obj_gen(al) + + for key, mat in result['temp_mat'].items(): + if mat is not None: + # Should have same number of rows as genes + assert mat.shape[0] == al.am['full'].shape[0] diff --git a/tests/test_weights.py b/tests/test_weights.py new file mode 100644 index 0000000..6676b7e --- /dev/null +++ b/tests/test_weights.py @@ -0,0 +1,121 @@ +""" +Tests for weight matrix generation functions. +""" + +import pytest +import numpy as np + +from selectsim import AlterationLandscape, generate_w_mean_tmb, generate_w_block + + +class TestGenerateWMeanTmb: + """Tests for generate_w_mean_tmb function.""" + + def test_output_shape(self): + """Test that output has correct shape.""" + tmb = np.array([100, 200, 150]) + n_genes = 5 + + W = generate_w_mean_tmb(tmb, mean_tmb=150, n_genes=n_genes) + + assert W.shape == (n_genes, len(tmb)) + + def test_weight_values_bounded(self): + """Test that weights are between 0 and 1.""" + tmb = np.array([50, 100, 200, 500]) + + W = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=3) + + assert np.all(W > 0) + assert np.all(W <= 1) + + def test_low_tmb_high_weight(self): + """Test that low TMB samples get high weights.""" + tmb = np.array([50, 200]) # Low TMB, High TMB + + W = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=1, lambda_=0.3, tao=1.0) + + # Low TMB sample should have higher weight + assert W[0, 0] >= W[0, 1] + + def test_tao_threshold(self): + """Test that tao parameter works as threshold.""" + tmb = np.array([50, 100, 150]) # All below or at mean when tao=1 + + W1 = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=1, tao=1.0) + W2 = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=1, tao=2.0) + + # With higher tao, more samples should get weight 1.0 + assert np.sum(W2 == 1.0) >= np.sum(W1 == 1.0) + + def test_lambda_effect(self): + """Test that lambda controls penalty strength.""" + tmb = np.array([200]) # High TMB + + W_low_lambda = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=1, lambda_=0.1) + W_high_lambda = generate_w_mean_tmb(tmb, mean_tmb=100, n_genes=1, lambda_=0.5) + + # Higher lambda should give lower weight + assert W_low_lambda[0, 0] > W_high_lambda[0, 0] + + +class TestGenerateWBlock: + """Tests for generate_w_block function.""" + + def test_returns_dict(self, simple_test_data): + """Test that function returns correct structure.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = generate_w_block(al, lambda_=0.3, tao=1.0) + + assert isinstance(result, dict) + assert 'W_block' in result + assert 'W' in result + assert 'W_median' in result + assert 'mean_TMB' in result + + def test_combined_weight_matrix(self, simple_test_data): + """Test that combined W matrix has correct structure.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = generate_w_block(al, lambda_=0.3, tao=1.0) + + # W should have genes x samples dimensions + assert result['W'].shape[0] == al.am['full'].shape[0] + + def test_block_weights_exist(self, simple_test_data): + """Test that block-wise weights are computed.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = generate_w_block(al, lambda_=0.3, tao=1.0) + + # Should have weights for each block + assert len(result['W_block']) > 0 + + def test_mean_tmb_positive(self, simple_test_data): + """Test that mean TMB is computed correctly.""" + al = AlterationLandscape( + simple_test_data["M"], + sample_covariates=simple_test_data["sample_class"], + min_freq=0, + verbose=False + ) + + result = generate_w_block(al, lambda_=0.3, tao=1.0) + + assert result['mean_TMB'] > 0 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..33d7332 --- /dev/null +++ b/uv.lock @@ -0,0 +1,3219 @@ +version = 1 +revision = 3 +requires-python = ">=3.10, <3.13" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] + +[[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 = "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.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + +[[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/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" }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" }, +] + +[[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 = "asciitree" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/885bc91484e1aa8f618f6f0228d76d0e67000b0fdd6090673b777e311913/asciitree-0.3.3.tar.gz", hash = "sha256:4aa4b9b649f85e3fcb343363d97564aa1fb62e249677f2e18a96765145cc0f6e", size = 3951, upload-time = "2016-09-05T19:10:42.681Z" } + +[[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 = "async-lru" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/1f/989ecfef8e64109a489fff357450cb73fa73a865a92bd8c272170a6922c2/async_lru-2.3.0.tar.gz", hash = "sha256:89bdb258a0140d7313cf8f4031d816a042202faa61d0ab310a0a538baa1c24b6", size = 16332, upload-time = "2026-03-19T01:04:32.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl", hash = "sha256:eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315", size = 8403, upload-time = "2026-03-19T01:04:30.883Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[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.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/65/318323f98dbee45d42dff61d8f047181bc6f2268a9068cfad035a46be5af/beautifulsoup4-4.15.0.tar.gz", hash = "sha256:288e3ca7d54b06f2ac191970bc275c1939cb46d450b255bf6718b04aa37ab4f7", size = 632571, upload-time = "2026-06-07T16:44:20.453Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/92fcd42f1ba33e1184263f25bfabf3d27c383410470f169e4b8163bf9c17/beautifulsoup4-4.15.0-py3-none-any.whl", hash = "sha256:d6f88de62e1d4e38ecb1077eb9724cd0eff29d2a08ca16a401e9b9e93f117cf9", size = 109924, upload-time = "2026-06-07T16:44:21.566Z" }, +] + +[[package]] +name = "bleach" +version = "6.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/3c/e12ac860709702bd5ebeb9b56a4fe334f1001246ee1b8f2b7ee28912df7d/bleach-6.4.0.tar.gz", hash = "sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452", size = 204857, upload-time = "2026-06-05T13:01:13.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/9d/40b6267367182187139a4000b82a3b287d84d745bccd808e75d916920e9d/bleach-6.4.0-py3-none-any.whl", hash = "sha256:4b6b6a54fff2e69a3dde9d21cc6301220bee3c3cb792187d11403fd795031081", size = 165109, upload-time = "2026-06-05T13:01:12.504Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/e9/6d7724983b3d5a0908dbf74f64038ade77c18646ff6636ec7894fd392ce1/cffi-2.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:b65f590ef2a44640f9a05dbb548a429b4ade77913ce683ac8b1480777658a6c0", size = 183837, upload-time = "2026-07-06T21:32:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/69/aa/24580a278de21fd7322635556334d9b535f1cbc00b0a3919447cdf464c65/cffi-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164bff1657b2a74f0b6d54e11c9b375bc97b931f2ca9c43fcf875838da1570dd", size = 184226, upload-time = "2026-07-06T21:32:11.196Z" }, + { url = "https://files.pythonhosted.org/packages/88/a9/02cae418ec4beb282ace11958d9d4737793439d561fadc7e6d56f2e2b354/cffi-2.1.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:c941bb58d5a6e1c3892d86e42927ed6c180302f07e6d395d08c416e594b98b46", size = 211107, upload-time = "2026-07-06T21:32:12.328Z" }, + { url = "https://files.pythonhosted.org/packages/3b/30/c806937ed5e4c2c7ac30d9d6b76b5dc57ff8b75d83800d9bb11a8253cf2a/cffi-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a016194dbe13d14ee9556e734b772d8d67b947092b268d757fd4290e3ba2dfc2", size = 218733, upload-time = "2026-07-06T21:32:13.67Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cf/398272b8bbfd58aa314fda5a7f1cdbb26d1d78ae324a11211521315dd1f0/cffi-2.1.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03e9810d18c646077e501f661b682fbf5dee4676048527ca3cffe66faa9960dd", size = 205543, upload-time = "2026-07-06T21:32:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/45/ca/f91641185cdd90c36d317a9dc7f85e88ef8682d8b300977baff5e23c35d8/cffi-2.1.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19c54ac121cad98450b4896fa9a43ee0180d57bc4bc911a33db6cab1efab6cd3", size = 205460, upload-time = "2026-07-06T21:32:16.479Z" }, + { url = "https://files.pythonhosted.org/packages/38/66/04781a77b411f0bb5b234d62c1814754ab75ebe455ccff1b08e8d7aae98f/cffi-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d433a51f1870e43a13b6732f92aaf540ff77c2015097c78556f75a2d6c030e0", size = 218760, upload-time = "2026-07-06T21:32:17.98Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9a/bb1d5ed9c3fcae158e9f6391bf309c95d98c2ac37ed56573228471d0af5e/cffi-2.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3d7f118b5adbfdfead90c25822690b02bc8074fba949bb7858bec4ebd55adb43", size = 221230, upload-time = "2026-07-06T21:32:19.407Z" }, + { url = "https://files.pythonhosted.org/packages/41/aa/3c1409cdd26094efacd1c36c66e0a6eb9d4296e4fd4f9901b8b2042f4323/cffi-2.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5f5df567f6eb216de69be06ce55c8b714090fae02b18a3b40da8163b8c5fa9c", size = 213524, upload-time = "2026-07-06T21:32:20.828Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/74dfb7c3fc6ebbd408038476bd4c1d7e925c62614e7b9c534ecc34218288/cffi-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:11b3fb55f4f8ad92274ed26705f65d8f91457de71f5380061eb6d125a768fecd", size = 220341, upload-time = "2026-07-06T21:32:21.9Z" }, + { url = "https://files.pythonhosted.org/packages/70/b6/9003c33a3e7d2c1306f5962e646457dcfe5a8cd8fce6bbe02d7af25db783/cffi-2.1.0-cp310-cp310-win32.whl", hash = "sha256:9d72af0cf10a76a600a9690078fe31c63b9588c8e86bf9fd353f713c84b5db0f", size = 174578, upload-time = "2026-07-06T21:32:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/8a/26/710688310447531c7a22f857c7f79d9855ec18b03e04494ced723fb37e2f/cffi-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:fb62edb5bb52cca65fab91a63afa7561607120d26090a7e8fda6fb9f064726da", size = 185071, upload-time = "2026-07-06T21:32:24.671Z" }, + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/eb/da/5c4918a2d61d86fa927d716cb3d8e4626ef8dc8f605a599d32f33897f59a/cffi-2.1.0-cp311-cp311-win32.whl", hash = "sha256:64c753a0f87a256020004f37a1c8c02c480e725f910f0b2a0f3f07debd1b2479", size = 174496, upload-time = "2026-07-06T21:32:40.467Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c8/6c2de1d55cf35ef8b92885d5ef280790f0fb9634d87ea1cc315176aecd61/cffi-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:4f26194e3d95e06501b942642855aed4f953d55e95d7d01b7c4483db3ecff458", size = 185113, upload-time = "2026-07-06T21:32:41.761Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4e/e8d7cb5783f1841a3c8fb3a7735838d7484d08ec08c9f984b14cac1ac0e9/cffi-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:35aaea0c7ee0e58a5cd8c2fd1a48fdf7ece0d2699b7ecdda08194e9ce5dd9b3d", size = 179927, upload-time = "2026-07-06T21:32:42.961Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[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.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +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/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.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/97/c52dc440c390b6cfa87be9432b141a956e2d56d9b9f5fc8bd71c5f471722/coverage-7.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50913d4bf5ddafa6ca3693da5e4dd833dd1b772e0283c99ca7f7d287db67331a", size = 220539, upload-time = "2026-07-02T13:08:19.252Z" }, + { url = "https://files.pythonhosted.org/packages/3f/26/602de8c2aec7e2e3e99ebfb8e04ba65598f746275396eea5f6794ff4673f/coverage-7.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:359e141ccd33893ce3f1ad5525f8b96083003677c82182e5907d62d4ea5799fc", size = 221058, upload-time = "2026-07-02T13:08:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/fc/13/ebab0743138891c1d646d61e247ec29639afcbb6c4e1905e6a0f0c75291a/coverage-7.15.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3200b6204935f928c64b2ca1f923ab8c1acb7c9de45ec61569711b34d25cccaf", size = 247797, upload-time = "2026-07-02T13:08:22.474Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/b6ffb9e042aa48dc4144a8a65529affaec8dca0685309353614a2a7386ad/coverage-7.15.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be616bf61346883b2cfdc5178669647e03531d81ab761a7e378558b7e8bcb628", size = 249626, upload-time = "2026-07-02T13:08:23.803Z" }, + { url = "https://files.pythonhosted.org/packages/9c/06/243ff05b652333d8e3d060c11223efc2723b19cacf6605e433fa686ab5d4/coverage-7.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7bafc3fe1059463a8fdd97ca79972d6e2bf819d775c7d54991b5b1971201d6", size = 251493, upload-time = "2026-07-02T13:08:25.397Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2b/867faa17030a806114dae388b32a3fa929d8cd4bf39226fbc11f6e6bb705/coverage-7.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b713aa7fcf325a01d4184d848acb46fd84f78fdb0978470c636b23a06a753d91", size = 253406, upload-time = "2026-07-02T13:08:26.842Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/d789ce18f6605afc4895db75723424be2ef494282f77f61d8e5832923183/coverage-7.15.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e38e6fba2d56652fdfaf0231f8f78aeb805234a912de25dc291ee5cce5b8faa4", size = 248512, upload-time = "2026-07-02T13:08:28.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/b2673c30739f4a2e06649a0a38ad8b093c4d865462dc7bab0e9524a2c3b1/coverage-7.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:884499f42e382675be80770391983b90e0c0c774d87dbeeebf5f991cf6612b20", size = 249532, upload-time = "2026-07-02T13:08:29.731Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/acd79e9a41beabee92b623afe4f30b549916f48566271475f2907e752828/coverage-7.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:840481b12e083dbcbafab14794a8781a958edf327c8d3d70b4eee42f9b8253aa", size = 247537, upload-time = "2026-07-02T13:08:31.173Z" }, + { url = "https://files.pythonhosted.org/packages/12/d4/2d301c4d1b3238d7c88b70ab9d13fd53ed9505662a7ff1b46ba1e2e4e3c3/coverage-7.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:276646e9481703d09f854f3b2f018f24e19fd7049ae670a92570043eb97203b1", size = 251348, upload-time = "2026-07-02T13:08:32.63Z" }, + { url = "https://files.pythonhosted.org/packages/35/bb/c67708b2bc00f32e12805ec23d5fa677a0a51652f449341a89f9d6b1b715/coverage-7.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4de4b4d3f5545aa6c60dc4efd9c63b5b5dcc3bf00fe83146b2bdfffb8f6613bd", size = 247806, upload-time = "2026-07-02T13:08:33.931Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6c/57c4f653c47a6e917748f8938e389e72fbcae44e3643cd906664f0477a13/coverage-7.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5c504097b2a89b1e85bc6070d920df77daec701337e3aeef2c17775a5dd0ca90", size = 248410, upload-time = "2026-07-02T13:08:35.189Z" }, + { url = "https://files.pythonhosted.org/packages/6c/94/bb083041aef828903668f134273f319f2bd49224962875359c52faa5497f/coverage-7.15.0-cp310-cp310-win32.whl", hash = "sha256:f6e80ed91f98316e86b9c137206b04b2bcfbffccbdff49bd2eb09dddb1cf14e0", size = 222588, upload-time = "2026-07-02T13:08:36.486Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/a09d8ee618956f626741b0734854bac4425a00e10c0565f5abca64e7e751/coverage-7.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3b3e22030f3f6f5e01a5ce69936552a5c0f6992b7698777377b99041961031f", size = 223214, upload-time = "2026-07-02T13:08:37.885Z" }, + { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, + { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, + { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, + { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, + { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[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 = "debugpy" +version = "1.8.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/aa/12037145b7a56eaa5b29b41872f7a21b538e807e13f32c4d3c46e59be084/debugpy-1.8.21.tar.gz", hash = "sha256:a3c53278e84c94e11bd87c53970ec391d1a67396c8b22609fcac576520e611a6", size = 1697577, upload-time = "2026-06-01T19:30:35.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f3/6b1d4c71f4cbb5360009f928934a03b42906f28fc7b3f7f35f04e58acead/debugpy-1.8.21-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:8eeab7b5462f683452c57c0126aaa5ec4e974ddb705f39ba87dff8818c8e08f9", size = 2113873, upload-time = "2026-06-01T19:30:37.148Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f2/17c3bf91cebc173bfbf5734cd2669723d0a35c0cf9d2fd2124546efeae83/debugpy-1.8.21-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:0fddfdc130ac6d8bfc0415b0409822fa901c8f310e5c945ac5653a0352532344", size = 3004715, upload-time = "2026-06-01T19:30:38.888Z" }, + { url = "https://files.pythonhosted.org/packages/5a/22/1f8efd80c7b5909e760f9cfd0c9e8681d2d35d532f7c0a40760cd4da4a19/debugpy-1.8.21-cp310-cp310-win32.whl", hash = "sha256:72b5d676c4cbfac3bac5bb01c138a4656e843f93f03ce2a5f4e394ad49fbee73", size = 5303455, upload-time = "2026-06-01T19:30:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/da/ce/54c79abd6cccef92fa7b43d97e3acafedf4d645557267ece05e948b5e4b8/debugpy-1.8.21-cp310-cp310-win_amd64.whl", hash = "sha256:a7fe47fd23da57b9e0bec3f4a8ee65a2dc55782455ed7f2141d75ab5d2eaeef5", size = 5331751, upload-time = "2026-06-01T19:30:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/89/fb/cbf306d6e07a313a91e7171a98669054502840931432c227cfd505ee367f/debugpy-1.8.21-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:da456226c7b4c69e35dbe35dcee6623d912000a77816db7856a41af1c72a0264", size = 2203120, upload-time = "2026-06-01T19:30:43.964Z" }, + { url = "https://files.pythonhosted.org/packages/aa/57/aa739bd4ad2cbf96aeb1b20b56918ddd5ae4c28b68709bfcd327f02123ee/debugpy-1.8.21-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:f68b891688e61bdc08b8d364d919ff0051e0b94657b39dcd027bc3173edb7cdc", size = 3059958, upload-time = "2026-06-01T19:30:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/a8/31/453d2c9a23d133fe2c8ec7ca1d816ded52a913487fe3ffef7c01b4b706af/debugpy-1.8.21-cp311-cp311-win32.whl", hash = "sha256:f843a8b08c2edeaf9b1582eed4f25441af21a297c22ff16bf76a662557aa9c9e", size = 5236515, upload-time = "2026-06-01T19:30:47.461Z" }, + { url = "https://files.pythonhosted.org/packages/60/94/6660de2f2d7bf388f229335ba4637646eebabdbf38564cb439a95a9193c9/debugpy-1.8.21-cp311-cp311-win_amd64.whl", hash = "sha256:84c564d8cc701d41843b29a92814c1f1bef6798724ca9d675c284ad9f6a547d7", size = 5256138, upload-time = "2026-06-01T19:30:49.113Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/bf625547431a9cadc9f4cbfeda38866e2b17f6aed147b625377e87834449/debugpy-1.8.21-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:9f96713896f39c3dff0ee841f47320c3f2983d33c341e009361bb0ebc79adc4e", size = 2483609, upload-time = "2026-06-01T19:30:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/59324b903599031ff9faaec1758292409f6561a0ec2492fe4b703327705a/debugpy-1.8.21-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:c193d474f0a211191f2b4449d2d06157c689013035bd952f3b617e0ef422b176", size = 3968900, upload-time = "2026-06-01T19:30:52.341Z" }, + { url = "https://files.pythonhosted.org/packages/14/cd/27f65b805d7fe005c44e1a36b9183ecdfbcdbf9d3e721a5115d461ecc7ee/debugpy-1.8.21-cp312-cp312-win32.whl", hash = "sha256:4743373c1cac7f9e74a1b9915bf1dbe0e900eca657ffb170ae07ac8363205ae9", size = 5336340, upload-time = "2026-06-01T19:30:54.047Z" }, + { url = "https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl", hash = "sha256:bd7ba9dd3daa7c2f942c6ca8d4695a16bf9ac16b63615261c7982bc74f7ed20c", size = 5374751, upload-time = "2026-06-01T19:30:55.891Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/67e7cf11a53e40694f720457d5b3a1cdaaa3d5a9a633e482f225456b93ff/debugpy-1.8.21-py2.py3-none-any.whl", hash = "sha256:b1e37d333663c8851516a47364ef473da127f9caebe4417e6df6f5825a7e9a92", size = 5352888, upload-time = "2026-06-01T19:31:25.186Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[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 = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[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 = "fasteners" +version = "0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, +] + +[[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 = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[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 = "greenlet" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/a1/1f7f0c555f5858fd2906fe9f7b0a3554fddb85cb70df7a6aaec41dc292c2/greenlet-3.5.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c180d22d325fb613956b443c3c6f4406eb70e6defc70d3974da2a7b59e06f48c", size = 285838, upload-time = "2026-06-26T18:21:05.167Z" }, + { url = "https://files.pythonhosted.org/packages/0a/29/be9f43ed61677a5759b38c8a9389248133c8c731bbfc0574ecdff66c99fc/greenlet-3.5.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483d08c11181c83a6ce1a7a61df0f624a208ec40817a3bb2302714592eee4f04", size = 602342, upload-time = "2026-06-26T19:07:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/ba41c97ec36aa4b3ec25e5aa691d79561254805fad7f2f826dd6770587e2/greenlet-3.5.3-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1dae6e0091eae084317e411f047f0b7cb241c6db570f7c45fd6b900a274914ce", size = 615541, upload-time = "2026-06-26T19:10:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c7/28747042e1df8a9cd120a1ebe15529fc4be3b486e13e8d551ff307a82412/greenlet-3.5.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcd2d72ccd70a1ec68ba6ef93e7fbb4420ef9997dabc7010d893bd4015e0bec", size = 615675, upload-time = "2026-06-26T18:32:14.444Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a8/b85525a6c8fba9f009a5f7c8df1545de8fb0f0bf3e0179194ef4e500317f/greenlet-3.5.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73f152c895e09907e0dbe24f6c2db37beb085cd63db91c3825a0fcd0064124a8", size = 1575057, upload-time = "2026-06-26T19:09:00.264Z" }, + { url = "https://files.pythonhosted.org/packages/03/79/fb76edb218fe6735ab0edeba176c7ab80df9618f7c02ce4208979f3ae7db/greenlet-3.5.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8bdb43e1a1d1873721acab2be99c5befd4d2044ddfd52e4d610801019880a702", size = 1641692, upload-time = "2026-06-26T18:31:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/6b/79/86fe3ee50ed55d9b3907eecd3208b5c3fe8a79515519aae98b4753c3fa1d/greenlet-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:0909f9355a9f24845d3299f3112e266a06afb68302041989fd26bd68894933db", size = 238742, upload-time = "2026-06-26T18:20:40.758Z" }, + { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/1c/35/f0d8ee998b422cf8693b270f098e55d8d4ec8006b061b333f54f177d28d9/greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7", size = 239137, upload-time = "2026-06-26T18:23:21.664Z" }, + { url = "https://files.pythonhosted.org/packages/fb/96/b9820295576ef18c9edc404f10e260ae7215ceaf3781a54b720ed2627862/greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44", size = 237630, upload-time = "2026-06-26T18:24:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" }, + { url = "https://files.pythonhosted.org/packages/37/87/b4d095775a3fb1bcafbb483fc206b27ebb785724c83051447737085dc54e/greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861", size = 614244, upload-time = "2026-06-26T18:32:17.594Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/7559b609683650fa2b95b8ab84b4ab0b26556a635d19675e12aa832d826d/greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149", size = 1574210, upload-time = "2026-06-26T19:09:03.077Z" }, + { url = "https://files.pythonhosted.org/packages/ae/73/be55392074c60fc37655ca40fa6022457bfbf6718e9e342a7b0b41f96dd2/greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea", size = 1638627, upload-time = "2026-06-26T18:31:44.748Z" }, + { url = "https://files.pythonhosted.org/packages/14/40/c57489acf8e37d74e2913d4eff63aa0dba17acccc4bdeef874dde2dbbec9/greenlet-3.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c", size = 239882, upload-time = "2026-06-26T18:23:27.518Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/6fea0e3d6600f785069481ee637e09378dd4118acdfd38ad88ae2db31c98/greenlet-3.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d", size = 238211, upload-time = "2026-06-26T18:22:37.671Z" }, +] + +[[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 = "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 = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[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.3.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-asyncio2" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/c4/e4a38f579de4225a561305666f7541cdabb30075def2aa1ac17bd73c1fb5/ipykernel-7.3.0.tar.gz", hash = "sha256:9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09", size = 184899, upload-time = "2026-06-10T08:41:25.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/02/77b271f5dc58bfbc0b577c877b2365d1ffea2afe66a80c13f2312820348c/ipykernel-7.3.0-py3-none-any.whl", hash = "sha256:897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057", size = 120583, upload-time = "2026-06-10T08:41:23.648Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { 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/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[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 = "jax" +version = "0.4.38" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaxlib" }, + { name = "ml-dtypes" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "opt-einsum" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/e5/c4aa9644bb96b7f6747bd7c9f8cda7665ca5e194fa2542b2dea3ff730701/jax-0.4.38.tar.gz", hash = "sha256:43bae65881628319e0a2148e8f81a202fbc2b8d048e35c7cb1df2416672fa4a8", size = 1930034, upload-time = "2024-12-17T23:03:47.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/49/b4418a7a892c0dd64442bbbeef54e1cdfe722dfc5a7bf0d611d3f5f90e99/jax-0.4.38-py3-none-any.whl", hash = "sha256:78987306f7041ea8500d99df1a17c33ed92620c2268c4c3677fb24e06712be64", size = 2236864, upload-time = "2024-12-17T23:03:44.433Z" }, +] + +[[package]] +name = "jaxlib" +version = "0.4.38" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d4/e6a0881a88b8f17491c2ee271fd77c348b0221d9e2ec92dad23a2c9e41bc/jaxlib-0.4.38-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:55c19b9d3f33a6fc59f644aa5a21fba02639ccdd776cb4a9b5526625f57839ff", size = 99663603, upload-time = "2024-12-17T23:04:08.864Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6d/11569ce873f04c82ec22e58d822f4187dccae1d400c0d6dd05ed314d5328/jaxlib-0.4.38-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30b2f52cb50d74734af2f477c2533a7a583e3bb7b2c8acdeb361ee77d940577a", size = 79475708, upload-time = "2024-12-17T23:04:21.67Z" }, + { url = "https://files.pythonhosted.org/packages/72/61/1de2405d13089c83b1ad87ec0266479c9d00080659dae2474892ae356306/jaxlib-0.4.38-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ee19c163a8fdf0839d4c18b88a5fbfb4e731ba7c437416d3e5483e570bb764e4", size = 93219045, upload-time = "2024-12-17T23:04:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/9c/24/0829decf233c6af9efe7c53888ae8ac72395e0979869cd9cee487e35dac3/jaxlib-0.4.38-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:61aeccb9a27c67fdb8450f6357240019cd4511cb9d62a44e4764756d384853ad", size = 101732107, upload-time = "2024-12-17T23:04:49.106Z" }, + { url = "https://files.pythonhosted.org/packages/0d/04/120c4caac6151f7297fedf9dd776362aa2d417d3f87bda826050b4da45e8/jaxlib-0.4.38-cp310-cp310-win_amd64.whl", hash = "sha256:d6ab745a89d0fb737a36fe1d8b86659e3fffe6ee8303b20651b26193d5edc0ef", size = 64223924, upload-time = "2024-12-17T23:05:04.362Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/b9fba73eb5e758e40a514919e096a039d27dc0ab4776a6cc977f5153a55f/jaxlib-0.4.38-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:b67fdeabd6dfed08b7768f3bdffb521160085f8305669bd197beef61d08de08b", size = 99679916, upload-time = "2024-12-17T23:05:19.908Z" }, + { url = "https://files.pythonhosted.org/packages/44/2a/3458130d44d44038fd6974e7c43948f68408f685063203b82229b9b72c1a/jaxlib-0.4.38-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb0eaae7369157afecbead50aaf29e73ffddfa77a2335d721bd9794f3c510e4", size = 79488377, upload-time = "2024-12-17T23:05:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/94/96/7d9a0b9f35af4727df44b68ade4c6f15163840727d1cb47251b1ea515e30/jaxlib-0.4.38-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:43db58c4c427627296366a56c10318e1f00f503690e17f94bb4344293e1995e0", size = 93241543, upload-time = "2024-12-17T23:05:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2d/68f85037e60c981b37b18b23ace458c677199dea4722ddce541b48ddfc63/jaxlib-0.4.38-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:2751ff7037d6a997d0be0e77cc4be381c5a9f9bb8b314edb755c13a6fd969f45", size = 101751923, upload-time = "2024-12-17T23:05:55.321Z" }, + { url = "https://files.pythonhosted.org/packages/cc/24/a9c571c8a189f58e0b54b14d53fc7f5a0a06e4f1d7ab9edcf8d1d91d07e7/jaxlib-0.4.38-cp311-cp311-win_amd64.whl", hash = "sha256:35226968fc9de6873d1571670eac4117f5ed80e955f7a1775204d1044abe16c6", size = 64255189, upload-time = "2024-12-17T23:06:06.683Z" }, + { url = "https://files.pythonhosted.org/packages/49/df/08b94c593c0867c7eaa334592807ba74495de4be90580f360db8b96221dc/jaxlib-0.4.38-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3fefea985f0415816f3bbafd3f03a437050275ef9bac9a72c1314e1644ac57c1", size = 99737849, upload-time = "2024-12-17T23:06:20.406Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b1/c9d2a7ba9ebeabb7ac37082f4c466364f475dc7550a79358c0f0aa89fdf2/jaxlib-0.4.38-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f33bcafe32c97a562ecf6894d7c41674c80c0acdedfa5423d49af51147149874", size = 79509242, upload-time = "2024-12-17T23:06:33.73Z" }, + { url = "https://files.pythonhosted.org/packages/53/25/dd670d8bdf3799ece76d12cfe6a6a250ea256057aa4b0fcace4753a99d2d/jaxlib-0.4.38-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:496f45b0e001a2341309cd0c74af0b670537dced79c168cb230cfcc773f0aa86", size = 93251503, upload-time = "2024-12-17T23:06:48.243Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/37fce5162f6b9070203fd76cc0f298d9b3bfdf01939a78935a6078d63621/jaxlib-0.4.38-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:dad6c0a96567c06d083c0469fec40f201210b099365bd698be31a6d2ec88fd59", size = 101792792, upload-time = "2024-12-17T23:07:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7a/8515950a60a4ea5b13cc98fc0a42e36553b2db5a6eedc00d3bd7836f77b5/jaxlib-0.4.38-cp312-cp312-win_amd64.whl", hash = "sha256:966cdec36cfa978f5b4582bcb4147fe511725b94c1a752dac3a5f52ce46b6fa3", size = 64288223, upload-time = "2024-12-17T23:07:15.024Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[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 = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "json5" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/7d/05c46a96a78147ae3bf99c2f4169ce144a70220b8d6fcd56f6ec368b8ce9/json5-0.15.0.tar.gz", hash = "sha256:7424d1f1eb1d56da6e3d70643f53619862b4ce81440bdb8ecfd6f875e5ba4a71", size = 53278, upload-time = "2026-06-19T20:08:27.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/be/59527c99478aade6bb33a68d72e6e18dd4e6ff6eacfc7d01bdb15bc76912/json5-0.15.0-py3-none-any.whl", hash = "sha256:56636a30c0e8a4665fe2179c0212f32eae3796dea89ea6f649b9436ecdb39618", size = 36570, upload-time = "2026-06-19T20:08:26.748Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +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-builder" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/df/db5efc4e28803a1421350445e53d85ec571a4e6928e3524ccc39f4e16584/jupyter_builder-1.1.0.tar.gz", hash = "sha256:b996e8af616900f18724fa34883169d869be2205497940bec78a3a1031eb897d", size = 971142, upload-time = "2026-07-11T07:24:02.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/fb/53adbc72931b4429275b160044c3e1bcbc8fbb5ea6b8f318a357834842fb/jupyter_builder-1.1.0-py3-none-any.whl", hash = "sha256:d9d5280e8845f726663545c3a6db55bd205127616eeb8958481091d6cba71b6a", size = 912964, upload-time = "2026-07-11T07:24:00.279Z" }, +] + +[[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.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/5512503b088997c2250b8bf18258fba9d9ce5ead641183700960d3c9d342/jupyter_client-8.9.1.tar.gz", hash = "sha256:a58f730dd9e728ba16ba1d62ebccf7ffe1ebbdbce4e95cfae941b7321ae1f4fa", size = 359256, upload-time = "2026-06-09T13:15:01.033Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/6f/56d39bf385c5c27988aebaf0c18a2a17e960575740100973511018bd904e/jupyter_client-8.9.1-py3-none-any.whl", hash = "sha256:0b7a295bc46e8751e9adae84781f726c851c1d911bd793edc4a3bde942e3da81", size = 109828, upload-time = "2026-06-09T13:14:58.835Z" }, +] + +[[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.1" +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/18/f8/475c4241b2b75af0deaae453ed003c6c851766dbc44d332d8baf245dc931/jupyter_events-0.12.1.tar.gz", hash = "sha256:faff25f77218335752f35f23c5fe6e4a392a7bd99a5939ccb9b8fbf594636cf3", size = 62854, upload-time = "2026-04-20T23:17:50.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/6c/6fcde0c8f616ed360ffd3587f7db9e225a7e62b583a04494d2f069cf64ea/jupyter_events-0.12.1-py3-none-any.whl", hash = "sha256:c366585253f537a627da52fa7ca7410c5b5301fe893f511e7b077c2d93ec8bcf", size = 19512, upload-time = "2026-04-20T23:17:48.927Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/ff/1e4a61f5170a9a1d978f3ac3872449de6c01fc71eaf89657824c878b1549/jupyter_lsp-2.3.1.tar.gz", hash = "sha256:fdf8a4aa7d85813976d6e29e95e6a2c8f752701f926f2715305249a3829805a6", size = 55677, upload-time = "2026-04-02T08:10:06.749Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/e8/9d61dcbd1dce8ef418f06befd4ac084b4720429c26b0b1222bc218685eff/jupyter_lsp-2.3.1-py3-none-any.whl", hash = "sha256:71b954d834e85ff3096400554f2eefaf7fe37053036f9a782b0f7c5e42dadb81", size = 77513, upload-time = "2026-04-02T08:10:01.753Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.20.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/6b/dc/db3a582633170186f8c8b31298d7eb26ad0eb031a1f53476c258b64eed05/jupyter_server-2.20.0.tar.gz", hash = "sha256:b5778ba337d8015a3dc2b80803ecdd5ac18d3797fddf61a50ea5fb472b4ebe14", size = 756523, upload-time = "2026-06-17T12:09:09.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/71/8c002223e873a870f5c41dc69b0a7c922301123e4a31d5d01ecb700aef77/jupyter_server-2.20.0-py3-none-any.whl", hash = "sha256:c3b67c93c471e947c18b5026f04f21614218adb706df8f48227d3ee8e0a7cdcc", size = 393143, upload-time = "2026-06-17T12:09:07.234Z" }, +] + +[[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.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-builder" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/2a/d6af53bfd45a43a5bfe7e40ba47ee7a8921a807daf4bb708e3a295bbb54d/jupyterlab-4.6.1.tar.gz", hash = "sha256:75315982ed28427edaa62bb85eadb5105e4043a757643c910efd787fe6ed0837", size = 28179125, upload-time = "2026-06-29T12:48:45.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/81/90ac6cc31d248e83a0d1eab343a5e6e68bc783d3f74fbe61640f42a61da4/jupyterlab-4.6.1-py3-none-any.whl", hash = "sha256:85a58546c831f3dce6cf919468c26874c9065e99c42279fb4abb8e1b552a98bb", size = 17164660, upload-time = "2026-06-29T12:48:41.21Z" }, +] + +[[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.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[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 = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[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/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { 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" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" } }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" } }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261, upload-time = "2026-06-12T02:27:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669, upload-time = "2026-06-12T02:27:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076, upload-time = "2026-06-12T02:27:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999, upload-time = "2026-06-12T02:27:42.433Z" }, + { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103, upload-time = "2026-06-12T02:27:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945, upload-time = "2026-06-12T02:27:46.867Z" }, + { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304, upload-time = "2026-06-12T02:27:48.798Z" }, + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094, upload-time = "2026-06-12T02:29:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183, upload-time = "2026-06-12T02:29:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/fc/f8d0863f8862f25602c0404d75568e89fb6b4109804645e5cdfb1be5cf56/mdit_py_plugins-0.6.1.tar.gz", hash = "sha256:a2bca0f039f39dbd35fb74ae1b5f998608c437463371f0ff7f49a19a17a114d0", size = 56114, upload-time = "2026-05-13T09:03:38.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/69/6da5581c6a7fede7dc261bf4e67d6adca4196f176b43288b55b3db395b6e/mdit_py_plugins-0.6.1-py3-none-any.whl", hash = "sha256:214c82fb2ac524472ab6a5bcab1de80f73b50443e187f401bfd77efbc7c6481d", size = 66663, upload-time = "2026-05-13T09:03:37.76Z" }, +] + +[[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 = "mistune" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/a5/2dab368d6950e6808904dec98f54c7e726ee7be4a0c6afe00e6e011bd52d/mistune-3.3.3.tar.gz", hash = "sha256:c4c6c0c840b8637a2e9b8b6d607eb7c8f00888bf14c754409bcd339e848c2477", size = 115363, upload-time = "2026-07-09T06:18:05.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/70/b1e4737b84163db5bb1dfde6f216dbfbf32783330a9989c965e121172830/mistune-3.3.3-py3-none-any.whl", hash = "sha256:99de1585e42dcbd826faa9e11a202727a5e202e4e4722a4c69ac1ff615793dd7", size = 63569, upload-time = "2026-07-09T06:18:03.839Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/3a/c5b855752a70267ff729c349e650263adb3c206c29d28cc8ea7ace30a1d5/ml_dtypes-0.5.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b95e97e470fe60ed493fd9ae3911d8da4ebac16bd21f87ffa2b7c588bf22ea2c", size = 679735, upload-time = "2025-11-17T22:31:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/41/79/7433f30ee04bd4faa303844048f55e1eb939131c8e5195a00a96a0939b64/ml_dtypes-0.5.4-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4b801ebe0b477be666696bda493a9be8356f1f0057a57f1e35cd26928823e5a", size = 5051883, upload-time = "2025-11-17T22:31:33.658Z" }, + { url = "https://files.pythonhosted.org/packages/10/b1/8938e8830b0ee2e167fc75a094dea766a1152bde46752cd9bfc57ee78a82/ml_dtypes-0.5.4-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:388d399a2152dd79a3f0456a952284a99ee5c93d3e2f8dfe25977511e0515270", size = 5030369, upload-time = "2025-11-17T22:31:35.595Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a3/51886727bd16e2f47587997b802dd56398692ce8c6c03c2e5bb32ecafe26/ml_dtypes-0.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:4ff7f3e7ca2972e7de850e7b8fcbb355304271e2933dd90814c1cb847414d6e2", size = 210738, upload-time = "2025-11-17T22:31:37.43Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, +] + +[[package]] +name = "myst-nb" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jupyter-cache" }, + { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "myst-parser", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "pyyaml" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/b4/ff1abeea67e8cfe0a8c033389f6d1d8b0bfecfd611befb5cbdeab884fce6/myst_nb-1.4.0.tar.gz", hash = "sha256:c145598de62446a6fd009773dd071a40d3b76106ace780de1abdfc6961f614c2", size = 82285, upload-time = "2026-03-02T21:14:56.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/93/0a378b48488879a1d925b42a804edfc6e0cd0ef854220f2dce738a46e7e9/myst_nb-1.4.0-py3-none-any.whl", hash = "sha256:0e2c86e7d3b82c3aa51383f82d6268f7714f3b772c23a796ab09538a8e68b4e4", size = 82555, upload-time = "2026-03-02T21:14:55.652Z" }, +] + +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" } }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, +] + +[[package]] +name = "myst-parser" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" } }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/dc/603751677fff302f34396e206b610f556a59d7fe58b9a2145f54e96b48e8/myst_parser-5.1.0.tar.gz", hash = "sha256:ab69322dc6719dcc7f296479dbb70181b66df6ed315064f92dbc85c0e1bf2f02", size = 101182, upload-time = "2026-05-13T09:38:19.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/dc/f3dfb7488b770f3f67e6545085bf2abea5172e88f57b8ad25ef860ca704c/myst_parser-5.1.0-py3-none-any.whl", hash = "sha256:9c91c52b3cdb4d94a6506e4fab4e2f296c7623a0da0dcbe6de1565c3dad67a8a", size = 85817, upload-time = "2026-05-13T09:38:17.904Z" }, +] + +[[package]] +name = "nbclient" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/a5/b3bae4b590c0cbcada2c63a34f7580024e834a8ba213e949a2f906705787/nbclient-0.11.0.tar.gz", hash = "sha256:04a134a5b087f2c5887f228aca155db50169b8cd9334dee6942c8e927e56081a", size = 62535, upload-time = "2026-06-05T07:52:41.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/c9/94d73e5a01c5b926c3fa2496e97d7a8dc28ed5a77c0b2ed712f1a62e6694/nbclient-0.11.0-py3-none-any.whl", hash = "sha256:ef7fa0d59d6e1d41103933d8a445a18d5de860ca6b613b87b8574accdb3c2895", size = 25288, upload-time = "2026-06-05T07:52:40.115Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.17.1" +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/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" }, +] + +[[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 = "nest-asyncio2" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/73/731debf26e27e0a0323d7bda270dc2f634b398e38f040a09da1f4351d0aa/nest_asyncio2-1.7.2.tar.gz", hash = "sha256:1921d70b92cc4612c374928d081552efb59b83d91b2b789d935c665fa01729a8", size = 14743, upload-time = "2026-02-13T00:34:04.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/3c/3179b85b0e1c3659f0369940200cd6d0fa900e6cefcc7ea0bc6dd0e29ffb/nest_asyncio2-1.7.2-py3-none-any.whl", hash = "sha256:f5dfa702f3f81f6a03857e9a19e2ba578c0946a4ad417b4c50a24d7ba641fe01", size = 7843, upload-time = "2026-02-13T00:34:02.691Z" }, +] + +[[package]] +name = "notebook" +version = "7.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-builder" }, + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/44/d5c65783f490298473bb1c05722e05ee2256231389559c2c5ae0a3e5d975/notebook-7.6.0.tar.gz", hash = "sha256:ea13e79e601bf273074895fdfb17dd3f2da916d3c045e0b9c47d18b16ab62481", size = 5497344, upload-time = "2026-06-18T16:18:55.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d1/e617c40db57ff40e75f43a7d4d1c305e3a54c053ab5cb0534a6c314664f9/notebook-7.6.0-py3-none-any.whl", hash = "sha256:98aa2811b54ac191321d5dfce12ca700f8a511a33a26e4de2fa106a357c43d6a", size = 5544575, upload-time = "2026-06-18T16:18:52.551Z" }, +] + +[[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 = "numcodecs" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/56/8895a76abe4ec94ebd01eeb6d74f587bc4cddd46569670e1402852a5da13/numcodecs-0.13.1.tar.gz", hash = "sha256:a3cf37881df0898f3a9c0d4477df88133fe85185bffe57ba31bcc2fa207709bc", size = 5955215, upload-time = "2024-10-09T16:28:00.188Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/c0/6d72cde772bcec196b7188731d41282993b2958440f77fdf0db216f722da/numcodecs-0.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:96add4f783c5ce57cc7e650b6cac79dd101daf887c479a00a29bc1487ced180b", size = 1580012, upload-time = "2024-10-09T16:27:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/94/1d/f81fc1fa9210bbea97258242393a1f9feab4f6d8fb201f81f76003005e4b/numcodecs-0.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:237b7171609e868a20fd313748494444458ccd696062f67e198f7f8f52000c15", size = 1176919, upload-time = "2024-10-09T16:27:21.634Z" }, + { url = "https://files.pythonhosted.org/packages/16/e4/b9ec2f4dfc34ecf724bc1beb96a9f6fa9b91801645688ffadacd485089da/numcodecs-0.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96e42f73c31b8c24259c5fac6adba0c3ebf95536e37749dc6c62ade2989dca28", size = 8625842, upload-time = "2024-10-09T16:27:24.168Z" }, + { url = "https://files.pythonhosted.org/packages/fe/90/299952e1477954ec4f92813fa03e743945e3ff711bb4f6c9aace431cb3da/numcodecs-0.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:eda7d7823c9282e65234731fd6bd3986b1f9e035755f7fed248d7d366bb291ab", size = 828638, upload-time = "2024-10-09T16:27:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/f0/78/34b8e869ef143e88d62e8231f4dbfcad85e5c41302a11fc5bd2228a13df5/numcodecs-0.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2eda97dd2f90add98df6d295f2c6ae846043396e3d51a739ca5db6c03b5eb666", size = 1580199, upload-time = "2024-10-09T16:27:29.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cf/f70797d86bb585d258d1e6993dced30396f2044725b96ce8bcf87a02be9c/numcodecs-0.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a86f5367af9168e30f99727ff03b27d849c31ad4522060dde0bce2923b3a8bc", size = 1177203, upload-time = "2024-10-09T16:27:31.011Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b5/d14ad69b63fde041153dfd05d7181a49c0d4864de31a7a1093c8370da957/numcodecs-0.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233bc7f26abce24d57e44ea8ebeb5cd17084690b4e7409dd470fdb75528d615f", size = 8868743, upload-time = "2024-10-09T16:27:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/27a7b5af0b33f6d61e198faf177fbbf3cb83ff10d9d1a6857b7efc525ad5/numcodecs-0.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:796b3e6740107e4fa624cc636248a1580138b3f1c579160f260f76ff13a4261b", size = 829603, upload-time = "2024-10-09T16:27:35.415Z" }, + { url = "https://files.pythonhosted.org/packages/37/3a/bc09808425e7d3df41e5fc73fc7a802c429ba8c6b05e55f133654ade019d/numcodecs-0.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5195bea384a6428f8afcece793860b1ab0ae28143c853f0b2b20d55a8947c917", size = 1575806, upload-time = "2024-10-09T16:27:37.804Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/dc74d0bfdf9ec192332a089d199f1e543e747c556b5659118db7a437dcca/numcodecs-0.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3501a848adaddce98a71a262fee15cd3618312692aa419da77acd18af4a6a3f6", size = 1178233, upload-time = "2024-10-09T16:27:40.169Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/434e8e3970b8e92ae9ab6d9db16cb9bc7aa1cd02e17c11de6848224100a1/numcodecs-0.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2230484e6102e5fa3cc1a5dd37ca1f92dfbd183d91662074d6f7574e3e8f53", size = 8857827, upload-time = "2024-10-09T16:27:42.743Z" }, + { url = "https://files.pythonhosted.org/packages/83/e7/1d8b1b266a92f9013c755b1c146c5ad71a2bff147ecbc67f86546a2e4d6a/numcodecs-0.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:e5db4824ebd5389ea30e54bc8aeccb82d514d28b6b68da6c536b8fa4596f4bca", size = 826539, upload-time = "2024-10-09T16:27:44.808Z" }, +] + +[[package]] +name = "numcodecs" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "deprecated" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/fc/bb532969eb8236984ba65e4f0079a7da885b8ac0ce1f0835decbb3938a62/numcodecs-0.15.1.tar.gz", hash = "sha256:eeed77e4d6636641a2cc605fbc6078c7a8f2cc40f3dfa2b3f61e52e6091b04ff", size = 6267275, upload-time = "2025-02-10T10:23:33.254Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/fc/410f1cacaef0931f5daf06813b1b8a2442f7418ee284ec73fe5e830dca48/numcodecs-0.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:698f1d59511488b8fe215fadc1e679a4c70d894de2cca6d8bf2ab770eed34dfd", size = 1649501, upload-time = "2025-02-10T10:23:01.828Z" }, + { url = "https://files.pythonhosted.org/packages/85/29/dff62fae04323035912c419a82dc9624fad7d08541dbfcd9ab78a3a40074/numcodecs-0.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bef8c8e64fab76677324a07672b10c31861775d03fc63ed5012ca384144e4bb9", size = 1187306, upload-time = "2025-02-10T10:23:04.569Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a8/908a226632ffabf19caf8c99f1b2898f2f22aac02795a6fe9d018fd6d9dd/numcodecs-0.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdfaef9f5f2ed8f65858db801f1953f1007c9613ee490a1c56233cd78b505ed5", size = 8891971, upload-time = "2025-02-10T10:23:07.689Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e8/058aac43e1300d588e99b2d0d5b771c8a43fa92ce9c9517da596869fc146/numcodecs-0.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:e2547fa3a7ffc9399cfd2936aecb620a3db285f2630c86c8a678e477741a4b3c", size = 840035, upload-time = "2025-02-10T10:23:10.761Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7e/f12fc32d3beedc6a8f1ec69ea0ba72e93cb99c0350feed2cff5d04679bc3/numcodecs-0.15.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0a9d9cd29a0088220682dda4a9898321f7813ff7802be2bbb545f6e3d2f10ff", size = 1691889, upload-time = "2025-02-10T10:23:12.934Z" }, + { url = "https://files.pythonhosted.org/packages/81/38/88e40d40288b73c3b3a390ed5614a34b0661d00255bdd4cfb91c32101364/numcodecs-0.15.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a34f0fe5e5f3b837bbedbeb98794a6d4a12eeeef8d4697b523905837900b5e1c", size = 1189149, upload-time = "2025-02-10T10:23:15.803Z" }, + { url = "https://files.pythonhosted.org/packages/28/7d/7527d9180bc76011d6163c848c9cf02cd28a623c2c66cf543e1e86de7c5e/numcodecs-0.15.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a09e22140f2c691f7df26303ff8fa2dadcf26d7d0828398c0bc09b69e5efa3", size = 8879163, upload-time = "2025-02-10T10:23:18.582Z" }, + { url = "https://files.pythonhosted.org/packages/ab/bc/b6c3cde91c754860a3467a8c058dcf0b1a5ca14d82b1c5397c700cf8b1eb/numcodecs-0.15.1-cp312-cp312-win_amd64.whl", hash = "sha256:daed6066ffcf40082da847d318b5ab6123d69ceb433ba603cb87c323a541a8bc", size = 836785, upload-time = "2025-02-10T10:23:22.314Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, +] + +[[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.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, +] + +[[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.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +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.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[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 = "prometheus-client" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, +] + +[[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/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 = "pyarrow" +version = "18.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671, upload-time = "2024-11-26T02:01:48.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/bb/8d4a1573f66e0684f190dd2b55fd0b97a7214de8882d58a3867e777bf640/pyarrow-18.1.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:e21488d5cfd3d8b500b3238a6c4b075efabc18f0f6d80b29239737ebd69caa6c", size = 29531620, upload-time = "2024-11-26T01:58:27.03Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/893acfad917533b624a97b9e498c0e8393908508a0a72d624fe935e632bf/pyarrow-18.1.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:b516dad76f258a702f7ca0250885fc93d1fa5ac13ad51258e39d402bd9e2e1e4", size = 30836521, upload-time = "2024-11-26T01:58:34.607Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2a/526545a7464b5fb2fa6e2c4bad16ca90e59e1843025c534fd907b7f73e5a/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f443122c8e31f4c9199cb23dca29ab9427cef990f283f80fe15b8e124bcc49b", size = 39213905, upload-time = "2024-11-26T01:58:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/4b3fab91a30e19e233e738d0c5eca5a8f6dd05758bc349a2ca262c65de79/pyarrow-18.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a03da7f2758645d17b7b4f83c8bffeae5bbb7f974523fe901f36288d2eab71", size = 40128881, upload-time = "2024-11-26T01:58:45.561Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e2/a88e16c5e45e562449c52305bd3bc2f9d704295322d3434656e7ccac1444/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ba17845efe3aa358ec266cf9cc2800fa73038211fb27968bfa88acd09261a470", size = 38627517, upload-time = "2024-11-26T01:58:50.922Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/8037c20005ccc7b869726465be0957bd9c29cfc88612962030f08292ad06/pyarrow-18.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:3c35813c11a059056a22a3bef520461310f2f7eea5c8a11ef9de7062a23f8d56", size = 40060187, upload-time = "2024-11-26T01:58:56.848Z" }, + { url = "https://files.pythonhosted.org/packages/2a/38/d6435c723ff73df8ae74626ea778262fbcc2b9b0d1a4f3db915b61711b05/pyarrow-18.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9736ba3c85129d72aefa21b4f3bd715bc4190fe4426715abfff90481e7d00812", size = 25118314, upload-time = "2024-11-26T01:59:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4d/a4988e7d82f4fbc797715db4185939a658eeffb07a25bab7262bed1ea076/pyarrow-18.1.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:eaeabf638408de2772ce3d7793b2668d4bb93807deed1725413b70e3156a7854", size = 29554860, upload-time = "2024-11-26T01:59:06.94Z" }, + { url = "https://files.pythonhosted.org/packages/59/03/3a42c5c1e4bd4c900ab62aa1ff6b472bdb159ba8f1c3e5deadab7222244f/pyarrow-18.1.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:3b2e2239339c538f3464308fd345113f886ad031ef8266c6f004d49769bb074c", size = 30867076, upload-time = "2024-11-26T01:59:11.475Z" }, + { url = "https://files.pythonhosted.org/packages/75/7e/332055ac913373e89256dce9d14b7708f55f7bd5be631456c897f0237738/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f39a2e0ed32a0970e4e46c262753417a60c43a3246972cfc2d3eb85aedd01b21", size = 39212135, upload-time = "2024-11-26T01:59:16.045Z" }, + { url = "https://files.pythonhosted.org/packages/8c/64/5099cdb325828722ef7ffeba9a4696f238eb0cdeae227f831c2d77fcf1bd/pyarrow-18.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e31e9417ba9c42627574bdbfeada7217ad8a4cbbe45b9d6bdd4b62abbca4c6f6", size = 40125195, upload-time = "2024-11-26T01:59:21.267Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/1938d783727db1b178ff71bc6a6143d7939e406db83a9ec23cad3dad325c/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:01c034b576ce0eef554f7c3d8c341714954be9b3f5d5bc7117006b85fcf302fe", size = 38641884, upload-time = "2024-11-26T01:59:26.672Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b5/9e14e9f7590e0eaa435ecea84dabb137284a4dbba7b3c337b58b65b76d95/pyarrow-18.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f266a2c0fc31995a06ebd30bcfdb7f615d7278035ec5b1cd71c48d56daaf30b0", size = 40076877, upload-time = "2024-11-26T01:59:31.926Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a3/817ac7fe0891a2d66e247e223080f3a6a262d8aefd77e11e8c27e6acf4e1/pyarrow-18.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:d4f13eee18433f99adefaeb7e01d83b59f73360c231d4782d9ddfaf1c3fbde0a", size = 25119811, upload-time = "2024-11-26T01:59:35.669Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620, upload-time = "2024-11-26T01:59:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494, upload-time = "2024-11-26T01:59:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624, upload-time = "2024-11-26T01:59:49.189Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341, upload-time = "2024-11-26T01:59:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629, upload-time = "2024-11-26T01:59:59.966Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661, upload-time = "2024-11-26T02:00:04.55Z" }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330, upload-time = "2024-11-26T02:00:09.576Z" }, +] + +[[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 = "pydata-sphinx-theme" +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673, upload-time = "2024-06-25T19:28:45.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" } }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[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 = "pyreadr" +version = "0.5.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/a8/6235ba50870da6b9b5b1d651ff64fdfb36716e1c2d63b347f1abe9c7ce3b/pyreadr-0.5.6.tar.gz", hash = "sha256:ec10bef1798a4d9e91ff75ab69fbcd009a3fad2250c79f5377e19e787c949b8f", size = 1300692, upload-time = "2026-04-13T16:57:32.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/5a/418c23e854c7a153cbf35e190607640dd1e66d76e43f7c2063af584d52d4/pyreadr-0.5.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:97b0885cc41e437465b78b2d41ec574d98ac2733ccf52c6181f5a19186690906", size = 319414, upload-time = "2026-04-13T16:56:48.488Z" }, + { url = "https://files.pythonhosted.org/packages/26/10/78c55925b22a321e6143a870ff03fcb8648f39bb1f2ac7af2f015f82aa88/pyreadr-0.5.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6fdfc5071c1620e48a61a0ec42a5e08ea5d3b643cd892a474a26f09471d9a965", size = 320139, upload-time = "2026-04-13T16:56:50.265Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/dc8bd69ae20d56a78d992be7ceeea894f6131f2875b81716b9a85676a1cb/pyreadr-0.5.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d70ad2cfe2ad3684e9596cefebc328b88585a1f882d8fa8a364d73d456df120", size = 788067, upload-time = "2026-04-13T16:56:52.058Z" }, + { url = "https://files.pythonhosted.org/packages/71/82/b5d2827100766d0efd657ad40d2660779b8df29cf0f940a058b845dc429e/pyreadr-0.5.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:586b816f0e9ea04246f9c632ddc3c20ac12e9bdd80d906275f53b7765a5524b8", size = 786295, upload-time = "2026-04-13T16:56:53.901Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ed/63d1bd713b948a01e8c6a2c4355747715516d92c840f93255e4e5075a14c/pyreadr-0.5.6-cp310-cp310-win_amd64.whl", hash = "sha256:6a5935da52656b945ddfb46b5fb50102c763775d9bb2edbeaf9ef4cb4b186e5c", size = 2361406, upload-time = "2026-04-13T16:56:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/73/ef/ff734bfce765a87adcc06dc3fed9fe8e8b4a65637eb9423306c45f7d43a6/pyreadr-0.5.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fd58ef8fdacd5d04543dc81e1168bc6e1812a1f0e46eb2b0cd85527000c754a0", size = 318002, upload-time = "2026-04-13T16:56:58.222Z" }, + { url = "https://files.pythonhosted.org/packages/39/18/0520f2ec8380cf3c7aaca28c089d07a8c4378071d7e5b06ae49958b89d8e/pyreadr-0.5.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0ebc7c4897ccff75b228f7c7a2414f8a31a4cb38aed070269a4565068ad709", size = 319519, upload-time = "2026-04-13T16:56:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ec/153a7c69a471bb5492c05d680b90fcb84af12b5b7e5097e57c9d1db9bcc9/pyreadr-0.5.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c583ec41ef03902e2f81f7d1c8a3a24a0e2ea25f45f7dd20ac1a01a8c72be31", size = 809699, upload-time = "2026-04-13T16:57:01.002Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/7c71f2ce20141207a86ea2714acb7cd1c8f437e823a372f20c736914aa86/pyreadr-0.5.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09d41870a3c1f6df12352e25e800bedfa633d5bf2177381748a46e65ccd4cf8d", size = 809036, upload-time = "2026-04-13T16:57:02.679Z" }, + { url = "https://files.pythonhosted.org/packages/0e/47/20f5544a20073af354231cb78026c8be0cfa840cec485876da8744dae849/pyreadr-0.5.6-cp311-cp311-win_amd64.whl", hash = "sha256:cd2551051521cde33c491a6054d68dfd3a2dafa97b9909bbc1c63d95bd23d18a", size = 2360322, upload-time = "2026-04-13T16:57:04.326Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/be007324a855ecfe8ec6fee8e48585bee516931da1f096e6dae3849a91c6/pyreadr-0.5.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:abd86d9ad317e51e0b311f02c919cccb395979dca67b7f7641a605b1b0c39943", size = 323479, upload-time = "2026-04-13T16:57:05.881Z" }, + { url = "https://files.pythonhosted.org/packages/85/2a/9e2c0542f9408ed342861aedda58e50bf9f3b773816ee377739a707fc656/pyreadr-0.5.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0fa4f4df894277b875245052bed1c2f58fa6bbf5a98d0adcf81ad5df9599822e", size = 323684, upload-time = "2026-04-13T16:57:07.24Z" }, + { url = "https://files.pythonhosted.org/packages/da/9d/b1a56e4760abf1f36df269fdf263b55bc4c4311896a7b30f00c13aac31c8/pyreadr-0.5.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc5ef88115561aadb6243feaf36c129435eac72fd2fc2b9316cb32a31d5bbc23", size = 789413, upload-time = "2026-04-13T16:57:08.962Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2e/458c6c8ae6a7854a0097e664f52ee322a98faa79b8f210b65bfc798b240b/pyreadr-0.5.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62cb6f016aa1cf7849c268c7b01aa3e56712590dd8b4c8fe66e86a2f4de500a6", size = 788334, upload-time = "2026-04-13T16:57:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0f/849c14794794c45ef2bd14838a103ddb51ddcbdbbfb922877d318b337b39/pyreadr-0.5.6-cp312-cp312-win_amd64.whl", hash = "sha256:7591ba29347b1865b14f335cc43040c4c898d28ae99e286611d81ea954d17b35", size = 2361799, upload-time = "2026-04-13T16:57:12.393Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-cov" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/15/da3df99fd551507694a9b01f512a2f6cf1254f33601605843c3775f39460/pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6", size = 63245, upload-time = "2023-05-24T18:44:56.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/4b/8b78d126e275efa2379b1c2e09dc52cf70df16fc3b90613ef82531499d73/pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a", size = 21949, upload-time = "2023-05-24T18:44:54.079Z" }, +] + +[[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.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/ff/3cc9165fd44106973cd7ac9facb674a65ed853494592541d339bdc9a30eb/python_json_logger-4.1.0.tar.gz", hash = "sha256:b396b9e3ed782b09ff9d6e4f1683d46c83ad0d35d2e407c09a9ebbf038f88195", size = 17573, upload-time = "2026-03-29T04:39:56.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/ef/2d27f30c59a67be7025b2d7858c8c2d282b74d66544b2384730b82de74fd/pywinpty-3.0.5.tar.gz", hash = "sha256:61db0db063de9865adbea66db294628f8577f608d9764a4c7d3384eeacc4e81b", size = 16223484, upload-time = "2026-06-11T00:11:58.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ff/8bd91d4502b48bee0459bc912806fa512111905ab97d317e7cb1201b3542/pywinpty-3.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:b467dcad72365bc2205ed8b6e694e817d71de269d46e56cfe267dfa9d3d30e1b", size = 2092426, upload-time = "2026-06-10T23:40:23.292Z" }, + { url = "https://files.pythonhosted.org/packages/00/d2/dee13e67af2300a080dea30d4da8fdb05133a7eebb39171111f0530f7e88/pywinpty-3.0.5-cp310-cp310-win_arm64.whl", hash = "sha256:7dc4046ea8e4d7f0a16dae8dfcaeeda6df7ca3a9330444d2ba5bb96138fe0a91", size = 818083, upload-time = "2026-06-10T23:42:04.203Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5c/31feb3dd82d1b33ae0bd09ca601edb993d9da1b7f0226b3336d4b4c39e1e/pywinpty-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:af7a8720c78776ddd6259b71dd567944f766a6cd67f8d2887fbc4973967bacda", size = 2092466, upload-time = "2026-06-10T23:44:24.453Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fe/fe23e2229ffec0c10190cef5964f5c9b2dba179d23b69ae537b7ea90bcab/pywinpty-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:c2406f54f699eab75953fb75ce805f2ae55a33a957cd070890abd454fb4b7680", size = 818395, upload-time = "2026-06-10T23:41:56.93Z" }, + { url = "https://files.pythonhosted.org/packages/45/34/942cc95ca4e26489875aa8a95192766247a687379ec29543eebe73ec945f/pywinpty-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:d62946adf14b15b54c0b8d785f93fe18b04da23f4ad59e2e8c4612646e9abd23", size = 2090915, upload-time = "2026-06-10T23:43:14.98Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/5b9053004844139ea8bd86209c57ade12b134b2782f383a095784c8531ec/pywinpty-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:e9391c05fbfa7a992a97e831fc6849887b4014a614192e3d984a7ca59592b376", size = 815934, upload-time = "2026-06-10T23:41:42.384Z" }, +] + +[[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/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { 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" }, +] + +[[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/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { 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/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { 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", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions" }, +] +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.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[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 = "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 = "roman-numerals-py" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "roman-numerals" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +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/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { 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/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 = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", +] +dependencies = [ + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, +] + +[[package]] +name = "seaborn" +version = "0.13.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/11/00d3c3dfc25ad54e731d91449895a79e4bf2384dc3ac01809010ba88f6d5/seaborn-0.13.2-py3-none-any.whl", hash = "sha256:636f8336facf092165e27924f223d3c62ca560b1f2bb5dff7ab7fad265361987", size = 294914, upload-time = "2024-01-25T13:21:49.598Z" }, +] + +[[package]] +name = "selectsim" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "joblib" }, + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "seaborn" }, + { name = "tqdm" }, +] + +[package.optional-dependencies] +docs = [ + { name = "jupyter" }, + { name = "myst-nb" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-autodoc-typehints" }, + { name = "sphinx-book-theme", version = "1.1.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx-book-theme", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "sphinx-copybutton" }, +] +fast = [ + { name = "jax" }, + { name = "jaxlib" }, +] +storage = [ + { name = "zarr", version = "2.18.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "zarr", version = "2.18.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] + +[package.dev-dependencies] +dev = [ + { name = "ipython" }, + { name = "pyreadr" }, + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "jax", marker = "extra == 'fast'", specifier = ">=0.4.35,<0.5" }, + { name = "jaxlib", marker = "extra == 'fast'", specifier = ">=0.4.35,<0.5" }, + { name = "joblib", specifier = ">=1.4.0,<2" }, + { name = "jupyter", marker = "extra == 'docs'", specifier = ">=1.1.0,<2" }, + { name = "matplotlib", specifier = ">=3.9.0,<4" }, + { name = "myst-nb", marker = "extra == 'docs'", specifier = ">=1.1.0,<2" }, + { name = "numpy", specifier = ">=2.1.3,<3" }, + { name = "pandas", specifier = ">=2.2.3,<3" }, + { name = "pyarrow", specifier = ">=18.0.0,<19" }, + { name = "scipy", specifier = ">=1.14.1,<2" }, + { name = "seaborn", specifier = ">=0.13.0,<0.14" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.1.3,<9" }, + { name = "sphinx-autodoc-typehints", marker = "extra == 'docs'", specifier = ">=2.3.0,<3" }, + { name = "sphinx-book-theme", marker = "extra == 'docs'", specifier = ">=1.1.3,<2" }, + { name = "sphinx-copybutton", marker = "extra == 'docs'", specifier = ">=0.5.2,<0.6" }, + { name = "tqdm", specifier = ">=4.66.0,<5" }, + { name = "zarr", marker = "extra == 'storage'", specifier = ">=2.18.0,<3" }, +] +provides-extras = ["fast", "storage", "docs"] + +[package.metadata.requires-dev] +dev = [ + { name = "ipython", specifier = ">=8.30.0,<9" }, + { name = "pyreadr", specifier = ">=0.5.0,<0.6" }, + { name = "pytest", specifier = ">=8.0.0,<9" }, + { name = "pytest-cov", specifier = ">=4.1.0,<5" }, +] + +[[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 = "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 = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "8.2.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "roman-numerals-py" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" }, +] + +[[package]] +name = "sphinx-autodoc-typehints" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/a0/4f17d564c86aa269749d99dd498a5c94abe0915d2ff349187f8ff8c75994/sphinx_autodoc_typehints-2.5.0.tar.gz", hash = "sha256:259e1026b218d563d72743f417fcc25906a9614897fe37f91bd8d7d58f748c3b", size = 40822, upload-time = "2024-10-09T01:42:59.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/ae/322d05bec884977b89eced3af811c228652a9e25f9646ee6236890987214/sphinx_autodoc_typehints-2.5.0-py3-none-any.whl", hash = "sha256:53def4753239683835b19bfa8b68c021388bd48a096efcb02cdab508ece27363", size = 20104, upload-time = "2024-10-09T01:42:57.945Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "pydata-sphinx-theme", version = "0.15.4", source = { registry = "https://pypi.org/simple" } }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188, upload-time = "2025-02-20T16:32:32.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952, upload-time = "2025-02-20T16:32:31.009Z" }, +] + +[[package]] +name = "sphinx-book-theme" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "pydata-sphinx-theme", version = "0.16.1", source = { registry = "https://pypi.org/simple" } }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/f7/154786f3cfb7692cd7acc24b6dfe4dcd1146b66f376b17df9e47125555e9/sphinx_book_theme-1.2.0.tar.gz", hash = "sha256:4a7ebfc7da4395309ac942ddfc38fbec5c5254c3be22195e99ad12586fbda9e3", size = 443962, upload-time = "2026-03-09T23:20:30.442Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/bf/6f506a37c7f8ecc4576caf9486e303c7af249f6d70447bb51dde9d78cb99/sphinx_book_theme-1.2.0-py3-none-any.whl", hash = "sha256:709605d308e1991c5ef0cf19c481dbe9084b62852e317fafab74382a0ee7ccfa", size = 455936, upload-time = "2026-03-09T23:20:28.788Z" }, +] + +[[package]] +name = "sphinx-copybutton" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +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 = "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.51" +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/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" }, + { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + +[[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 = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[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 = "tinycss2" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" }, + { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[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.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[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 = "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 = "wrapt" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/a4/282c8e64300a59fc834518a54bf0afabb4ff9218b5fa76958b450459a844/wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302", size = 129068, upload-time = "2026-06-20T23:49:44.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/8b/59781d0fe7b0adfbea37f600857de4be68921e454aeecf1a11bda35cdccc/wrapt-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:055e6fcfaa28e58c6a8c247d48b92be9d56f818b7068aa4f22b15b3343a09931", size = 80556, upload-time = "2026-06-20T23:47:28.473Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/66c61aca927230c9cf97a3cb005c803971a1076ff9f7d61085d035c20085/wrapt-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8374eb6b1a58809211e84ff835a182bb17ab2807a5bfef23204c8cff38178a00", size = 81648, upload-time = "2026-06-20T23:47:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/23/1b/545eee1c18f3af4cf140bb5822b6ef81ebe569df0a63ac109973103a30a5/wrapt-2.2.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:656593bb3f5529f03d27af4136c4d7b11990e470bcbc6fefa5ef218695bece55", size = 152956, upload-time = "2026-06-20T23:47:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/44/a7/6f42a3d03e44dc612a5dcff324e7366075a7857f0be2d49a8cb8a68279b8/wrapt-2.2.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb00cb7bb22099e2f64b7340fb96113639aa7260c0972af3797ace2297b936c", size = 154771, upload-time = "2026-06-20T23:47:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/bf/55/4d76175aaa97523c38f1d28f79d18ab41a1b116814158a818bc0eba00571/wrapt-2.2.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7f10ee0bd53673bfd52b67cbce83336fe6cad90d2377b03baf66491d2bbfb91", size = 149460, upload-time = "2026-06-20T23:47:34.712Z" }, + { url = "https://files.pythonhosted.org/packages/84/9b/12e23264d8f4735e8483262f95c5a6b03c3665fd2a84bdf99a45b6a2f4ec/wrapt-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4402f57c5f0d0579599858ffbdd9bf4e3f0972f51096f2bd6cc7dab6b76ee49e", size = 153648, upload-time = "2026-06-20T23:47:36.092Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a3/bcd5ec37289dcd85ecd4d15395a6a6063d60bc45ff94a9d77814e1e54d64/wrapt-2.2.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3a4eb7964ff4643d333c84f880bcf554652b2a1050aebc54ae696327f61acfaf", size = 148502, upload-time = "2026-06-20T23:47:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/716d708f607fa70f8a6eb47dff8ee945d5278dfc89ffeeff33039d052e63/wrapt-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e542b7c5af91e2123a8aabf19894319d5ec4268d2a9ffd2f239386133fc47746", size = 152238, upload-time = "2026-06-20T23:47:39.118Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c0/1a48e7e54501274f5d906f18372221b13183b0afbb5b8bb4c7ca0392c0b4/wrapt-2.2.2-cp310-cp310-win32.whl", hash = "sha256:6e7e45b43d3c774d244fe7264378f5a3f0f383bc55a54a9866434e524540110f", size = 77278, upload-time = "2026-06-20T23:47:40.476Z" }, + { url = "https://files.pythonhosted.org/packages/b0/82/9cd69a1af288fbdedf01a10e3c8a0b6890b08c7f3f96d36a213699dbcd94/wrapt-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:955f1d6e72a352e478de8d8b503abe301c5e139a141b62eb0923bd694995025f", size = 80131, upload-time = "2026-06-20T23:47:41.785Z" }, + { url = "https://files.pythonhosted.org/packages/7f/73/8db7e27daef37ae70a53ea62bef7fe80cc51a8b5e9e9181a8be6eb9a999c/wrapt-2.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:b89d8d73c82db2bb7e6090b3afd7973f980d24e905cc34394eab60b884b3bf67", size = 79615, upload-time = "2026-06-20T23:47:43.109Z" }, + { url = "https://files.pythonhosted.org/packages/27/15/0c2d55168707465abfc41f33c0b23d792a5fa9b65c26983606940900a120/wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73", size = 80782, upload-time = "2026-06-20T23:47:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b5/5c0b093eb48f8a062ef6267d3cb36e9bb1b88440181f6545a383c60efdf8/wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e", size = 81678, upload-time = "2026-06-20T23:47:45.857Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/de70937472dd3e8a4e6811192f9c6075efdffd4a2cd9b4596bf160f89668/wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d", size = 159671, upload-time = "2026-06-20T23:47:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/40aed2330e7f02ecf74386ffcfef9ccb7108c6a430f15b6a252b663b1bed/wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328", size = 160785, upload-time = "2026-06-20T23:47:48.759Z" }, + { url = "https://files.pythonhosted.org/packages/45/04/aa5309beed5344b00220ae6b3b24055852192656194c27947bee1736306a/wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5", size = 153699, upload-time = "2026-06-20T23:47:50.177Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/2def7e99d1fe87eea413f95f671924cdddcb08823b1ffd212748dfa6d062/wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0", size = 159695, upload-time = "2026-06-20T23:47:51.602Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f6/a906d01a2ce12157bad2404957b3e2140da354b8a70b2fa48bbf282871c0/wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae", size = 152813, upload-time = "2026-06-20T23:47:53.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/49/bc0086292d239575b4c08f4cf8a4079fa58abbad58ec23abf84833a283ed/wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099", size = 158809, upload-time = "2026-06-20T23:47:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/55/83/8fbd034de1f3e907edaa18786d5dd8f6932874edee0826c7cecb5cab03a1/wrapt-2.2.2-cp311-cp311-win32.whl", hash = "sha256:5499236ad1dc116012e2a5dd943f3f31af12fce452128e2bbcbd55a7d3d4d14c", size = 77414, upload-time = "2026-06-20T23:47:55.882Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9c/23695baa331c6de4e874c3d78b8e0bed92e1d2a274e665b29858f6841672/wrapt-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8636809939152be6ae20a6cef0fed9fe60f411b47847d0426a826884b469e971", size = 80368, upload-time = "2026-06-20T23:47:57.237Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/40cefc342bf89b234a4490d741290fce781774b831aefb39c25471da96c9/wrapt-2.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:5d0a142f7af07caeb5e5da87493162a7b8efa19ba919e550a746f7446e13fb30", size = 79489, upload-time = "2026-06-20T23:47:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/180b40628b23772692a0c76e8030114e1c0ae068470ed531919f0a5f2a4a/wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b", size = 81484, upload-time = "2026-06-20T23:47:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/21c90f2a16689702e2aaff45795b11018dff2c9b1242bac10d225483f676/wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb", size = 82151, upload-time = "2026-06-20T23:48:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/7e6e9fcf4fe7e1b69a49fe6cc5a44e8224bab6283c5233c97e132f14908e/wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a", size = 169828, upload-time = "2026-06-20T23:48:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/894f132d857ed5a9904d937baf368badcbe5ea9e436e2f1930fe21c9f1f0/wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900", size = 171544, upload-time = "2026-06-20T23:48:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/29/de/3c833e03725b477e9ea34028224dd21a48781830101e4e036f77e8b6b102/wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79", size = 160663, upload-time = "2026-06-20T23:48:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/27edce350b24e3054d9d047f65f16d4c4d4c1f3f31c4278a1f8a95c723c8/wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a", size = 169387, upload-time = "2026-06-20T23:48:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/9fd9679af8bf38e146652c7f47b6b352c3e5795b4ad1c0b7f94e15ac2aa7/wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf", size = 158849, upload-time = "2026-06-20T23:48:08.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c2/aa6c0c2206803068c6859dabe01f8c84c43744da93d4c67b8946d21655ee/wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab", size = 168147, upload-time = "2026-06-20T23:48:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/42/63/3eb25da41049d20ae18fcab2dd8b056e02387c4bfa626cbdfb7c3b872e4f/wrapt-2.2.2-cp312-cp312-win32.whl", hash = "sha256:ef2cce266b5b0b07e19fa82e59673b81142b7a3607c8ed1254113d048ed668da", size = 77734, upload-time = "2026-06-20T23:48:11.769Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/0390e008a305360948fa9ce69507d041ac12cb2ee5d28e34467e2ee79391/wrapt-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:abf8c20a2d72ee69e16328b3c91342c446e723bfe48bfcc4dded3b9722ac027f", size = 80585, upload-time = "2026-06-20T23:48:13.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/84c445c66969f2d3457276b183a48c91097d59bbef9af6c075366b0f8c36/wrapt-2.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:c6c64c5d02578bc4c4bca4f0aef1504de933c1d5b4ac2710b9131111459506c8", size = 79553, upload-time = "2026-06-20T23:48:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d2/6317eb6d4554855bbf12d61857774af34747bf88a42c19bf306de67e2fa3/wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048", size = 61460, upload-time = "2026-06-20T23:49:42.966Z" }, +] + +[[package]] +name = "zarr" +version = "2.18.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "asciitree" }, + { name = "fasteners", marker = "sys_platform != 'emscripten'" }, + { name = "numcodecs", version = "0.13.1", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/c4/187a21ce7cf7c8f00c060dd0e04c2a81139bb7b1ab178bba83f2e1134ce2/zarr-2.18.3.tar.gz", hash = "sha256:2580d8cb6dd84621771a10d31c4d777dca8a27706a1a89b29f42d2d37e2df5ce", size = 3603224, upload-time = "2024-09-04T23:20:16.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/142095e654c2b97133ff71df60979422717b29738b08bc8a1709a5d5e0d0/zarr-2.18.3-py3-none-any.whl", hash = "sha256:b1f7dfd2496f436745cdd4c7bcf8d3b4bc1dceef5fdd0d589c87130d842496dd", size = 210723, upload-time = "2024-09-04T23:20:14.491Z" }, +] + +[[package]] +name = "zarr" +version = "2.18.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version == '3.11.*'", +] +dependencies = [ + { name = "asciitree" }, + { name = "fasteners", marker = "sys_platform != 'emscripten'" }, + { name = "numcodecs", version = "0.15.1", source = { registry = "https://pypi.org/simple" } }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/1d/01cf9e3ab2d85190278efc3fca9f68563de35ae30ee59e7640e3af98abe3/zarr-2.18.7.tar.gz", hash = "sha256:b2b8f66f14dac4af66b180d2338819981b981f70e196c9a66e6bfaa9e59572f5", size = 3604558, upload-time = "2025-04-09T07:59:28.482Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/d8/9ffd8c237b3559945bb52103cf0eed64ea098f7b7f573f8d2962ef27b4b2/zarr-2.18.7-py3-none-any.whl", hash = "sha256:ac3dc4033e9ae4e9d7b5e27c97ea3eaf1003cc0a07f010bd83d5134bf8c4b223", size = 211273, upload-time = "2025-04-09T07:59:27.039Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +]