diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 3011cc1..23e4667 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -4,43 +4,31 @@ description: Build the package runs: using: "composite" steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 with: python-version: '3.14' - - name: Install dependencies - run: | - python3 -m pip install --upgrade pip - python3 -m pip install flake8 pytest - python3 -m pip install poetry - python3 -m pip install twine - shell: bash - - - name: Lint with flake8 + - name: Lint with ruff run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=88 --statistics + uvx ruff check . --select=E9,F63,F7,F82 --output-format=full + # exit-zero treats all findings (line length + complexity) as warnings + uvx ruff check . --select=E501,C901 --line-length=88 --exit-zero --statistics shell: bash - - name: Install CVMatrix dependencies - run: poetry install - shell: bash - - name: Build a binary wheel and a source tarball - run: poetry build + run: uv build shell: bash - + - name: Check the distribution with twine - run: twine check dist/* + run: uvx twine check dist/* shell: bash - + - name: Store the distribution packages uses: actions/upload-artifact@v4 with: name: python-package-distributions - path: dist/ \ No newline at end of file + path: dist/ diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml index ed89ad9..c675176 100644 --- a/.github/actions/test/action.yml +++ b/.github/actions/test/action.yml @@ -5,31 +5,33 @@ inputs: PYTHON_VERSION: description: Test Python version required: true + INSTALL_JAX: + description: Whether to install the optional JAX backend extra (true/false) + required: false + default: 'true' runs: using: "composite" steps: - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v5 with: python-version: ${{ inputs.PYTHON_VERSION }} - - name: Install Poetry - run: | - python -m pip install --upgrade pip - python -m pip install poetry - shell: bash - - - name: Install project dependencies - run: poetry install + - name: Install project dependencies (with JAX backend) + if: ${{ inputs.INSTALL_JAX == 'true' }} + run: uv sync --extra jax --group dev shell: bash - - name: Install additional dependencies for testing - run: | - poetry add --group dev pandas pytest flake8 pytest-cov typeguard + - name: Install project dependencies (numpy-only, without JAX) + if: ${{ inputs.INSTALL_JAX != 'true' }} + run: uv sync --group dev shell: bash + # The same test suite runs in both modes. With the JAX backend installed it also + # exercises the parametrized backend="jax" tests; without JAX those parametrizations + # are skipped (pytest.importorskip), verifying the numpy-only path. - name: Run tests run: | - poetry run pytest tests --doctest-modules --junitxml=junit/test-results.xml --cov=cvmatrix/ --cov-report=xml --typeguard-packages=cvmatrix/ + uv run pytest tests --doctest-modules --junitxml=junit/test-results.xml --cov=cvmatrix/ --cov-report=xml --typeguard-packages=cvmatrix/ shell: bash diff --git a/.github/workflows/pull_request_package_workflow.yml b/.github/workflows/pull_request_package_workflow.yml index fad9e84..28f6cb1 100644 --- a/.github/workflows/pull_request_package_workflow.yml +++ b/.github/workflows/pull_request_package_workflow.yml @@ -8,7 +8,7 @@ on: - 'tests/**' - '.github/**' - 'pyproject.toml' - - 'poetry.lock' + - 'uv.lock' jobs: build_package: diff --git a/.github/workflows/pull_request_test_workflow.yml b/.github/workflows/pull_request_test_workflow.yml index 9e29b2b..0e35627 100644 --- a/.github/workflows/pull_request_test_workflow.yml +++ b/.github/workflows/pull_request_test_workflow.yml @@ -8,14 +8,15 @@ on: - 'tests/**' - '.github/**' - 'pyproject.toml' - - 'poetry.lock' + - 'uv.lock' jobs: - test_package: + # Full test suite with the JAX backend installed (also runs backend="jax" tests) + test_package_with_jax: runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: + strategy: + fail-fast: false + matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.11", "3.12", "3.13", "3.14"] steps: @@ -23,17 +24,41 @@ jobs: - uses: ./.github/actions/test with: PYTHON_VERSION: ${{ matrix.python-version }} - + INSTALL_JAX: 'true' + + - name: Upload coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.xml + flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }}-jax + parallel: true + + # Numpy-only installation (without JAX); backend="jax" tests are skipped + test_package_numpy_only: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/test + with: + PYTHON_VERSION: ${{ matrix.python-version }} + INSTALL_JAX: 'false' + - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: coverage.xml - flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }} + flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }}-numpy-only parallel: true finish: - needs: test_package + needs: [test_package_with_jax, test_package_numpy_only] runs-on: ubuntu-latest steps: - name: Finish Coveralls diff --git a/.github/workflows/test_workflow.yml b/.github/workflows/test_workflow.yml index 0b90c33..3c01227 100644 --- a/.github/workflows/test_workflow.yml +++ b/.github/workflows/test_workflow.yml @@ -8,14 +8,15 @@ on: - 'tests/**' - '.github/**' - 'pyproject.toml' - - 'poetry.lock' + - 'uv.lock' jobs: - test_package: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: + # Full test suite with the JAX backend installed (also runs backend="jax" tests) + test_package_with_jax: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.11", "3.12", "3.13", "3.14"] steps: @@ -23,17 +24,41 @@ jobs: - uses: ./.github/actions/test with: PYTHON_VERSION: ${{ matrix.python-version }} - + INSTALL_JAX: 'true' + + - name: Upload coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: coverage.xml + flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }}-jax + parallel: true + + # Numpy-only installation (without JAX); backend="jax" tests are skipped + test_package_numpy_only: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.11", "3.12", "3.13", "3.14"] + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/test + with: + PYTHON_VERSION: ${{ matrix.python-version }} + INSTALL_JAX: 'false' + - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: coverage.xml - flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }} + flag-name: ${{ matrix.os }}-python-${{ matrix.python-version }}-numpy-only parallel: true finish: - needs: test_package + needs: [test_package_with_jax, test_package_numpy_only] runs-on: ubuntu-latest steps: - name: Finish Coveralls diff --git a/CHANGELOG.md b/CHANGELOG.md index 6960dd2..6b7f5fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [4.0.0] - 2025 +## [3.2.0] - 2026-06-29 + +### Added +- Optional **JAX backend** for `CVMatrix` via `backend="jax"` (a `Literal["numpy", "jax"]`; default remains `"numpy"`). All array operations are routed through a resolved array namespace (`numpy` or `jax.numpy`), so the per-fold `training_XTX`/`training_XTY`/`training_XTX_XTY`/`training_statistics` computations can be traced by `jax.jit` and batched with `jax.vmap` on CPU/GPU/TPU. Install with `cvmatrix[jax]`. +- Backend-neutral, trace-safe standard-deviation clamping (`maximum`/`where` instead of boolean-mask assignment). Degenerate-fold validation (`ValueError`s) still fires for eager (numpy or concrete jax) execution; under `jax.jit`/`jax.vmap` tracing the check is deferred to a host-side pre-flight by the caller. + +### Changed +- Switched build and development tooling from Poetry to [uv](https://docs.astral.sh/uv/): the project now uses PEP 621 `[project]` metadata, the `hatchling` build backend, and a PEP 735 `[dependency-groups]` dev group (`poetry.lock` is replaced by `uv.lock`). The published package, its runtime dependencies, and the `jax` extra are unchanged. + +### Notes +- The numpy backend is byte-identical to previous releases (verified against the existing test suite) and performance-neutral. + +## [3.1.6] - 2025 ### Changed - `CVMatrix` and `Partitioner` can now be imported with `from cvmatrix import CVMatrix, Partitioner` or `import cvmatrix` followed by `cvmatrix.CVMatrix` and `cvmatrix.Partitioner`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b4eefc..afff81c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,7 +44,7 @@ If you feel like you've made a valuable contribution, but you don't know how to ## Build from source -CVMatrix uses [poetry](https://python-poetry.org/) to manage its dependencies and packaging. To build the package from source, follow these steps: +CVMatrix uses [uv](https://docs.astral.sh/uv/) to manage its dependencies and packaging. To build the package from source, follow these steps: 1. Clone the repository: @@ -58,57 +58,50 @@ CVMatrix uses [poetry](https://python-poetry.org/) to manage its dependencies an cd CVMatrix ``` -3. Install poetry and twine: +3. [Install uv](https://docs.astral.sh/uv/getting-started/installation/), e.g.: ```shell - pip3 install poetry - pip3 install twine + curl -LsSf https://astral.sh/uv/install.sh | sh ``` -4. Install the dependencies: +4. Build the package. uv builds in an isolated environment, so no separate dependency-install step is needed: ```shell - poetry install + uv build ``` -5. Build the package: +5. Check the package with twine: ```shell - poetry build - ``` - -6. Check the package with twine: - - ```shell - twine check dist/* + uvx twine check dist/* ``` ## Run the test suite To run the test suite, follow these steps: -1. Install poetry: +1. [Install uv](https://docs.astral.sh/uv/getting-started/installation/), e.g.: ```shell - pip3 install poetry + curl -LsSf https://astral.sh/uv/install.sh | sh ``` -2. Install the project dependencies with poetry: +2. Install the project together with its development dependencies. To also exercise the optional JAX backend (the `backend="jax"` tests), include the `jax` extra: ```shell - poetry install + uv sync --extra jax --group dev ``` -3. Install additional dependencies for testing: + To test the numpy-only installation instead (the `backend="jax"` tests are skipped automatically), omit the extra: ```shell - poetry add --group dev pandas pytest flake8 pytest-cov typeguard + uv sync --group dev ``` -4. Now, the tests can be run with the following command: +3. Now, the tests can be run with the following command: ```shell - poetry run pytest tests --doctest-modules --junitxml=junit/test-results.xml --cov=cvmatrix/ --cov-report=xml --cov-report=html --typeguard-packages=cvmatrix/ + uv run pytest tests --doctest-modules --junitxml=junit/test-results.xml --cov=cvmatrix/ --cov-report=xml --cov-report=html --typeguard-packages=cvmatrix/ ``` ## Build the documentation diff --git a/README.md b/README.md index d3b0139..e406827 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,54 @@ The `cvmatrix` software package now also features **weigthed matrix produts** $\ from cvmatrix import CVMatrix ``` +- You can also install the optional JAX dependency to enable the JAX backend of + `CVMatrix`: + ```shell + pip3 install "cvmatrix[jax]" + ``` +- Now, you can select the JAX backend by passing `backend="jax"` to the constructor: + ```python + from cvmatrix import CVMatrix + + cvm = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, backend="jax" + ) + ``` + With `backend="jax"`, the per-fold training-matrix computations (`training_XTX`, + `training_XTY`, `training_XTX_XTY`, and `training_statistics`) use `jax.numpy` and can + be traced by `jax.jit` and batched over folds with `jax.vmap` on CPUs, GPUs, and TPUs. + The default `backend="numpy"` is unchanged and remains byte-for-byte identical to + previous releases. + +### Prerequisites for the JAX backend + +The JAX backend supports running on CPU, GPU, and TPU. + +- To enable NVIDIA GPU execution, install JAX and CUDA with: + ```shell + pip3 install -U "jax[cuda13]" + ``` + +- To enable Google Cloud TPU execution, install JAX with: + ```shell + pip3 install -U "jax[tpu]" + ``` + +These are typical installation instructions that will be what most users are looking for. +For customized installations, follow the instructions from the [JAX Installation +Guide](https://jax.readthedocs.io/en/latest/installation.html). + +To ensure that the JAX backend uses float64, set the environment variable +```JAX_ENABLE_X64=True``` as per the [Common +Gotchas](https://docs.jax.dev/en/latest/notebooks/Common_Gotchas_in_JAX.html#double-64bit-precision). +`CVMatrix` also enables this automatically when constructed with `backend="jax"` and a +64-bit `dtype` (the default). Alternatively, float64 can be enabled with the following +function call: +```python +import jax +jax.config.update("jax_enable_x64", True) +``` + ## Quick Start ### Use the cvmatrix package for fast computation of training set kernel matrices @@ -96,6 +144,7 @@ The `cvmatrix` software package now also features **weigthed matrix produts** $\ In [examples](https://github.com/Sm00thix/CVMatrix/tree/main/examples), you will find: - [Compute training matrices with CVMatrix](https://github.com/Sm00thix/CVMatrix/tree/main/examples/training_matrices.py) +- [Compute training matrices with the JAX backend, batched over folds with `jax.vmap` on CPU/GPU/TPU](https://github.com/Sm00thix/CVMatrix/tree/main/examples/training_matrices_jax.py) ## Benchmarks @@ -107,6 +156,14 @@ In [benchmarks](https://github.com/Sm00thix/CVMatrix/tree/main/benchmarks), we h Left: Benchmarking cross-validation with the CVMatrix implementation versus the baseline implementation using three common combinations of (column-wise) centering and scaling. Right: Benchmarking cross-validation with the CVMatrix implementation for all possible combinations of (column-wise) centering and scaling. Here, most of the graphs lie on top of eachother. In general, no preprocessing is faster than centering which, in turn, is faster than scaling.

+The figures below add the optional JAX backend to the comparison (NumPy vs. JAX, and the JAX execution modes). They are measured **multi-threaded** (no thread limit; GPU on an NVIDIA RTX 3090 Ti) and **exclude `NaiveCVMatrix`**: + +

+ +
+ Left: CVMatrix with the NumPy backend versus the JAX backend (warm, JIT-compiled jax.vmap over folds) on CPU and GPU, for the three common preprocessing combinations. Right: The JAX backend under no-JIT (eager vmap), cold-JIT (compilation + run) and warm-JIT (run only), on CPU and GPU. JIT compilation is a large fixed cost: on the GPU, no-JIT beats cold-JIT for a single run, whereas on the CPU, JIT (even cold) is far faster than eager vmap at a large number of folds. +

+ ## Contribute To contribute, please read the [Contribution diff --git a/benchmarks/benchmark.py b/benchmarks/benchmark.py index 872878d..7638480 100644 --- a/benchmarks/benchmark.py +++ b/benchmarks/benchmark.py @@ -17,10 +17,8 @@ # Add the parent directory of 'CVMatrix' to sys.path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -# Set the number of threads to 1. This must be done before importing numpy. -os.environ["OMP_NUM_THREADS"] = "1" - from itertools import product +from time import perf_counter from timeit import timeit from typing import Hashable, Iterable, Union @@ -35,15 +33,15 @@ def save_result_to_csv( model, use_weights, P, N, K, M, center_X, center_Y, scale_X, scale_Y, time, version ): + csv_path = os.environ.get("BENCH_CSV", "benchmark_results.csv") try: - with open("benchmark_results.csv", "x") as f: + with open(csv_path, "x") as f: f.write( - "model,weights,P,N,K,M," - "center_X,center_Y,scale_X,scale_Y,time,version\n" + "model,weights,P,N,K,M,center_X,center_Y,scale_X,scale_Y,time,version\n" ) except FileExistsError: pass - with open("benchmark_results.csv", "a") as f: + with open(csv_path, "a") as f: f.write( f"{model},{use_weights},{P},{N},{K},{M}," f"{center_X},{center_Y},{scale_X},{scale_Y}," @@ -61,6 +59,8 @@ def execute_algorithm( X: np.ndarray, Y: np.ndarray, weights: Union[None, np.ndarray], + backend: str = "numpy", + batch_size: int = 10000, ): """ Execute the computation of the training set matrices @@ -98,15 +98,26 @@ def execute_algorithm( The weights for the samples, if any. If None, no weights are used. """ - # Create the model - model = model_class( - center_X=center_X, - center_Y=center_Y, - scale_X=scale_X, - scale_Y=scale_Y, - dtype=X.dtype, - copy=True, - ) + # Create the model. The CVMatrix model takes a `backend` ("numpy" or "jax"). + if model_class is CVMatrix: + model = CVMatrix( + center_X=center_X, + center_Y=center_Y, + scale_X=scale_X, + scale_Y=scale_Y, + dtype=X.dtype, + copy=True, + backend=backend, + ) + else: + model = model_class( + center_X=center_X, + center_Y=center_Y, + scale_X=scale_X, + scale_Y=scale_Y, + dtype=X.dtype, + copy=True, + ) # Create the validation partitioner p = Partitioner(folds=cv_splits) @@ -122,6 +133,23 @@ def execute_algorithm( [p.get_validation_indices(f) for f in p.folds_dict if f != fold] ) model.training_XTX_XTY(training_indices) + elif backend == "jax": + # Batch the per-fold training matrices over folds with jax.vmap (grouping by + # validation-set size so shapes are fixed, and chunking by `batch_size` to bound + # memory). + import jax + import jax.numpy as jnp + from collections import defaultdict + + buckets: dict[int, list] = defaultdict(list) + for fold in p.folds_dict: + vi = p.get_validation_indices(fold) + buckets[vi.shape[0]].append(vi) + vmapped = jax.jit(jax.vmap(model.training_XTX_XTY)) + for vis in buckets.values(): + stack = jnp.asarray(np.stack(vis)) + for s in range(0, len(vis), batch_size): + jax.block_until_ready(vmapped(stack[s : s + batch_size])) else: # Compute the training set matrices for fold in p.folds_dict: @@ -130,26 +158,130 @@ def execute_algorithm( model.training_XTX_XTY(validation_indices) +def benchmark_jax_variants( + cv_splits, center_X, center_Y, scale_X, scale_Y, X, Y, weights, batch_size +): + """ + Times the per-fold training-matrix computation (vmapped over folds, chunked by + `batch_size`) under three JAX execution modes, each including the one-time fit so the + result is the total cross-validation time (comparable to the numpy/naive totals): + + - "nojit": eager ``jax.vmap`` (no JIT compilation). + - "coldjit": ``jax.jit(jax.vmap(...))`` timed on its first call (compilation + run). + - "warmjit": the same jitted function timed on a subsequent call (run only). + + Returns a dict mapping each mode to its total time in seconds. + """ + import jax + import jax.numpy as jnp + from collections import defaultdict + + p = Partitioner(folds=cv_splits) + + t0 = perf_counter() + model = CVMatrix( + center_X=center_X, center_Y=center_Y, scale_X=scale_X, scale_Y=scale_Y, + dtype=X.dtype, copy=True, backend="jax", + ) + model.fit(X, Y, weights) + # Stack the per-fold validation indices into fixed-shape batches (one per fold size). + buckets = defaultdict(list) + for fold in p.folds_dict: + vi = p.get_validation_indices(fold) + buckets[vi.shape[0]].append(vi) + batches = [jnp.asarray(np.stack(vis)) for vis in buckets.values()] + fit_time = perf_counter() - t0 + + def run(vf): + for stack in batches: + for s in range(0, stack.shape[0], batch_size): + jax.block_until_ready(vf(stack[s : s + batch_size])) + + # No JIT: eager vmap. + vf_nojit = jax.vmap(model.training_XTX_XTY) + t0 = perf_counter() + run(vf_nojit) + nojit = perf_counter() - t0 + + # JIT: the first call compiles (cold); a subsequent call reuses it (warm). + vf_jit = jax.jit(jax.vmap(model.training_XTX_XTY)) + t0 = perf_counter() + run(vf_jit) + coldjit = perf_counter() - t0 + t0 = perf_counter() + run(vf_jit) + warmjit = perf_counter() - t0 + + return { + "nojit": fit_time + nojit, + "coldjit": fit_time + coldjit, + "warmjit": fit_time + warmjit, + } + + if __name__ == "__main__": seed = 42 # Seed for reproducibility rng = np.random.default_rng(seed=seed) - N = 100000 # 100k samples - K = 500 # 500 features - M = 10 # 10 targets + N = int(os.environ.get("BENCH_N", 100000)) # 100k samples + K = int(os.environ.get("BENCH_K", 500)) # 500 features + M = int(os.environ.get("BENCH_M", 10)) # 10 targets dtype = np.float64 # Data type X = rng.random((N, K), dtype=dtype) # Random X matrix Y = rng.random((N, M), dtype=dtype) # Random Y matrix weights = rng.random((N,), dtype=dtype) # Random weights cv_splits = np.arange(N) # We can use mod P for P-fold cross-validation use_weights = [True, False] # Whether to use weights or not - center_Xs = [True, False] - center_Ys = [True, False] - scale_Xs = [True, False] - scale_Ys = [True, False] - Ps = [3, 5, 10, 100, 1000, 10000, 100000] - - for use_w, center_X, center_Y, scale_X, scale_Y, P in product( - use_weights, center_Xs, center_Ys, scale_Xs, scale_Ys, Ps + # Preprocessing combinations. BENCH_CONFIGS="plot" restricts to the three + # combinations shown in benchmark_cvmatrix_vs_naive.png (and the ones for which + # NaiveCVMatrix is benchmarked): no preprocessing, centering only, and centering + + # scaling. "all" (the default) sweeps every combination of the four flags. + if os.environ.get("BENCH_CONFIGS", "all") == "plot": + preprocessing_configs = [ + (False, False, False, False), + (True, True, False, False), + (True, True, True, True), + ] + else: + preprocessing_configs = list( + product([True, False], [True, False], [True, False], [True, False]) + ) + # Ps = [3, 5, 10, 100, 1000, 10000, 100000] + Ps = [int(p) for p in os.environ.get("BENCH_PS", "100000").split(",")] + # CVMatrix backends to benchmark (comma-separated). "jax" requires cvmatrix[jax]. + backends = os.environ.get("BENCH_BACKENDS", "numpy").split(",") + batch_size = int(os.environ.get("BENCH_BATCH", 10000)) + + # BENCH_JAX_VARIANTS=1: instead of the numpy/naive/jax sweep, benchmark the JAX + # backend under no-JIT / cold-JIT / warm-JIT (weighted only) for the figure + # benchmark_jax_variants.png. + if os.environ.get("BENCH_JAX_VARIANTS", "0") == "1": + import jax + + platform = jax.devices()[0].platform + for (center_X, center_Y, scale_X, scale_Y), P in product( + preprocessing_configs, Ps + ): + print( + f"JAX variants ({platform}): P={P}, center_X={center_X}, " + f"center_Y={center_Y}, scale_X={scale_X}, scale_Y={scale_Y}" + ) + mode_times = benchmark_jax_variants( + cv_splits % P, center_X, center_Y, scale_X, scale_Y, + X, Y, weights, batch_size, + ) + for mode, t in mode_times.items(): + print(f" CVMatrix-jax-{platform}-{mode}: {t:.2f} seconds") + save_result_to_csv( + f"CVMatrix-jax-{platform}-{mode}", + True, + P, N, K, M, + center_X, center_Y, scale_X, scale_Y, + t, __version__, + ) + sys.exit(0) + + for use_w, (center_X, center_Y, scale_X, scale_Y), P in product( + use_weights, preprocessing_configs, Ps ): print( f"weights={use_w}, " @@ -157,40 +289,56 @@ def execute_algorithm( f"center_X={center_X}, center_Y={center_Y}, " f"scale_X={scale_X}, scale_Y={scale_Y}, " ) - time = timeit( - stmt=lambda: execute_algorithm( - model_class=CVMatrix, - cv_splits=cv_splits % P, - center_X=center_X, - center_Y=center_Y, - scale_X=scale_X, - scale_Y=scale_Y, - X=X, - Y=Y, - weights=weights if use_weights else None, - ), - number=1, - ) - print(f"CVMatrix, Time: {time:.2f} seconds") - save_result_to_csv( - "CVMatrix", - use_w, - P, - N, - K, - M, - center_X, - center_Y, - scale_X, - scale_Y, - time, - __version__, - ) + for backend in backends: + time = timeit( + stmt=lambda backend=backend: execute_algorithm( + model_class=CVMatrix, + cv_splits=cv_splits % P, + center_X=center_X, + center_Y=center_Y, + scale_X=scale_X, + scale_Y=scale_Y, + X=X, + Y=Y, + weights=weights if use_w else None, + backend=backend, + batch_size=batch_size, + ), + number=1, + ) + if backend == "jax": + import jax + + # Distinguish CPU vs GPU/TPU JAX runs in the results. + model_name = f"CVMatrix-jax-{jax.devices()[0].platform}" + else: + model_name = ( + "CVMatrix" if backend == "numpy" else f"CVMatrix-{backend}" + ) + print(f"{model_name}, Time: {time:.2f} seconds") + save_result_to_csv( + model_name, + use_w, + P, + N, + K, + M, + center_X, + center_Y, + scale_X, + scale_Y, + time, + __version__, + ) - if ( + # NaiveCVMatrix is only benchmarked for the three preprocessing combinations + # shown in benchmark_cvmatrix_vs_naive.png (no preprocessing, centering only, and + # centering + scaling). It is extremely slow (hours at large P), so it only runs + # when BENCH_NAIVE=1 is set explicitly. + if os.environ.get("BENCH_NAIVE", "0") == "1" and ( center_X == center_Y == scale_X == scale_Y - or center_X == center_Y == True - and scale_X == scale_Y == False + or center_X == center_Y is True + and scale_X == scale_Y is False ): time = timeit( stmt=lambda: execute_algorithm( @@ -202,7 +350,7 @@ def execute_algorithm( scale_Y=scale_Y, X=X, Y=Y, - weights=weights, + weights=weights if use_w else None, ), number=1, ) diff --git a/benchmarks/benchmark_cvmatrix_numpy_vs_jax.png b/benchmarks/benchmark_cvmatrix_numpy_vs_jax.png new file mode 100644 index 0000000..7aa4e10 Binary files /dev/null and b/benchmarks/benchmark_cvmatrix_numpy_vs_jax.png differ diff --git a/benchmarks/benchmark_jax_variants.png b/benchmarks/benchmark_jax_variants.png new file mode 100644 index 0000000..6cbc718 Binary files /dev/null and b/benchmarks/benchmark_jax_variants.png differ diff --git a/benchmarks/benchmark_results.csv b/benchmarks/benchmark_results.csv index b288dec..bb767dd 100644 --- a/benchmarks/benchmark_results.csv +++ b/benchmarks/benchmark_results.csv @@ -1,134 +1,267 @@ -model,P,N,K,M,center_X,center_Y,scale_X,scale_Y,time,version -CVMatrix,3,100000,500,10,True,True,True,True,1.1192415489999803,1.0.0 -NaiveCVMatrix,3,100000,500,10,True,True,True,True,1.525318347999928,1.0.0 -CVMatrix,5,100000,500,10,True,True,True,True,1.1311610759998985,1.0.0 -NaiveCVMatrix,5,100000,500,10,True,True,True,True,3.018902382000306,1.0.0 -CVMatrix,10,100000,500,10,True,True,True,True,1.1376194539998323,1.0.0 -NaiveCVMatrix,10,100000,500,10,True,True,True,True,6.742205670000203,1.0.0 -CVMatrix,100,100000,500,10,True,True,True,True,1.1367952779996813,1.0.0 -NaiveCVMatrix,100,100000,500,10,True,True,True,True,73.3318574079999,1.0.0 -CVMatrix,1000,100000,500,10,True,True,True,True,1.7896336470003007,1.0.0 -NaiveCVMatrix,1000,100000,500,10,True,True,True,True,729.6242519150001,1.0.0 -CVMatrix,10000,100000,500,10,True,True,True,True,8.568363662000138,1.0.0 -NaiveCVMatrix,10000,100000,500,10,True,True,True,True,7256.869186553,1.0.0 -CVMatrix,100000,100000,500,10,True,True,True,True,87.74830843000018,1.0.0 -NaiveCVMatrix,100000,100000,500,10,True,True,True,True,73826.453205139,1.0.0 -CVMatrix,3,100000,500,10,True,True,True,False,1.118339446009486,1.0.0 -CVMatrix,5,100000,500,10,True,True,True,False,1.123899715996231,1.0.0 -CVMatrix,10,100000,500,10,True,True,True,False,1.12319784500869,1.0.0 -CVMatrix,100,100000,500,10,True,True,True,False,1.114717668009689,1.0.0 -CVMatrix,1000,100000,500,10,True,True,True,False,1.7559398589946795,1.0.0 -CVMatrix,10000,100000,500,10,True,True,True,False,8.289280587996473,1.0.0 -CVMatrix,100000,100000,500,10,True,True,True,False,86.02160608601116,1.0.0 -CVMatrix,3,100000,500,10,True,True,False,True,1.0702028819941916,1.0.0 -CVMatrix,5,100000,500,10,True,True,False,True,1.0765490460034923,1.0.0 -CVMatrix,10,100000,500,10,True,True,False,True,1.0745869239908643,1.0.0 -CVMatrix,100,100000,500,10,True,True,False,True,1.0526132069935557,1.0.0 -CVMatrix,1000,100000,500,10,True,True,False,True,1.4689054019981995,1.0.0 -CVMatrix,10000,100000,500,10,True,True,False,True,5.673868882993702,1.0.0 -CVMatrix,100000,100000,500,10,True,True,False,True,59.158317786990665,1.0.0 -CVMatrix,3,100000,500,10,True,True,False,False,1.071286210004473,1.0.0 -NaiveCVMatrix,3,100000,500,10,True,True,False,False,1.2249300480034435,1.0.0 -CVMatrix,5,100000,500,10,True,True,False,False,1.0742946690006647,1.0.0 -NaiveCVMatrix,5,100000,500,10,True,True,False,False,2.3995333990023937,1.0.0 -CVMatrix,10,100000,500,10,True,True,False,False,1.0759979769936765,1.0.0 -NaiveCVMatrix,10,100000,500,10,True,True,False,False,5.318012420000741,1.0.0 -CVMatrix,100,100000,500,10,True,True,False,False,1.0516475519980304,1.0.0 -NaiveCVMatrix,100,100000,500,10,True,True,False,False,57.35077489100513,1.0.0 -CVMatrix,1000,100000,500,10,True,True,False,False,1.4252776270004688,1.0.0 -NaiveCVMatrix,1000,100000,500,10,True,True,False,False,578.4537375619984,1.0.0 -CVMatrix,10000,100000,500,10,True,True,False,False,5.3335611680086,1.0.0 -NaiveCVMatrix,10000,100000,500,10,True,True,False,False,5781.912272988004,1.0.0 -CVMatrix,100000,100000,500,10,True,True,False,False,56.71489888700307,1.0.0 -NaiveCVMatrix,100000,100000,500,10,True,True,False,False,58952.35878656501,1.0.0 -CVMatrix,3,100000,500,10,True,False,True,True,1.1248798799933866,1.0.0 -CVMatrix,5,100000,500,10,True,False,True,True,1.1283268360130023,1.0.0 -CVMatrix,10,100000,500,10,True,False,True,True,1.1263538440107368,1.0.0 -CVMatrix,100,100000,500,10,True,False,True,True,1.119193897000514,1.0.0 -CVMatrix,1000,100000,500,10,True,False,True,True,1.7814686659839936,1.0.0 -CVMatrix,10000,100000,500,10,True,False,True,True,8.538902194995899,1.0.0 -CVMatrix,100000,100000,500,10,True,False,True,True,87.61705726798391,1.0.0 -CVMatrix,3,100000,500,10,True,False,True,False,1.1210701089876238,1.0.0 -CVMatrix,5,100000,500,10,True,False,True,False,1.1266690100019332,1.0.0 -CVMatrix,10,100000,500,10,True,False,True,False,1.1292253129940946,1.0.0 -CVMatrix,100,100000,500,10,True,False,True,False,1.1189919460157398,1.0.0 -CVMatrix,1000,100000,500,10,True,False,True,False,1.7613697280175984,1.0.0 -CVMatrix,10000,100000,500,10,True,False,True,False,8.302096697007073,1.0.0 -CVMatrix,100000,100000,500,10,True,False,True,False,86.14565880500595,1.0.0 -CVMatrix,3,100000,500,10,True,False,False,True,1.0740263529878575,1.0.0 -CVMatrix,5,100000,500,10,True,False,False,True,1.072119502001442,1.0.0 -CVMatrix,10,100000,500,10,True,False,False,True,1.069851884996751,1.0.0 -CVMatrix,100,100000,500,10,True,False,False,True,1.0546411169925705,1.0.0 -CVMatrix,1000,100000,500,10,True,False,False,True,1.4577812999777962,1.0.0 -CVMatrix,10000,100000,500,10,True,False,False,True,5.598208290990442,1.0.0 -CVMatrix,100000,100000,500,10,True,False,False,True,59.14233927798341,1.0.0 -CVMatrix,3,100000,500,10,True,False,False,False,1.072362840990536,1.0.0 -CVMatrix,5,100000,500,10,True,False,False,False,1.068599329009885,1.0.0 -CVMatrix,10,100000,500,10,True,False,False,False,1.0694805849925615,1.0.0 -CVMatrix,100,100000,500,10,True,False,False,False,1.051443169999402,1.0.0 -CVMatrix,1000,100000,500,10,True,False,False,False,1.4250595640041863,1.0.0 -CVMatrix,10000,100000,500,10,True,False,False,False,5.306501427985495,1.0.0 -CVMatrix,100000,100000,500,10,True,False,False,False,56.23384317601449,1.0.0 -CVMatrix,3,100000,500,10,False,True,True,True,1.1242727210046723,1.0.0 -CVMatrix,5,100000,500,10,False,True,True,True,1.1221562070131768,1.0.0 -CVMatrix,10,100000,500,10,False,True,True,True,1.1232905049982946,1.0.0 -CVMatrix,100,100000,500,10,False,True,True,True,1.093528530007461,1.0.0 -CVMatrix,1000,100000,500,10,False,True,True,True,1.50849640899105,1.0.0 -CVMatrix,10000,100000,500,10,False,True,True,True,5.678116741997656,1.0.0 -CVMatrix,100000,100000,500,10,False,True,True,True,59.44565243599936,1.0.0 -CVMatrix,3,100000,500,10,False,True,True,False,1.1244757279928308,1.0.0 -CVMatrix,5,100000,500,10,False,True,True,False,1.1264495220093522,1.0.0 -CVMatrix,10,100000,500,10,False,True,True,False,1.1244927080115303,1.0.0 -CVMatrix,100,100000,500,10,False,True,True,False,1.0898696849762928,1.0.0 -CVMatrix,1000,100000,500,10,False,True,True,False,1.487639525992563,1.0.0 -CVMatrix,10000,100000,500,10,False,True,True,False,5.532944937003776,1.0.0 -CVMatrix,100000,100000,500,10,False,True,True,False,57.769016504025785,1.0.0 -CVMatrix,3,100000,500,10,False,True,False,True,1.0699797239794862,1.0.0 -CVMatrix,5,100000,500,10,False,True,False,True,1.076646833011182,1.0.0 -CVMatrix,10,100000,500,10,False,True,False,True,1.066042849997757,1.0.0 -CVMatrix,100,100000,500,10,False,True,False,True,1.022873626992805,1.0.0 -CVMatrix,1000,100000,500,10,False,True,False,True,1.1741839119931683,1.0.0 -CVMatrix,10000,100000,500,10,False,True,False,True,2.767864119989099,1.0.0 -CVMatrix,100000,100000,500,10,False,True,False,True,31.288429460983025,1.0.0 -CVMatrix,3,100000,500,10,False,True,False,False,1.067737389006652,1.0.0 -CVMatrix,5,100000,500,10,False,True,False,False,1.0645694179984275,1.0.0 -CVMatrix,10,100000,500,10,False,True,False,False,1.0654148519970477,1.0.0 -CVMatrix,100,100000,500,10,False,True,False,False,1.0180366100103129,1.0.0 -CVMatrix,1000,100000,500,10,False,True,False,False,1.1448387459968217,1.0.0 -CVMatrix,10000,100000,500,10,False,True,False,False,2.4890578409831505,1.0.0 -CVMatrix,100000,100000,500,10,False,True,False,False,28.57835416399757,1.0.0 -CVMatrix,3,100000,500,10,False,False,True,True,1.1212003620166795,1.0.0 -CVMatrix,5,100000,500,10,False,False,True,True,1.1258387090056203,1.0.0 -CVMatrix,10,100000,500,10,False,False,True,True,1.128786824992858,1.0.0 -CVMatrix,100,100000,500,10,False,False,True,True,1.0982693690166343,1.0.0 -CVMatrix,1000,100000,500,10,False,False,True,True,1.4995669290074147,1.0.0 -CVMatrix,10000,100000,500,10,False,False,True,True,5.570729988015955,1.0.0 -CVMatrix,100000,100000,500,10,False,False,True,True,58.68603180799983,1.0.0 -CVMatrix,3,100000,500,10,False,False,True,False,1.1134203580149915,1.0.0 -CVMatrix,5,100000,500,10,False,False,True,False,1.1239128790039103,1.0.0 -CVMatrix,10,100000,500,10,False,False,True,False,1.1241297630185727,1.0.0 -CVMatrix,100,100000,500,10,False,False,True,False,1.087912362010684,1.0.0 -CVMatrix,1000,100000,500,10,False,False,True,False,1.4660285169957206,1.0.0 -CVMatrix,10000,100000,500,10,False,False,True,False,5.316281512990827,1.0.0 -CVMatrix,100000,100000,500,10,False,False,True,False,56.17939067899715,1.0.0 -CVMatrix,3,100000,500,10,False,False,False,True,1.0351853250176646,1.0.0 -CVMatrix,5,100000,500,10,False,False,False,True,1.033534198009875,1.0.0 -CVMatrix,10,100000,500,10,False,False,False,True,1.0377773079962935,1.0.0 -CVMatrix,100,100000,500,10,False,False,False,True,0.9942518580064644,1.0.0 -CVMatrix,1000,100000,500,10,False,False,False,True,1.1315311660000589,1.0.0 -CVMatrix,10000,100000,500,10,False,False,False,True,2.5680760239774827,1.0.0 -CVMatrix,100000,100000,500,10,False,False,False,True,29.533790613000747,1.0.0 -CVMatrix,3,100000,500,10,False,False,False,False,1.0287453010096217,1.0.0 -NaiveCVMatrix,3,100000,500,10,False,False,False,False,1.0761993249761872,1.0.0 -CVMatrix,5,100000,500,10,False,False,False,False,1.0289292840170674,1.0.0 -NaiveCVMatrix,5,100000,500,10,False,False,False,False,2.1083633030066267,1.0.0 -CVMatrix,10,100000,500,10,False,False,False,False,1.0330040709814057,1.0.0 -NaiveCVMatrix,10,100000,500,10,False,False,False,False,4.668692281993572,1.0.0 -CVMatrix,100,100000,500,10,False,False,False,False,0.9845351910043972,1.0.0 -NaiveCVMatrix,100,100000,500,10,False,False,False,False,50.40878304699436,1.0.0 -CVMatrix,1000,100000,500,10,False,False,False,False,1.0881256299908273,1.0.0 -NaiveCVMatrix,1000,100000,500,10,False,False,False,False,506.2128036149952,1.0.0 -CVMatrix,10000,100000,500,10,False,False,False,False,2.194315168017056,1.0.0 -NaiveCVMatrix,10000,100000,500,10,False,False,False,False,5044.3570687549945,1.0.0 -CVMatrix,100000,100000,500,10,False,False,False,False,26.013640838005813,1.0.0 -NaiveCVMatrix,100000,100000,500,10,False,False,False,False,52100.47697248199,1.0.0 +model,weights,P,N,K,M,center_X,center_Y,scale_X,scale_Y,time,version +CVMatrix,True,3,100000,500,10,True,True,True,True,1.984333821994369,3.1.2 +NaiveCVMatrix,True,3,100000,500,10,True,True,True,True,2.391529404994799,3.1.2 +CVMatrix,True,5,100000,500,10,True,True,True,True,2.0060565159947146,3.1.2 +NaiveCVMatrix,True,5,100000,500,10,True,True,True,True,4.756514220003737,3.1.2 +CVMatrix,True,10,100000,500,10,True,True,True,True,2.0229949439963093,3.1.2 +NaiveCVMatrix,True,10,100000,500,10,True,True,True,True,10.702684000993031,3.1.2 +CVMatrix,True,100,100000,500,10,True,True,True,True,1.9863586209976347,3.1.2 +NaiveCVMatrix,True,100,100000,500,10,True,True,True,True,116.75834354999824,3.1.2 +CVMatrix,True,1000,100000,500,10,True,True,True,True,2.696976027000346,3.1.2 +NaiveCVMatrix,True,1000,100000,500,10,True,True,True,True,1167.3831180050038,3.1.2 +CVMatrix,True,10000,100000,500,10,True,True,True,True,10.405765339004574,3.1.2 +NaiveCVMatrix,True,10000,100000,500,10,True,True,True,True,11684.97914216001,3.1.2 +CVMatrix,True,100000,100000,500,10,True,True,True,True,97.79410633000953,3.1.2 +NaiveCVMatrix,True,100000,100000,500,10,True,True,True,True,117450.870239473,3.1.2 +CVMatrix,True,3,100000,500,10,True,True,True,False,1.9866961159859784,3.1.2 +CVMatrix,True,5,100000,500,10,True,True,True,False,1.9947043650026899,3.1.2 +CVMatrix,True,10,100000,500,10,True,True,True,False,2.0186615100246854,3.1.2 +CVMatrix,True,100,100000,500,10,True,True,True,False,1.9616220210155006,3.1.2 +CVMatrix,True,1000,100000,500,10,True,True,True,False,2.6776564110186882,3.1.2 +CVMatrix,True,10000,100000,500,10,True,True,True,False,10.229800548026105,3.1.2 +CVMatrix,True,100000,100000,500,10,True,True,True,False,96.38440647299285,3.1.2 +CVMatrix,True,3,100000,500,10,True,True,False,True,1.8646600369829684,3.1.2 +CVMatrix,True,5,100000,500,10,True,True,False,True,1.8728829300089274,3.1.2 +CVMatrix,True,10,100000,500,10,True,True,False,True,1.8833721109840553,3.1.2 +CVMatrix,True,100,100000,500,10,True,True,False,True,1.8414228140027262,3.1.2 +CVMatrix,True,1000,100000,500,10,True,True,False,True,2.2803589590184856,3.1.2 +CVMatrix,True,10000,100000,500,10,True,True,False,True,7.142492076003691,3.1.2 +CVMatrix,True,100000,100000,500,10,True,True,False,True,65.46094575102325,3.1.2 +CVMatrix,True,3,100000,500,10,True,True,False,False,1.8603149139962625,3.1.2 +NaiveCVMatrix,True,3,100000,500,10,True,True,False,False,2.0659156549954787,3.1.2 +CVMatrix,True,5,100000,500,10,True,True,False,False,1.8669229579973035,3.1.2 +NaiveCVMatrix,True,5,100000,500,10,True,True,False,False,4.117473580001388,3.1.2 +CVMatrix,True,10,100000,500,10,True,True,False,False,1.8735272120102309,3.1.2 +NaiveCVMatrix,True,10,100000,500,10,True,True,False,False,9.192210117995273,3.1.2 +CVMatrix,True,100,100000,500,10,True,True,False,False,1.8366783630044665,3.1.2 +NaiveCVMatrix,True,100,100000,500,10,True,True,False,False,100.20058388100006,3.1.2 +CVMatrix,True,1000,100000,500,10,True,True,False,False,2.2641661269881297,3.1.2 +NaiveCVMatrix,True,1000,100000,500,10,True,True,False,False,1013.2832974810153,3.1.2 +CVMatrix,True,10000,100000,500,10,True,True,False,False,6.955503848002991,3.1.2 +NaiveCVMatrix,True,10000,100000,500,10,True,True,False,False,10075.192611823994,3.1.2 +CVMatrix,True,100000,100000,500,10,True,True,False,False,63.794935021025594,3.1.2 +NaiveCVMatrix,True,100000,100000,500,10,True,True,False,False,101962.998182905,3.1.2 +CVMatrix,True,3,100000,500,10,True,False,True,True,1.9902973789721727,3.1.2 +CVMatrix,True,5,100000,500,10,True,False,True,True,2.0029557759989984,3.1.2 +CVMatrix,True,10,100000,500,10,True,False,True,True,2.0234543079859577,3.1.2 +CVMatrix,True,100,100000,500,10,True,False,True,True,1.9662584849866107,3.1.2 +CVMatrix,True,1000,100000,500,10,True,False,True,True,2.7020226089516655,3.1.2 +CVMatrix,True,10000,100000,500,10,True,False,True,True,10.588881150993984,3.1.2 +CVMatrix,True,100000,100000,500,10,True,False,True,True,97.88805527199293,3.1.2 +CVMatrix,True,3,100000,500,10,True,False,True,False,1.9904241299955174,3.1.2 +CVMatrix,True,5,100000,500,10,True,False,True,False,1.9963804120197892,3.1.2 +CVMatrix,True,10,100000,500,10,True,False,True,False,2.0168173640267923,3.1.2 +CVMatrix,True,100,100000,500,10,True,False,True,False,1.9618249780032784,3.1.2 +CVMatrix,True,1000,100000,500,10,True,False,True,False,2.6584641320514493,3.1.2 +CVMatrix,True,10000,100000,500,10,True,False,True,False,10.064942481054459,3.1.2 +CVMatrix,True,100000,100000,500,10,True,False,True,False,96.15522754698759,3.1.2 +CVMatrix,True,3,100000,500,10,True,False,False,True,1.893277476017829,3.1.2 +CVMatrix,True,5,100000,500,10,True,False,False,True,1.9007876650430262,3.1.2 +CVMatrix,True,10,100000,500,10,True,False,False,True,1.9073550690081902,3.1.2 +CVMatrix,True,100,100000,500,10,True,False,False,True,1.8717375870328397,3.1.2 +CVMatrix,True,1000,100000,500,10,True,False,False,True,2.3017041080165654,3.1.2 +CVMatrix,True,10000,100000,500,10,True,False,False,True,7.046903911978006,3.1.2 +CVMatrix,True,100000,100000,500,10,True,False,False,True,65.85458980698604,3.1.2 +CVMatrix,True,3,100000,500,10,True,False,False,False,1.8796638959902339,3.1.2 +CVMatrix,True,5,100000,500,10,True,False,False,False,1.888564449036494,3.1.2 +CVMatrix,True,10,100000,500,10,True,False,False,False,1.8975845539825968,3.1.2 +CVMatrix,True,100,100000,500,10,True,False,False,False,1.8584078709827736,3.1.2 +CVMatrix,True,1000,100000,500,10,True,False,False,False,2.2896918160258792,3.1.2 +CVMatrix,True,10000,100000,500,10,True,False,False,False,6.9467962449998595,3.1.2 +CVMatrix,True,100000,100000,500,10,True,False,False,False,64.19237864797469,3.1.2 +CVMatrix,True,3,100000,500,10,False,True,True,True,2.0172123850206845,3.1.2 +CVMatrix,True,5,100000,500,10,False,True,True,True,2.0274247500346974,3.1.2 +CVMatrix,True,10,100000,500,10,False,True,True,True,2.043387685029302,3.1.2 +CVMatrix,True,100,100000,500,10,False,True,True,True,1.9616288489778526,3.1.2 +CVMatrix,True,1000,100000,500,10,False,True,True,True,2.4196650039521046,3.1.2 +CVMatrix,True,10000,100000,500,10,False,True,True,True,7.325938726018649,3.1.2 +CVMatrix,True,100000,100000,500,10,False,True,True,True,67.4904927710304,3.1.2 +CVMatrix,True,3,100000,500,10,False,True,True,False,2.0081770819961093,3.1.2 +CVMatrix,True,5,100000,500,10,False,True,True,False,2.017824358015787,3.1.2 +CVMatrix,True,10,100000,500,10,False,True,True,False,2.038971511996351,3.1.2 +CVMatrix,True,100,100000,500,10,False,True,True,False,1.9466590040246956,3.1.2 +CVMatrix,True,1000,100000,500,10,False,True,True,False,2.388793147983961,3.1.2 +CVMatrix,True,10000,100000,500,10,False,True,True,False,7.244912572961766,3.1.2 +CVMatrix,True,100000,100000,500,10,False,True,True,False,65.45056431303965,3.1.2 +CVMatrix,True,3,100000,500,10,False,True,False,True,1.8663307260139845,3.1.2 +CVMatrix,True,5,100000,500,10,False,True,False,True,1.8724938009981997,3.1.2 +CVMatrix,True,10,100000,500,10,False,True,False,True,1.8761495150392875,3.1.2 +CVMatrix,True,100,100000,500,10,False,True,False,True,1.8221882159705274,3.1.2 +CVMatrix,True,1000,100000,500,10,False,True,False,True,1.9893430410302244,3.1.2 +CVMatrix,True,10000,100000,500,10,False,True,False,True,4.0838878019712865,3.1.2 +CVMatrix,True,100000,100000,500,10,False,True,False,True,34.84725921304198,3.1.2 +CVMatrix,True,3,100000,500,10,False,True,False,False,1.8596344610559754,3.1.2 +CVMatrix,True,5,100000,500,10,False,True,False,False,1.8675400780048221,3.1.2 +CVMatrix,True,10,100000,500,10,False,True,False,False,1.86872684804257,3.1.2 +CVMatrix,True,100,100000,500,10,False,True,False,False,1.7929440249572508,3.1.2 +CVMatrix,True,1000,100000,500,10,False,True,False,False,1.9514500600052997,3.1.2 +CVMatrix,True,10000,100000,500,10,False,True,False,False,3.9068712599691935,3.1.2 +CVMatrix,True,100000,100000,500,10,False,True,False,False,33.09635844203876,3.1.2 +CVMatrix,True,3,100000,500,10,False,False,True,True,2.0005321159842424,3.1.2 +CVMatrix,True,5,100000,500,10,False,False,True,True,2.004382062004879,3.1.2 +CVMatrix,True,10,100000,500,10,False,False,True,True,2.019039334030822,3.1.2 +CVMatrix,True,100,100000,500,10,False,False,True,True,1.9341456539696082,3.1.2 +CVMatrix,True,1000,100000,500,10,False,False,True,True,2.3803899489575997,3.1.2 +CVMatrix,True,10000,100000,500,10,False,False,True,True,7.2361443800036795,3.1.2 +CVMatrix,True,100000,100000,500,10,False,False,True,True,65.62882983498275,3.1.2 +CVMatrix,True,3,100000,500,10,False,False,True,False,1.9825511670205742,3.1.2 +CVMatrix,True,5,100000,500,10,False,False,True,False,1.9831450549536385,3.1.2 +CVMatrix,True,10,100000,500,10,False,False,True,False,2.0022322680451907,3.1.2 +CVMatrix,True,100,100000,500,10,False,False,True,False,1.9140952400048263,3.1.2 +CVMatrix,True,1000,100000,500,10,False,False,True,False,2.3420013989671133,3.1.2 +CVMatrix,True,10000,100000,500,10,False,False,True,False,7.000652107002679,3.1.2 +CVMatrix,True,100000,100000,500,10,False,False,True,False,63.63822188001359,3.1.2 +CVMatrix,True,3,100000,500,10,False,False,False,True,1.8365034110029228,3.1.2 +CVMatrix,True,5,100000,500,10,False,False,False,True,1.8420435070293024,3.1.2 +CVMatrix,True,10,100000,500,10,False,False,False,True,1.8439627279876731,3.1.2 +CVMatrix,True,100,100000,500,10,False,False,False,True,1.778978560003452,3.1.2 +CVMatrix,True,1000,100000,500,10,False,False,False,True,1.9684552230173722,3.1.2 +CVMatrix,True,10000,100000,500,10,False,False,False,True,3.770185924018733,3.1.2 +CVMatrix,True,100000,100000,500,10,False,False,False,True,33.78727348102257,3.1.2 +CVMatrix,True,3,100000,500,10,False,False,False,False,1.8392871209653094,3.1.2 +NaiveCVMatrix,True,3,100000,500,10,False,False,False,False,1.8614093359792605,3.1.2 +CVMatrix,True,5,100000,500,10,False,False,False,False,1.8229632530128583,3.1.2 +NaiveCVMatrix,True,5,100000,500,10,False,False,False,False,3.6891562480013818,3.1.2 +CVMatrix,True,10,100000,500,10,False,False,False,False,1.8274640979943797,3.1.2 +NaiveCVMatrix,True,10,100000,500,10,False,False,False,False,8.244838489044923,3.1.2 +CVMatrix,True,100,100000,500,10,False,False,False,False,1.758714469964616,3.1.2 +NaiveCVMatrix,True,100,100000,500,10,False,False,False,False,89.59703296300722,3.1.2 +CVMatrix,True,1000,100000,500,10,False,False,False,False,1.9054143269895576,3.1.2 +NaiveCVMatrix,True,1000,100000,500,10,False,False,False,False,901.6295390359592,3.1.2 +CVMatrix,True,10000,100000,500,10,False,False,False,False,3.409175298991613,3.1.2 +NaiveCVMatrix,True,10000,100000,500,10,False,False,False,False,9001.239257315989,3.1.2 +CVMatrix,True,100000,100000,500,10,False,False,False,False,31.26620070001809,3.1.2 +NaiveCVMatrix,True,100000,100000,500,10,False,False,False,False,91358.00990715699,3.1.2 +CVMatrix,False,3,100000,500,10,True,True,True,True,2.034291260992177,3.1.2 +NaiveCVMatrix,False,3,100000,500,10,True,True,True,True,2.3922783290036023,3.1.2 +CVMatrix,False,5,100000,500,10,True,True,True,True,2.0007808249793015,3.1.2 +NaiveCVMatrix,False,5,100000,500,10,True,True,True,True,4.754343311011326,3.1.2 +CVMatrix,False,10,100000,500,10,True,True,True,True,2.023401284997817,3.1.2 +NaiveCVMatrix,False,10,100000,500,10,True,True,True,True,10.633911692013498,3.1.2 +CVMatrix,False,100,100000,500,10,True,True,True,True,1.9663792649516836,3.1.2 +NaiveCVMatrix,False,100,100000,500,10,True,True,True,True,116.16608670901041,3.1.2 +CVMatrix,False,1000,100000,500,10,True,True,True,True,2.6782585170003586,3.1.2 +NaiveCVMatrix,False,1000,100000,500,10,True,True,True,True,1166.5175434830016,3.1.2 +CVMatrix,False,10000,100000,500,10,True,True,True,True,10.234425690025091,3.1.2 +NaiveCVMatrix,False,10000,100000,500,10,True,True,True,True,11639.73957652098,3.1.2 +CVMatrix,False,100000,100000,500,10,True,True,True,True,97.09211129194591,3.1.2 +NaiveCVMatrix,False,100000,100000,500,10,True,True,True,True,117549.17203807004,3.1.2 +CVMatrix,False,3,100000,500,10,True,True,True,False,1.989648396964185,3.1.2 +CVMatrix,False,5,100000,500,10,True,True,True,False,1.9888193310471252,3.1.2 +CVMatrix,False,10,100000,500,10,True,True,True,False,2.0101286600111052,3.1.2 +CVMatrix,False,100,100000,500,10,True,True,True,False,1.955858564004302,3.1.2 +CVMatrix,False,1000,100000,500,10,True,True,True,False,2.6559186059748754,3.1.2 +CVMatrix,False,10000,100000,500,10,True,True,True,False,10.178072172100656,3.1.2 +CVMatrix,False,100000,100000,500,10,True,True,True,False,95.63062967301812,3.1.2 +CVMatrix,False,3,100000,500,10,True,True,False,True,1.8668326110346243,3.1.2 +CVMatrix,False,5,100000,500,10,True,True,False,True,1.874564933939837,3.1.2 +CVMatrix,False,10,100000,500,10,True,True,False,True,1.8787338759284467,3.1.2 +CVMatrix,False,100,100000,500,10,True,True,False,True,1.8407839860301465,3.1.2 +CVMatrix,False,1000,100000,500,10,True,True,False,True,2.278446708107367,3.1.2 +CVMatrix,False,10000,100000,500,10,True,True,False,True,7.0909933460643515,3.1.2 +CVMatrix,False,100000,100000,500,10,True,True,False,True,65.29609195003286,3.1.2 +CVMatrix,False,3,100000,500,10,True,True,False,False,1.8607327690115198,3.1.2 +NaiveCVMatrix,False,3,100000,500,10,True,True,False,False,2.0708270779578015,3.1.2 +CVMatrix,False,5,100000,500,10,True,True,False,False,1.865100613096729,3.1.2 +NaiveCVMatrix,False,5,100000,500,10,True,True,False,False,4.10796340694651,3.1.2 +CVMatrix,False,10,100000,500,10,True,True,False,False,1.875420168042183,3.1.2 +NaiveCVMatrix,False,10,100000,500,10,True,True,False,False,9.184276239015162,3.1.2 +CVMatrix,False,100,100000,500,10,True,True,False,False,1.8323370669968426,3.1.2 +NaiveCVMatrix,False,100,100000,500,10,True,True,False,False,100.16243708797265,3.1.2 +CVMatrix,False,1000,100000,500,10,True,True,False,False,2.2622019361006096,3.1.2 +NaiveCVMatrix,False,1000,100000,500,10,True,True,False,False,1008.8384319560137,3.1.2 +CVMatrix,False,10000,100000,500,10,True,True,False,False,6.879046739079058,3.1.2 +NaiveCVMatrix,False,10000,100000,500,10,True,True,False,False,10082.962717239046,3.1.2 +CVMatrix,False,100000,100000,500,10,True,True,False,False,63.4832807140192,3.1.2 +NaiveCVMatrix,False,100000,100000,500,10,True,True,False,False,101787.87960566801,3.1.2 +CVMatrix,False,3,100000,500,10,True,False,True,True,1.9959409980801865,3.1.2 +CVMatrix,False,5,100000,500,10,True,False,True,True,2.005585574079305,3.1.2 +CVMatrix,False,10,100000,500,10,True,False,True,True,2.025371566065587,3.1.2 +CVMatrix,False,100,100000,500,10,True,False,True,True,1.965202496969141,3.1.2 +CVMatrix,False,1000,100000,500,10,True,False,True,True,2.6782691329717636,3.1.2 +CVMatrix,False,10000,100000,500,10,True,False,True,True,10.276601496967487,3.1.2 +CVMatrix,False,100000,100000,500,10,True,False,True,True,97.20974447694607,3.1.2 +CVMatrix,False,3,100000,500,10,True,False,True,False,1.9936210400192067,3.1.2 +CVMatrix,False,5,100000,500,10,True,False,True,False,2.0063806009711698,3.1.2 +CVMatrix,False,10,100000,500,10,True,False,True,False,2.0131727680563927,3.1.2 +CVMatrix,False,100,100000,500,10,True,False,True,False,1.9558848090237007,3.1.2 +CVMatrix,False,1000,100000,500,10,True,False,True,False,2.66691228793934,3.1.2 +CVMatrix,False,10000,100000,500,10,True,False,True,False,10.278925959952176,3.1.2 +CVMatrix,False,100000,100000,500,10,True,False,True,False,95.35915566701442,3.1.2 +CVMatrix,False,3,100000,500,10,True,False,False,True,1.8701620240462944,3.1.2 +CVMatrix,False,5,100000,500,10,True,False,False,True,1.8752742019714788,3.1.2 +CVMatrix,False,10,100000,500,10,True,False,False,True,1.8875069540226832,3.1.2 +CVMatrix,False,100,100000,500,10,True,False,False,True,1.8479511609766632,3.1.2 +CVMatrix,False,1000,100000,500,10,True,False,False,True,2.2901278500212356,3.1.2 +CVMatrix,False,10000,100000,500,10,True,False,False,True,7.146467910963111,3.1.2 +CVMatrix,False,100000,100000,500,10,True,False,False,True,65.34969825902954,3.1.2 +CVMatrix,False,3,100000,500,10,True,False,False,False,1.8654925209702924,3.1.2 +CVMatrix,False,5,100000,500,10,True,False,False,False,1.8686304850270972,3.1.2 +CVMatrix,False,10,100000,500,10,True,False,False,False,1.8761505350703374,3.1.2 +CVMatrix,False,100,100000,500,10,True,False,False,False,1.8384834351018071,3.1.2 +CVMatrix,False,1000,100000,500,10,True,False,False,False,2.263621968915686,3.1.2 +CVMatrix,False,10000,100000,500,10,True,False,False,False,6.93361745798029,3.1.2 +CVMatrix,False,100000,100000,500,10,True,False,False,False,63.50186239590403,3.1.2 +CVMatrix,False,3,100000,500,10,False,True,True,True,2.0191891490248963,3.1.2 +CVMatrix,False,5,100000,500,10,False,True,True,True,2.029355225036852,3.1.2 +CVMatrix,False,10,100000,500,10,False,True,True,True,2.0495906240539625,3.1.2 +CVMatrix,False,100,100000,500,10,False,True,True,True,1.952279514982365,3.1.2 +CVMatrix,False,1000,100000,500,10,False,True,True,True,2.402014493010938,3.1.2 +CVMatrix,False,10000,100000,500,10,False,True,True,True,7.273113304981962,3.1.2 +CVMatrix,False,100000,100000,500,10,False,True,True,True,66.23194423306268,3.1.2 +CVMatrix,False,3,100000,500,10,False,True,True,False,1.9889022349379957,3.1.2 +CVMatrix,False,5,100000,500,10,False,True,True,False,1.9955584389390424,3.1.2 +CVMatrix,False,10,100000,500,10,False,True,True,False,2.009456299012527,3.1.2 +CVMatrix,False,100,100000,500,10,False,True,True,False,1.9206904279999435,3.1.2 +CVMatrix,False,1000,100000,500,10,False,True,True,False,2.3591902510961518,3.1.2 +CVMatrix,False,10000,100000,500,10,False,True,True,False,7.152248831000179,3.1.2 +CVMatrix,False,100000,100000,500,10,False,True,True,False,64.83727667492349,3.1.2 +CVMatrix,False,3,100000,500,10,False,True,False,True,1.8642071209615096,3.1.2 +CVMatrix,False,5,100000,500,10,False,True,False,True,1.8696786060463637,3.1.2 +CVMatrix,False,10,100000,500,10,False,True,False,True,1.8767199279973283,3.1.2 +CVMatrix,False,100,100000,500,10,False,True,False,True,1.8024164760718122,3.1.2 +CVMatrix,False,1000,100000,500,10,False,True,False,True,1.9886140170274302,3.1.2 +CVMatrix,False,10000,100000,500,10,False,True,False,True,4.061778323026374,3.1.2 +CVMatrix,False,100000,100000,500,10,False,True,False,True,34.55365612602327,3.1.2 +CVMatrix,False,3,100000,500,10,False,True,False,False,1.861834149924107,3.1.2 +CVMatrix,False,5,100000,500,10,False,True,False,False,1.8671569019788876,3.1.2 +CVMatrix,False,10,100000,500,10,False,True,False,False,1.870482338941656,3.1.2 +CVMatrix,False,100,100000,500,10,False,True,False,False,1.792074027005583,3.1.2 +CVMatrix,False,1000,100000,500,10,False,True,False,False,1.96328009897843,3.1.2 +CVMatrix,False,10000,100000,500,10,False,True,False,False,3.902390431961976,3.1.2 +CVMatrix,False,100000,100000,500,10,False,True,False,False,32.91603452595882,3.1.2 +CVMatrix,False,3,100000,500,10,False,False,True,True,1.996446231030859,3.1.2 +CVMatrix,False,5,100000,500,10,False,False,True,True,2.0023212619125843,3.1.2 +CVMatrix,False,10,100000,500,10,False,False,True,True,2.022794905002229,3.1.2 +CVMatrix,False,100,100000,500,10,False,False,True,True,1.9379006010713056,3.1.2 +CVMatrix,False,1000,100000,500,10,False,False,True,True,2.375303284963593,3.1.2 +CVMatrix,False,10000,100000,500,10,False,False,True,True,7.249086086987518,3.1.2 +CVMatrix,False,100000,100000,500,10,False,False,True,True,65.29011618508957,3.1.2 +CVMatrix,False,3,100000,500,10,False,False,True,False,1.9788222559727728,3.1.2 +CVMatrix,False,5,100000,500,10,False,False,True,False,1.9821522230049595,3.1.2 +CVMatrix,False,10,100000,500,10,False,False,True,False,2.000355315976776,3.1.2 +CVMatrix,False,100,100000,500,10,False,False,True,False,1.9086465439759195,3.1.2 +CVMatrix,False,1000,100000,500,10,False,False,True,False,2.3407338060205802,3.1.2 +CVMatrix,False,10000,100000,500,10,False,False,True,False,6.991061829030514,3.1.2 +CVMatrix,False,100000,100000,500,10,False,False,True,False,63.54187911900226,3.1.2 +CVMatrix,False,3,100000,500,10,False,False,False,True,1.831602277001366,3.1.2 +CVMatrix,False,5,100000,500,10,False,False,False,True,1.8437130060046911,3.1.2 +CVMatrix,False,10,100000,500,10,False,False,False,True,1.8450647690333426,3.1.2 +CVMatrix,False,100,100000,500,10,False,False,False,True,1.7816835039993748,3.1.2 +CVMatrix,False,1000,100000,500,10,False,False,False,True,1.9584064349764958,3.1.2 +CVMatrix,False,10000,100000,500,10,False,False,False,True,3.921983759966679,3.1.2 +CVMatrix,False,100000,100000,500,10,False,False,False,True,33.24656160501763,3.1.2 +CVMatrix,False,3,100000,500,10,False,False,False,False,1.8175993909826502,3.1.2 +NaiveCVMatrix,False,3,100000,500,10,False,False,False,False,1.8584301760420203,3.1.2 +CVMatrix,False,5,100000,500,10,False,False,False,False,1.8229373840149492,3.1.2 +NaiveCVMatrix,False,5,100000,500,10,False,False,False,False,3.6857824749313295,3.1.2 +CVMatrix,False,10,100000,500,10,False,False,False,False,1.8272661580704153,3.1.2 +NaiveCVMatrix,False,10,100000,500,10,False,False,False,False,8.242961511947215,3.1.2 +CVMatrix,False,100,100000,500,10,False,False,False,False,1.7539957320550457,3.1.2 +NaiveCVMatrix,False,100,100000,500,10,False,False,False,False,89.79859544499777,3.1.2 +CVMatrix,False,1000,100000,500,10,False,False,False,False,1.9064412079751492,3.1.2 +NaiveCVMatrix,False,1000,100000,500,10,False,False,False,False,906.4757035330404,3.1.2 +CVMatrix,False,10000,100000,500,10,False,False,False,False,3.561416274984367,3.1.2 +NaiveCVMatrix,False,10000,100000,500,10,False,False,False,False,9033.89608011092,3.1.2 +CVMatrix,False,100000,100000,500,10,False,False,False,False,30.454796339967288,3.1.2 +NaiveCVMatrix,False,100000,100000,500,10,False,False,False,False,91219.7644988551,3.1.2 \ No newline at end of file diff --git a/benchmarks/benchmark_results_jax.csv b/benchmarks/benchmark_results_jax.csv new file mode 100644 index 0000000..4ca2bb8 --- /dev/null +++ b/benchmarks/benchmark_results_jax.csv @@ -0,0 +1,169 @@ +model,weights,P,N,K,M,center_X,center_Y,scale_X,scale_Y,time,version +CVMatrix,True,3,100000,500,10,False,False,False,False,1.1941567149478942,3.2.0 +CVMatrix,True,5,100000,500,10,False,False,False,False,1.2388604329898953,3.2.0 +CVMatrix,True,10,100000,500,10,False,False,False,False,1.2152469219872728,3.2.0 +CVMatrix,True,100,100000,500,10,False,False,False,False,0.8777078640414402,3.2.0 +CVMatrix,True,1000,100000,500,10,False,False,False,False,1.2294215579750016,3.2.0 +CVMatrix,True,10000,100000,500,10,False,False,False,False,1.9392532469937578,3.2.0 +CVMatrix,True,100000,100000,500,10,False,False,False,False,34.31822472799104,3.2.0 +CVMatrix,True,3,100000,500,10,True,True,False,False,1.3004615990212187,3.2.0 +CVMatrix,True,5,100000,500,10,True,True,False,False,1.2773295060032979,3.2.0 +CVMatrix,True,10,100000,500,10,True,True,False,False,1.3540909610455856,3.2.0 +CVMatrix,True,100,100000,500,10,True,True,False,False,0.9889725480461493,3.2.0 +CVMatrix,True,1000,100000,500,10,True,True,False,False,1.9933221020037308,3.2.0 +CVMatrix,True,10000,100000,500,10,True,True,False,False,6.104556004982442,3.2.0 +CVMatrix,True,100000,100000,500,10,True,True,False,False,69.0630395989865,3.2.0 +CVMatrix,True,3,100000,500,10,True,True,True,True,1.6016558419214562,3.2.0 +CVMatrix,True,5,100000,500,10,True,True,True,True,1.952684861025773,3.2.0 +CVMatrix,True,10,100000,500,10,True,True,True,True,1.9289571170229465,3.2.0 +CVMatrix,True,100,100000,500,10,True,True,True,True,1.3856251860270277,3.2.0 +CVMatrix,True,1000,100000,500,10,True,True,True,True,2.928624163963832,3.2.0 +CVMatrix,True,10000,100000,500,10,True,True,True,True,10.213626472046599,3.2.0 +CVMatrix,True,100000,100000,500,10,True,True,True,True,108.04169350804295,3.2.0 +CVMatrix,False,3,100000,500,10,False,False,False,False,0.8644924859981984,3.2.0 +CVMatrix,False,5,100000,500,10,False,False,False,False,0.8797790000680834,3.2.0 +CVMatrix,False,10,100000,500,10,False,False,False,False,0.7050728399772197,3.2.0 +CVMatrix,False,100,100000,500,10,False,False,False,False,0.514429033966735,3.2.0 +CVMatrix,False,1000,100000,500,10,False,False,False,False,0.8157944809645414,3.2.0 +CVMatrix,False,10000,100000,500,10,False,False,False,False,3.271460584946908,3.2.0 +CVMatrix,False,100000,100000,500,10,False,False,False,False,34.160362099064514,3.2.0 +CVMatrix,False,3,100000,500,10,True,True,False,False,0.7326359620783478,3.2.0 +CVMatrix,False,5,100000,500,10,True,True,False,False,0.7950215539894998,3.2.0 +CVMatrix,False,10,100000,500,10,True,True,False,False,0.7952526080189273,3.2.0 +CVMatrix,False,100,100000,500,10,True,True,False,False,0.6158564779907465,3.2.0 +CVMatrix,False,1000,100000,500,10,True,True,False,False,1.4742512800730765,3.2.0 +CVMatrix,False,10000,100000,500,10,True,True,False,False,10.907702341093682,3.2.0 +CVMatrix,False,100000,100000,500,10,True,True,False,False,69.9015849790303,3.2.0 +CVMatrix,False,3,100000,500,10,True,True,True,True,1.1621856829151511,3.2.0 +CVMatrix,False,5,100000,500,10,True,True,True,True,1.1924858170095831,3.2.0 +CVMatrix,False,10,100000,500,10,True,True,True,True,1.209067014977336,3.2.0 +CVMatrix,False,100,100000,500,10,True,True,True,True,0.8500632049981505,3.2.0 +CVMatrix,False,1000,100000,500,10,True,True,True,True,1.8347180710406974,3.2.0 +CVMatrix,False,10000,100000,500,10,True,True,True,True,14.872147740912624,3.2.0 +CVMatrix,False,100000,100000,500,10,True,True,True,True,105.06958515406586,3.2.0 +CVMatrix-jax-gpu-nojit,True,3,100000,500,10,False,False,False,False,1.632313295849599,3.2.0 +CVMatrix-jax-gpu-coldjit,True,3,100000,500,10,False,False,False,False,23.34611799486447,3.2.0 +CVMatrix-jax-gpu-warmjit,True,3,100000,500,10,False,False,False,False,0.9329864250030369,3.2.0 +CVMatrix-jax-gpu-nojit,True,5,100000,500,10,False,False,False,False,0.5289996319916099,3.2.0 +CVMatrix-jax-gpu-coldjit,True,5,100000,500,10,False,False,False,False,11.792680245125666,3.2.0 +CVMatrix-jax-gpu-warmjit,True,5,100000,500,10,False,False,False,False,0.15165713499300182,3.2.0 +CVMatrix-jax-gpu-nojit,True,10,100000,500,10,False,False,False,False,0.5324810020392761,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10,100000,500,10,False,False,False,False,11.824156131013297,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10,100000,500,10,False,False,False,False,0.15182622498832643,3.2.0 +CVMatrix-jax-gpu-nojit,True,100,100000,500,10,False,False,False,False,0.5337930739624426,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100,100000,500,10,False,False,False,False,11.681885494850576,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100,100000,500,10,False,False,False,False,0.14219606888946146,3.2.0 +CVMatrix-jax-gpu-nojit,True,1000,100000,500,10,False,False,False,False,0.539993820944801,3.2.0 +CVMatrix-jax-gpu-coldjit,True,1000,100000,500,10,False,False,False,False,11.779184759943746,3.2.0 +CVMatrix-jax-gpu-warmjit,True,1000,100000,500,10,False,False,False,False,0.16031630500219762,3.2.0 +CVMatrix-jax-gpu-nojit,True,10000,100000,500,10,False,False,False,False,0.7048436108743772,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10000,100000,500,10,False,False,False,False,11.936229861923493,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10000,100000,500,10,False,False,False,False,0.27667936484795064,3.2.0 +CVMatrix-jax-gpu-nojit,True,100000,100000,500,10,False,False,False,False,1.5053048130357638,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100000,100000,500,10,False,False,False,False,11.766612329054624,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100000,100000,500,10,False,False,False,False,0.37316381302662194,3.2.0 +CVMatrix-jax-gpu-nojit,True,3,100000,500,10,True,True,False,False,2.366172896930948,3.2.0 +CVMatrix-jax-gpu-coldjit,True,3,100000,500,10,True,True,False,False,23.621587069006637,3.2.0 +CVMatrix-jax-gpu-warmjit,True,3,100000,500,10,True,True,False,False,0.7769030149793252,3.2.0 +CVMatrix-jax-gpu-nojit,True,5,100000,500,10,True,True,False,False,1.1171795859700069,3.2.0 +CVMatrix-jax-gpu-coldjit,True,5,100000,500,10,True,True,False,False,12.005785148008727,3.2.0 +CVMatrix-jax-gpu-warmjit,True,5,100000,500,10,True,True,False,False,0.14345634903293103,3.2.0 +CVMatrix-jax-gpu-nojit,True,10,100000,500,10,True,True,False,False,1.0804275509435683,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10,100000,500,10,True,True,False,False,11.986124473973177,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10,100000,500,10,True,True,False,False,0.14493258495349437,3.2.0 +CVMatrix-jax-gpu-nojit,True,100,100000,500,10,True,True,False,False,0.93102929496672,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100,100000,500,10,True,True,False,False,11.866976546007209,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100,100000,500,10,True,True,False,False,0.14944440498948097,3.2.0 +CVMatrix-jax-gpu-nojit,True,1000,100000,500,10,True,True,False,False,0.9813496950082481,3.2.0 +CVMatrix-jax-gpu-coldjit,True,1000,100000,500,10,True,True,False,False,11.930314689991064,3.2.0 +CVMatrix-jax-gpu-warmjit,True,1000,100000,500,10,True,True,False,False,0.16065363702364266,3.2.0 +CVMatrix-jax-gpu-nojit,True,10000,100000,500,10,True,True,False,False,1.1416200449457392,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10000,100000,500,10,True,True,False,False,12.290474948938936,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10000,100000,500,10,True,True,False,False,0.282087006024085,3.2.0 +CVMatrix-jax-gpu-nojit,True,100000,100000,500,10,True,True,False,False,2.967109062941745,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100000,100000,500,10,True,True,False,False,12.140538009931333,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100000,100000,500,10,True,True,False,False,0.5534275260288268,3.2.0 +CVMatrix-jax-gpu-nojit,True,3,100000,500,10,True,True,True,True,1.4237911779200658,3.2.0 +CVMatrix-jax-gpu-coldjit,True,3,100000,500,10,True,True,True,True,34.12143419694621,3.2.0 +CVMatrix-jax-gpu-warmjit,True,3,100000,500,10,True,True,True,True,0.3091441129799932,3.2.0 +CVMatrix-jax-gpu-nojit,True,5,100000,500,10,True,True,True,True,0.8284593160497025,3.2.0 +CVMatrix-jax-gpu-coldjit,True,5,100000,500,10,True,True,True,True,17.56956055003684,3.2.0 +CVMatrix-jax-gpu-warmjit,True,5,100000,500,10,True,True,True,True,0.14575090806465596,3.2.0 +CVMatrix-jax-gpu-nojit,True,10,100000,500,10,True,True,True,True,0.8230793218826875,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10,100000,500,10,True,True,True,True,17.819756863988005,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10,100000,500,10,True,True,True,True,0.1455215640598908,3.2.0 +CVMatrix-jax-gpu-nojit,True,100,100000,500,10,True,True,True,True,0.8331331339431927,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100,100000,500,10,True,True,True,True,17.688981912913732,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100,100000,500,10,True,True,True,True,0.14719849301036447,3.2.0 +CVMatrix-jax-gpu-nojit,True,1000,100000,500,10,True,True,True,True,0.8850459699751809,3.2.0 +CVMatrix-jax-gpu-coldjit,True,1000,100000,500,10,True,True,True,True,17.638945414917544,3.2.0 +CVMatrix-jax-gpu-warmjit,True,1000,100000,500,10,True,True,True,True,0.16532829497009516,3.2.0 +CVMatrix-jax-gpu-nojit,True,10000,100000,500,10,True,True,True,True,1.3153250009054318,3.2.0 +CVMatrix-jax-gpu-coldjit,True,10000,100000,500,10,True,True,True,True,18.117243187036365,3.2.0 +CVMatrix-jax-gpu-warmjit,True,10000,100000,500,10,True,True,True,True,0.3333543869666755,3.2.0 +CVMatrix-jax-gpu-nojit,True,100000,100000,500,10,True,True,True,True,4.518371606129222,3.2.0 +CVMatrix-jax-gpu-coldjit,True,100000,100000,500,10,True,True,True,True,18.720431115012616,3.2.0 +CVMatrix-jax-gpu-warmjit,True,100000,100000,500,10,True,True,True,True,1.2993427510373294,3.2.0 +CVMatrix-jax-cpu-nojit,True,3,100000,500,10,False,False,False,False,2.0641746720066294,3.2.0 +CVMatrix-jax-cpu-coldjit,True,3,100000,500,10,False,False,False,False,11.801093348069116,3.2.0 +CVMatrix-jax-cpu-warmjit,True,3,100000,500,10,False,False,False,False,0.8850470650941133,3.2.0 +CVMatrix-jax-cpu-nojit,True,5,100000,500,10,False,False,False,False,1.5895351129584014,3.2.0 +CVMatrix-jax-cpu-coldjit,True,5,100000,500,10,False,False,False,False,6.010262299911119,3.2.0 +CVMatrix-jax-cpu-warmjit,True,5,100000,500,10,False,False,False,False,0.6328710300149396,3.2.0 +CVMatrix-jax-cpu-nojit,True,10,100000,500,10,False,False,False,False,1.5342505369335413,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10,100000,500,10,False,False,False,False,6.067426074063405,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10,100000,500,10,False,False,False,False,0.6160863609984517,3.2.0 +CVMatrix-jax-cpu-nojit,True,100,100000,500,10,False,False,False,False,1.5889573320746422,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100,100000,500,10,False,False,False,False,6.2229238480795175,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100,100000,500,10,False,False,False,False,0.6620993650285527,3.2.0 +CVMatrix-jax-cpu-nojit,True,1000,100000,500,10,False,False,False,False,2.8438327610492706,3.2.0 +CVMatrix-jax-cpu-coldjit,True,1000,100000,500,10,False,False,False,False,6.3065667391056195,3.2.0 +CVMatrix-jax-cpu-warmjit,True,1000,100000,500,10,False,False,False,False,1.0215598409995437,3.2.0 +CVMatrix-jax-cpu-nojit,True,10000,100000,500,10,False,False,False,False,9.625814352999441,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10000,100000,500,10,False,False,False,False,9.841091375914402,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10000,100000,500,10,False,False,False,False,4.731531122932211,3.2.0 +CVMatrix-jax-cpu-nojit,True,100000,100000,500,10,False,False,False,False,56.788117082905956,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100000,100000,500,10,False,False,False,False,31.856476944987662,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100000,100000,500,10,False,False,False,False,26.553785535972565,3.2.0 +CVMatrix-jax-cpu-nojit,True,3,100000,500,10,True,True,False,False,2.1291528119472787,3.2.0 +CVMatrix-jax-cpu-coldjit,True,3,100000,500,10,True,True,False,False,11.624330532969907,3.2.0 +CVMatrix-jax-cpu-warmjit,True,3,100000,500,10,True,True,False,False,0.8000447900267318,3.2.0 +CVMatrix-jax-cpu-nojit,True,5,100000,500,10,True,True,False,False,1.6868692770367488,3.2.0 +CVMatrix-jax-cpu-coldjit,True,5,100000,500,10,True,True,False,False,6.064092201995663,3.2.0 +CVMatrix-jax-cpu-warmjit,True,5,100000,500,10,True,True,False,False,0.6561844360549003,3.2.0 +CVMatrix-jax-cpu-nojit,True,10,100000,500,10,True,True,False,False,1.7295897040748969,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10,100000,500,10,True,True,False,False,6.04276000300888,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10,100000,500,10,True,True,False,False,0.6275438821176067,3.2.0 +CVMatrix-jax-cpu-nojit,True,100,100000,500,10,True,True,False,False,1.7782964339712635,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100,100000,500,10,True,True,False,False,6.049204112030566,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100,100000,500,10,True,True,False,False,0.6719458089210093,3.2.0 +CVMatrix-jax-cpu-nojit,True,1000,100000,500,10,True,True,False,False,3.6490812728879973,3.2.0 +CVMatrix-jax-cpu-coldjit,True,1000,100000,500,10,True,True,False,False,6.275046626920812,3.2.0 +CVMatrix-jax-cpu-warmjit,True,1000,100000,500,10,True,True,False,False,0.918339499970898,3.2.0 +CVMatrix-jax-cpu-nojit,True,10000,100000,500,10,True,True,False,False,18.705401667044498,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10000,100000,500,10,True,True,False,False,9.688336852006614,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10000,100000,500,10,True,True,False,False,4.674701331998222,3.2.0 +CVMatrix-jax-cpu-nojit,True,100000,100000,500,10,True,True,False,False,143.26303969405126,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100000,100000,500,10,True,True,False,False,30.803388206055388,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100000,100000,500,10,True,True,False,False,25.407287453999743,3.2.0 +CVMatrix-jax-cpu-nojit,True,3,100000,500,10,True,True,True,True,2.5567956919549033,3.2.0 +CVMatrix-jax-cpu-coldjit,True,3,100000,500,10,True,True,True,True,17.105665273033082,3.2.0 +CVMatrix-jax-cpu-warmjit,True,3,100000,500,10,True,True,True,True,0.786845215014182,3.2.0 +CVMatrix-jax-cpu-nojit,True,5,100000,500,10,True,True,True,True,2.0708149360725656,3.2.0 +CVMatrix-jax-cpu-coldjit,True,5,100000,500,10,True,True,True,True,8.711395109072328,3.2.0 +CVMatrix-jax-cpu-warmjit,True,5,100000,500,10,True,True,True,True,0.6519641680642962,3.2.0 +CVMatrix-jax-cpu-nojit,True,10,100000,500,10,True,True,True,True,1.9614091481780633,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10,100000,500,10,True,True,True,True,8.78678807709366,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10,100000,500,10,True,True,True,True,0.6999291750835255,3.2.0 +CVMatrix-jax-cpu-nojit,True,100,100000,500,10,True,True,True,True,2.295204345951788,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100,100000,500,10,True,True,True,True,8.709234067006037,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100,100000,500,10,True,True,True,True,0.7009000619873405,3.2.0 +CVMatrix-jax-cpu-nojit,True,1000,100000,500,10,True,True,True,True,4.529199573909864,3.2.0 +CVMatrix-jax-cpu-coldjit,True,1000,100000,500,10,True,True,True,True,9.04653775296174,3.2.0 +CVMatrix-jax-cpu-warmjit,True,1000,100000,500,10,True,True,True,True,1.0235839679371566,3.2.0 +CVMatrix-jax-cpu-nojit,True,10000,100000,500,10,True,True,True,True,24.003119080909528,3.2.0 +CVMatrix-jax-cpu-coldjit,True,10000,100000,500,10,True,True,True,True,12.497502946993336,3.2.0 +CVMatrix-jax-cpu-warmjit,True,10000,100000,500,10,True,True,True,True,4.75980460701976,3.2.0 +CVMatrix-jax-cpu-nojit,True,100000,100000,500,10,True,True,True,True,197.2365424539894,3.2.0 +CVMatrix-jax-cpu-coldjit,True,100000,100000,500,10,True,True,True,True,33.92279968911316,3.2.0 +CVMatrix-jax-cpu-warmjit,True,100000,100000,500,10,True,True,True,True,25.930547127034515,3.2.0 diff --git a/benchmarks/plot_benchmark.py b/benchmarks/plot_benchmark.py index 2f075da..27f5f5b 100644 --- a/benchmarks/plot_benchmark.py +++ b/benchmarks/plot_benchmark.py @@ -11,6 +11,7 @@ E-mail: ole.e@di.ku.dk """ +import os from itertools import product import matplotlib.pyplot as plt @@ -20,23 +21,28 @@ from matplotlib.text import Text -def plot_cvmatrix_vs_naive(df, combination_to_color_map): +def plot_cvmatrix_vs_naive(df, combination_to_color_map, weights): + # Set text and label sizes to 20 + plt.rcParams.update({'font.size': 15, 'axes.labelsize': 15, 'axes.titlesize': 20, 'legend.fontsize': 15}) fig, ax = plt.subplots(figsize=(10, 10)) preprocessing_combinations = [] preprocessing_combinations.append( - {'center_X': False, 'center_Y': False, 'scale_X': False, 'scale_Y': False} + {'center_X': False, 'center_Y': False, 'scale_X': False, 'scale_Y': False, 'weights': weights} ) preprocessing_combinations.append( - {'center_X': True, 'center_Y': True, 'scale_X': False, 'scale_Y': False} + {'center_X': True, 'center_Y': True, 'scale_X': False, 'scale_Y': False, 'weights': weights} ) preprocessing_combinations.append( - {'center_X': True, 'center_Y': True, 'scale_X': True, 'scale_Y': True} + {'center_X': True, 'center_Y': True, 'scale_X': True, 'scale_Y': True, 'weights': weights} ) N = df['N'].unique()[0] K = df['K'].unique()[0] M = df['M'].unique()[0] for combination in preprocessing_combinations: - color = combination_to_color_map[str(combination)] + # Do not use the 'weights' key in combination when getting color + combination_no_weights = {k: v for k, v in combination.items() if k != 'weights'} + color = combination_to_color_map[str(combination_no_weights)] + fast_times = [] naive_times = [] Ps = [] @@ -47,7 +53,8 @@ def plot_cvmatrix_vs_naive(df, combination_to_color_map): (df['center_X'] == combination['center_X']) & (df['center_Y'] == combination['center_Y']) & (df['scale_X'] == combination['scale_X']) & - (df['scale_Y'] == combination['scale_Y']) + (df['scale_Y'] == combination['scale_Y']) & + (df['weights'] == combination['weights']) ]['time'].values[0] naive_time = df[ (df['model'] == 'NaiveCVMatrix') & @@ -55,29 +62,42 @@ def plot_cvmatrix_vs_naive(df, combination_to_color_map): (df['center_X'] == combination['center_X']) & (df['center_Y'] == combination['center_Y']) & (df['scale_X'] == combination['scale_X']) & - (df['scale_Y'] == combination['scale_Y']) + (df['scale_Y'] == combination['scale_Y']) & + (df['weights'] == combination['weights']) ]['time'].values[0] fast_times.append(fast_time) naive_times.append(naive_time) Ps.append(P) - label = "Fast (CVMatrix), " + ', '.join([f"{k}={v}" for k, v in combination.items()]) + # label = "Baseline, " + ', '.join( + # [f"{k}={v}" for k, v in combination.items() if k != 'weights'] + # ) + if combination['scale_X']: + label = "Algorithm 6" + elif combination['center_X']: + label = "Algorithm 4" + else: + label = "Algorithm 2" ax.plot( Ps, - fast_times, - marker='D', + naive_times, + marker='s', color=color, - linestyle='dashed', + linestyle='dotted', label=label ) - label = "Baseline, " + ', '.join( - [f"{k}={v}" for k, v in combination.items()] - ) + # label = "Fast (CVMatrix), " + ', '.join([f"{k}={v}" for k, v in combination.items() if k != 'weights']) + if combination['scale_X']: + label = "Algorithm 7" + elif combination['center_X']: + label = "Algorithm 5" + else: + label = "Algorithm 3" ax.plot( Ps, - naive_times, - marker='s', + fast_times, + marker='D', color=color, - linestyle='dotted', + linestyle='dashed', label=label ) lines = [1, 60, 3600, 86400] @@ -85,11 +105,11 @@ def plot_cvmatrix_vs_naive(df, combination_to_color_map): for j, line in enumerate(lines): ax.axhline(y=line, color='k', linestyle='--', linewidth=1) ax.text( - 310000, line, f"1 {line_names[j]}", fontsize=10, ha='center', va='center' + 310000, line, f"1 {line_names[j]}", fontsize=12, ha='center', va='center' ) cvmatrix_version = df['version'].unique()[0] - version_text = f'CVMatrix version: {cvmatrix_version}' - ax.text(1, 330000, version_text, fontsize=9, ha='center', va='center') + version_text = f'cvmatrix version: {cvmatrix_version}' + ax.text(1, 330000, version_text, fontsize=10, ha='center', va='center') ax.set_xscale('log') ax.set_yscale('log') current_x_ticks, current_x_labels = plt.xticks() @@ -106,16 +126,20 @@ def plot_cvmatrix_vs_naive(df, combination_to_color_map): new_x_labels = new_x_labels[start_idx:stop_idx] ax.set_xticks(new_x_ticks) ax.set_xticklabels(new_x_labels) - ax.set_title(f'Fast (CVMatrix) vs. Baseline Cross-Validation\n(N={N:,}, K={K}, M={M})', fontsize=10) + ax.set_title(f'Fast (cvmatrix) vs. Baseline Weighted Cross-Validation\n(N={N:,}, K={K}, M={M})', fontsize=20) ax.set_xlabel('P (cross-validation folds)') ax.set_ylabel('Time (s)') - fig.set_size_inches(8.27, 0.37 * 11.7) - ax.legend(loc='upper left', fontsize='xx-small') - plt.tight_layout() - plt.savefig('benchmark_cvmatrix_vs_naive.png') + # fig.set_size_inches(8.27, 0.37 * 11.7) + ax.legend(loc='upper left')#, fontsize='xx-small') + # plt.tight_layout() + if weights: + plt.savefig('benchmark_cvmatrix_vs_naive_weights.png') + plt.savefig('benchmark_cvmatrix_vs_naive_weights.eps') + else: + plt.savefig('benchmark_cvmatrix_vs_naive_no_weights.png') plt.clf() -def plot_cvmatrix(df, combination_to_color_map): +def plot_cvmatrix(df, combination_to_color_map, weights): fig, ax = plt.subplots(figsize=(10, 10)) preprocessing_combinations = [] center_Xs = [False, True] @@ -129,14 +153,16 @@ def plot_cvmatrix(df, combination_to_color_map): 'center_X': center_X, 'center_Y': center_Y, 'scale_X': scale_X, - 'scale_Y': scale_Y + 'scale_Y': scale_Y, + 'weights': weights } ) N = df['N'].unique()[0] K = df['K'].unique()[0] M = df['M'].unique()[0] for combination in preprocessing_combinations: - color = combination_to_color_map[str(combination)] + combination_no_weights = {k: v for k, v in combination.items() if k != 'weights'} + color = combination_to_color_map[str(combination_no_weights)] fast_times = [] Ps = [] for P in df['P'].unique(): @@ -146,11 +172,12 @@ def plot_cvmatrix(df, combination_to_color_map): (df['center_X'] == combination['center_X']) & (df['center_Y'] == combination['center_Y']) & (df['scale_X'] == combination['scale_X']) & - (df['scale_Y'] == combination['scale_Y']) + (df['scale_Y'] == combination['scale_Y']) & + (df['weights'] == combination['weights']) ]['time'].values[0] fast_times.append(fast_time) Ps.append(P) - label = ', '.join([f"{k}={v}" for k, v in combination.items()]) + label = ', '.join([f"{k}={v}" for k, v in combination.items() if k != 'weights']) ax.plot( Ps, fast_times, @@ -164,11 +191,11 @@ def plot_cvmatrix(df, combination_to_color_map): for j, line in enumerate(lines): ax.axhline(y=line, color='k', linestyle='--', linewidth=1) ax.text( - 310000,line, f"1 {line_names[j]}", fontsize=10, ha='center', va='center' + 310000,line, f"1 {line_names[j]}", fontsize=20, ha='center', va='center' ) cvmatrix_version = df['version'].unique()[0] version_text = f'CVMatrix version: {cvmatrix_version}' - ax.text(1, 330000, version_text, fontsize=9, ha='center', va='center') + ax.text(1, 330000, version_text, fontsize=20, ha='center', va='center') ax.set_xscale('log') ax.set_yscale('log') current_x_ticks, current_x_labels = plt.xticks() @@ -191,9 +218,155 @@ def plot_cvmatrix(df, combination_to_color_map): fig.set_size_inches(8.27, 0.37 * 11.7) ax.legend(loc='upper left', fontsize='xx-small') plt.tight_layout() - plt.savefig('benchmark_cvmatrix.png') + if weights: + plt.savefig('benchmark_cvmatrix_weights.png') + else: + plt.savefig('benchmark_cvmatrix_no_weights.png') + plt.clf() + +def _add_time_reflines(ax, ymax): + """Draw "1 Second/Minute/Hour/Day" reference lines up to (a little above) ymax.""" + from matplotlib.text import Text # noqa: F401 (kept for parity with other plots) + for y, name in [(1, 'Second'), (60, 'Minute'), (3600, 'Hour'), (86400, 'Day')]: + if y <= ymax * 2: + ax.axhline(y=y, color='k', linestyle='--', linewidth=1) + ax.text(310000, y, f'1 {name}', fontsize=11, ha='center', va='center') + + +_COMBOS = [ + ((False, False, False, False), 'solid', 'no preprocessing'), + ((True, True, False, False), 'dashed', 'centering'), + ((True, True, True, True), 'dotted', 'centering + scaling'), +] + + +def _combo_dict(key): + return dict(zip(('center_X', 'center_Y', 'scale_X', 'scale_Y'), key)) + + +def _series_times(df, model, key, Ps_all): + """Per-P times (weighted) for a given model and (center_X, center_Y, scale_X, + scale_Y) combination; returns (Ps, times) over the P values that have a row.""" + cX, cY, sX, sY = key + times, Ps = [], [] + for P in Ps_all: + sel = df[ + (df['model'] == model) & (df['P'] == P) & (df['weights'] == True) + & (df['center_X'] == cX) & (df['center_Y'] == cY) + & (df['scale_X'] == sX) & (df['scale_Y'] == sY) + ]['time'].values + if len(sel): + times.append(sel[0]) + Ps.append(P) + return Ps, times + + +def plot_numpy_vs_jax(df, combination_to_color_map): + """ + benchmark_cvmatrix_numpy_vs_jax.png: total weighted cross-validation time for the + CVMatrix NumPy backend vs. the JAX backend (warm, JIT-compiled jax.vmap over folds) on + CPU and GPU, across the three preprocessing combinations. All measured multi-threaded + (no thread limit) at cvmatrix 3.2.0. Color encodes preprocessing; line style encodes + backend. + """ + from matplotlib.lines import Line2D + plt.rcParams.update({'font.size': 15, 'axes.labelsize': 15, + 'axes.titlesize': 18, 'legend.fontsize': 11}) + fig, ax = plt.subplots(figsize=(10, 10)) + # (model, linestyle, marker, legend label) + series = [ + ('CVMatrix', 'dashed', 'D', 'NumPy'), + ('CVMatrix-jax-cpu-warmjit', 'dashdot', '^', 'JAX warm (CPU)'), + ('CVMatrix-jax-gpu-warmjit', 'solid', 'o', 'JAX warm (GPU)'), + ] + N = df['N'].unique()[0] + K = df['K'].unique()[0] + M = df['M'].unique()[0] + Ps_all = sorted(df['P'].unique()) + ymax = 0.0 + for key, _ls, combo_lab in _COMBOS: + color = combination_to_color_map[str(_combo_dict(key))] + for model, linestyle, marker, _lab in series: + Ps, times = _series_times(df, model, key, Ps_all) + if times: + ax.plot(Ps, times, marker=marker, color=color, linestyle=linestyle) + ymax = max(ymax, max(times)) + _add_time_reflines(ax, ymax) + ax.set_xscale('log') + ax.set_yscale('log') + ax.set_title('CVMatrix NumPy vs. JAX (warm) Weighted Cross-Validation\n' + f'(N={N:,}, K={K}, M={M})', fontsize=18) + ax.set_xlabel('P (cross-validation folds)') + ax.set_ylabel('Time (s)') + backend_handles = [Line2D([0], [0], color='gray', linestyle=ls, marker=mk, label=lab) + for _m, ls, mk, lab in series] + combo_handles = [Line2D([0], [0], color=combination_to_color_map[str(_combo_dict(k))], + lw=3, label=lab) for k, _ls, lab in _COMBOS] + leg1 = ax.legend(handles=backend_handles, loc='upper left', title='Backend') + ax.add_artist(leg1) + ax.legend(handles=combo_handles, loc='lower right', title='Preprocessing') + fig.text(0.5, 0.01, 'cvmatrix 3.2.0; multi-threaded CPU; GPU: RTX 3090 Ti', + ha='center', va='bottom', fontsize=9, alpha=0.7) + plt.tight_layout(rect=(0, 0.03, 1, 1)) + plt.savefig('benchmark_cvmatrix_numpy_vs_jax.png') plt.clf() + +def plot_jax_variants(df): + """ + benchmark_jax_variants.png: total weighted cross-validation time for the CVMatrix JAX + backend under no-JIT (eager vmap), cold-JIT (compilation + run) and warm-JIT (run + only), on CPU and GPU, across the three preprocessing combinations. Multi-threaded + CPU, cvmatrix 3.2.0. Color encodes the JAX mode/device; line style encodes + preprocessing. + """ + from matplotlib.lines import Line2D + plt.rcParams.update({'font.size': 15, 'axes.labelsize': 15, + 'axes.titlesize': 18, 'legend.fontsize': 10}) + fig, ax = plt.subplots(figsize=(10, 10)) + variants = [ + ('CVMatrix-jax-gpu-nojit', 'GPU, no-JIT'), + ('CVMatrix-jax-gpu-coldjit', 'GPU, cold-JIT'), + ('CVMatrix-jax-gpu-warmjit', 'GPU, warm-JIT'), + ('CVMatrix-jax-cpu-nojit', 'CPU, no-JIT'), + ('CVMatrix-jax-cpu-coldjit', 'CPU, cold-JIT'), + ('CVMatrix-jax-cpu-warmjit', 'CPU, warm-JIT'), + ] + cm = colormaps.get_cmap('tab10') + variant_colors = {m: cm(i) for i, (m, _lab) in enumerate(variants)} + N = df['N'].unique()[0] + K = df['K'].unique()[0] + M = df['M'].unique()[0] + Ps_all = sorted(df['P'].unique()) + ymax = 0.0 + for key, linestyle, _combo_lab in _COMBOS: + for model, _lab in variants: + Ps, times = _series_times(df, model, key, Ps_all) + if times: + ax.plot(Ps, times, color=variant_colors[model], linestyle=linestyle, + linewidth=1.8) + ymax = max(ymax, max(times)) + _add_time_reflines(ax, ymax) + ax.set_xscale('log') + ax.set_yscale('log') + ax.set_title('CVMatrix JAX backend: execution-mode comparison\n' + f'(weighted, N={N:,}, K={K}, M={M})', fontsize=18) + ax.set_xlabel('P (cross-validation folds)') + ax.set_ylabel('Time (s)') + variant_handles = [Line2D([0], [0], color=variant_colors[m], lw=2.5, label=lab) + for m, lab in variants] + combo_handles = [Line2D([0], [0], color='gray', linestyle=ls, label=lab) + for _k, ls, lab in _COMBOS] + leg1 = ax.legend(handles=variant_handles, loc='upper left', title='JAX mode / device') + ax.add_artist(leg1) + ax.legend(handles=combo_handles, loc='lower right', title='Preprocessing') + fig.text(0.5, 0.01, 'cvmatrix 3.2.0; multi-threaded CPU; GPU: RTX 3090 Ti', + ha='center', va='bottom', fontsize=9, alpha=0.7) + plt.tight_layout(rect=(0, 0.03, 1, 1)) + plt.savefig('benchmark_jax_variants.png') + plt.clf() + + def get_combination_to_color_map(): preprocessing_combinations = [] center_Xs = [False, True] @@ -223,5 +396,12 @@ def get_combination_to_color_map(): plt.rcParams.update({'font.size': 12}) df = pd.read_csv('benchmark_results.csv') combination_to_color_map = get_combination_to_color_map() - plot_cvmatrix_vs_naive(df, combination_to_color_map) - plot_cvmatrix(df, combination_to_color_map) + for weights in [True, False]: + plot_cvmatrix_vs_naive(df, combination_to_color_map, weights) + plot_cvmatrix(df, combination_to_color_map, weights) + # Multi-threaded CVMatrix NumPy-vs-JAX and JAX execution-mode figures, from the + # separate results CSV produced by the BENCH_JAX_VARIANTS / multi-threaded runs. + if os.path.exists('benchmark_results_jax.csv'): + df_jax = pd.read_csv('benchmark_results_jax.csv') + plot_numpy_vs_jax(df_jax, combination_to_color_map) + plot_jax_variants(df_jax) diff --git a/cvmatrix/__init__.py b/cvmatrix/__init__.py index 0e2a5c9..9e40323 100644 --- a/cvmatrix/__init__.py +++ b/cvmatrix/__init__.py @@ -1,4 +1,4 @@ -__version__ = "3.1.6" +__version__ = "3.2.0" __all__ = ["CVMatrix", "Partitioner"] from .cvmatrix import CVMatrix from .partitioner import Partitioner diff --git a/cvmatrix/cvmatrix.py b/cvmatrix/cvmatrix.py index 3fba937..ee0135d 100644 --- a/cvmatrix/cvmatrix.py +++ b/cvmatrix/cvmatrix.py @@ -10,11 +10,84 @@ E-mail: ocge@foss.dk """ -from typing import Optional, Tuple, Union +from typing import TYPE_CHECKING, Literal, Optional, Tuple, Union import numpy as np from numpy import typing as npt +try: + # Broaden the array/scalar type hints so that, with backend="jax", jax.numpy values + # (which are jax.Array instances, including under jit/vmap tracing) satisfy runtime + # type checking. numpy remains the only required dependency; this is skipped if jax + # is not installed, leaving the hints numpy-only. + import jax as _jax + + Array = Union[np.ndarray, _jax.Array] + Scalar = Union[np.floating, _jax.Array, float, int] + # float dtype types accepted by `dtype`: numpy float types (`type[np.floating]`) and + # JAX scalar dtypes. The latter (e.g. jnp.float64/float32/bfloat16) are instances of + # JAX's scalar-type metaclass -- NOT numpy.floating subclasses -- so they must be + # admitted via that metaclass. + _JaxScalarMeta = type(_jax.numpy.float64) + FloatDType = Union[type[np.floating], _JaxScalarMeta] + # Abstract values produced while a function is traced by jax.jit/jax.vmap. Used to + # skip data-dependent validity raises that cannot run under tracing (the host-side + # caller is expected to validate folds before vmap); eager (concrete) jax values are + # NOT tracers, so they still validate like the numpy backend. + _TRACER_TYPES: tuple = (_jax.core.Tracer,) +except ImportError: # pragma: no cover - exercised only without jax + Array = np.ndarray + Scalar = Union[np.floating, float, int] + FloatDType = type[np.floating] + _TRACER_TYPES = () + +if TYPE_CHECKING: + # JAX's scalar-dtype metaclass (`type(jnp.float64)`) has no public *static* type, so + # the precise runtime `FloatDType` union above is built from a value (`_JaxScalarMeta`) + # that static type checkers reject in a type expression ("variables are not allowed in + # type expressions"). For static analysis only, expose numpy's standard dtype-specifier + # alias, which cleanly accepts both numpy float types and JAX scalar dtypes. At run + # time `TYPE_CHECKING` is False, so typeguard still validates against the precise union. + FloatDType = npt.DTypeLike + + +def _resolve_backend(backend: str): + """ + Resolve the array-namespace module for a given backend name. + + Parameters + ---------- + backend : {"numpy", "jax"} + The array backend to use. ``"numpy"`` uses ``numpy`` (the default and only + required dependency). ``"jax"`` uses ``jax.numpy`` so that the per-fold training + matrix computations can run inside ``jax.jit``/``jax.vmap`` on CPU/GPU/TPU; it + requires the optional ``jax`` dependency (``pip install cvmatrix[jax]``). + + Returns + ------- + module + The resolved array namespace (``numpy`` or ``jax.numpy``). + + Raises + ------ + ValueError + If ``backend`` is not ``"numpy"`` or ``"jax"``. + ImportError + If ``backend == "jax"`` but JAX is not installed. + """ + if backend == "numpy": + return np + if backend == "jax": + try: + import jax.numpy as jnp + except ImportError as e: # pragma: no cover - exercised only without jax + raise ImportError( + "backend='jax' requires the optional JAX dependency. Install it with " + "`pip install cvmatrix[jax]`." + ) from e + return jnp + raise ValueError(f"Invalid backend: {backend!r}. Must be 'numpy' or 'jax'.") + class CVMatrix: r""" @@ -81,8 +154,9 @@ def __init__( scale_X: bool = True, scale_Y: bool = True, ddof: int = 1, - dtype: type[np.floating] = np.float64, + dtype: FloatDType = np.float64, copy: bool = True, + backend: Literal["numpy", "jax"] = "numpy", ) -> None: self.center_X = center_X self.center_Y = center_Y @@ -91,6 +165,18 @@ def __init__( self.ddof = ddof self.dtype = dtype.type if isinstance(dtype, np.dtype) else dtype self.copy = copy + self.backend = backend + # Array namespace: numpy (default) or jax.numpy. All array operations are routed + # through `self.xp` so that, with backend="jax", the per-fold `training_*` + # methods can be traced by jax.jit/jax.vmap. The numpy backend is byte-identical + # to previous releases. + self.xp = _resolve_backend(backend) + if backend == "jax" and np.dtype(self.dtype).itemsize >= 8: + # JAX defaults to 32-bit; enable 64-bit precision so a requested float64 + # dtype is honored instead of being silently truncated to float32. + import jax + + jax.config.update("jax_enable_x64", True) self.resolution = np.finfo(dtype).resolution * 10 self.X = None self.Y = None @@ -139,10 +225,10 @@ def fit( Attributes ---------- - X : np.ndarray + X : Array The total predictor matrix `X` for the entire dataset. - Y : np.ndarray or None + Y : Array or None The total response matrix `Y` for the entire dataset. If `Y` is `None`, this is `None`. @@ -155,60 +241,60 @@ def fit( M : int or None The number of response variables in `Y`. If `Y` is `None`, this is `None`. - XTX : np.ndarray + XTX : Array The total matrix :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` for the entire dataset. - XTY : np.ndarray or None + XTY : Array or None The total matrix :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}` for the entire dataset. This is computed only if `Y` is not `None`. - sum_X : np.ndarray or None + sum_X : Array or None The row of column-wise weighted sums of `X` for the entire dataset. This is computed only if `center_X`, `scale_X`, or `center_Y` is `True`. This is the row of column-wise sums of :math:`\mathbf{W}\mathbf{X}` if `weights` are provided and otherwise the row of column-wise sums of :math:`\mathbf{X}`. - sum_Y : np.ndarray or None + sum_Y : Array or None The row of column-wise weighted sums of `Y` for the entire dataset. This is computed only if `center_Y`, `scale_Y`, or `center_X` is `True` and `Y` is not `None`. This is the row of column-wise sums of :math:`\mathbf{W}\mathbf{Y}` if `weights` are provided and otherwise the row of column-wise sums of :math:`\mathbf{Y}`. - sum_sq_X : np.ndarray or None + sum_sq_X : Array or None The row of column-wise weighted squared sums of `X` for the entire dataset. This is computed only if `scale_X` is `True`. This is the row of column-wise sums of :math:`\mathbf{W}\mathbf{X}\odot\mathbf{X}` if `weights` are provided and otherwise the row of column-wise sums of :math:`\mathbf{X}\odot\mathbf{X}`. - sum_sq_Y : np.ndarray or None + sum_sq_Y : Array or None The row of column-wise weighted squared sums of `Y` for the entire dataset. This is computed only if `scale_Y` is `True` and `Y` is not `None`. This is the row of column-wise sums of :math:`\mathbf{W}\mathbf{Y}\odot\mathbf{Y}` if `weights` are provided and otherwise the row of column-wise sums of :math:`\mathbf{Y}\odot\mathbf{Y}`. - sq_X : np.ndarray or None + sq_X : Array or None The total weighted squared predictor matrix `X` for the entire dataset. This is :math:`\mathbf{W}\mathbf{X}\odot\mathbf{X}`. This is computed only if `scale_X` is `True`. - sq_Y : np.ndarray or None + sq_Y : Array or None The total weighted squared response matrix `Y` for the entire dataset. This is :math:`\mathbf{W}\mathbf{Y}\odot\mathbf{Y}`. This is computed only if `scale_Y` is `True` and `Y` is not `None`. - WX : np.ndarray or None + WX : Array or None The total weighted predictor matrix `X` for the entire dataset. This is :math:`\mathbf{W}\mathbf{X}`. - WY : np.ndarray or None + WY : Array or None The total weighted response matrix `Y` for the entire dataset. This is :math:`\mathbf{W}\mathbf{Y}`. This is computed only if `Y` is not `None`. - weights : np.ndarray or None + weights : Array or None The total weights for the entire dataset. This is an array of shape (N, 1). If `weights` is `None`, this is `None`. @@ -237,7 +323,7 @@ def fit( def training_XTX( self, validation_indices: npt.NDArray[np.int_] ) -> Tuple[ - np.ndarray, Tuple[Optional[np.ndarray], Optional[np.ndarray], None, None] + Array, Tuple[Optional[Array], Optional[Array], None, None] ]: r""" Computes the training set :math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` @@ -292,12 +378,12 @@ def training_XTX( def training_XTY( self, validation_indices: npt.NDArray[np.int_] ) -> Tuple[ - np.ndarray, + Array, Tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Array], ], ]: r""" @@ -358,12 +444,12 @@ def training_XTY( def training_XTX_XTY( self, validation_indices: npt.NDArray[np.int_] ) -> Tuple[ - Tuple[np.ndarray, np.ndarray], + Tuple[Array, Array], Tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Array], ], ]: r""" @@ -426,10 +512,10 @@ def training_XTX_XTY( def training_statistics( self, validation_indices: npt.NDArray[np.int_] ) -> Tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Array], ]: """ Computes the row of column-wise weighted means and standard deviations for `X` @@ -448,7 +534,7 @@ def training_statistics( Returns ------- - Tuple of four elements of Optional[np.ndarray] + Tuple of four elements of Optional[Array] A tuple containing the row of column-wise weighted means for `X`, the row of column-wise weighted standard deviations for `X`, the row of column-wise weighted means for `Y`, and the row of column-wise weighted standard @@ -480,9 +566,22 @@ def training_statistics( return_Y_std=self.scale_Y and self.Y is not None, )[:-1] # Exclude the sum of training weights from the return tuple + def _as_scalar(self, x): + """ + Cast a scalar-valued result to the configured dtype. + + For the numpy backend this returns a NumPy scalar (preserving the historical + ``np.floating`` return types and byte-identical behavior). For the jax backend + it returns the value unchanged (already a ``jax.numpy`` value of the right + dtype), keeping the per-fold path trace-safe. + """ + if self.backend == "numpy": + return self.dtype(x) + return x + def _get_sum_w_train_and_num_nonzero_w_train( self, val_indices: npt.NDArray[np.int_] - ) -> Tuple[np.floating, np.floating]: + ) -> Tuple[Scalar, Scalar]: """ Returns a tuple containing the sum of weights in the training set and the number of non-zero weights in the training set. If `self.weights` is `None`, it returns @@ -502,16 +601,21 @@ def _get_sum_w_train_and_num_nonzero_w_train( make it impossible to compute either of training set means or standard deviations. """ + xp = self.xp if self.weights is None: sum_w_val = val_indices.size - sum_w_train = self.dtype(self.sum_w - sum_w_val) + sum_w_train = self._as_scalar(self.sum_w - sum_w_val) return (sum_w_train, sum_w_train) w_val = self.weights[val_indices] - sum_w_val = np.sum(w_val) - sum_w_train = self.dtype(self.sum_w - sum_w_val) - num_nonzero_w_val = np.count_nonzero(w_val) - num_nonzero_w_train = self.dtype(self.num_nonzero_w - num_nonzero_w_val) - if num_nonzero_w_train == 0: + sum_w_val = xp.sum(w_val) + sum_w_train = self._as_scalar(self.sum_w - sum_w_val) + num_nonzero_w_val = xp.count_nonzero(w_val) + num_nonzero_w_train = self._as_scalar(self.num_nonzero_w - num_nonzero_w_val) + # Validate eagerly (numpy, or concrete/eager jax). Under jax.jit/vmap tracing the + # input `val_indices` is a tracer (in both the weighted and unweighted paths), so + # we skip the data-dependent raise; degenerate folds are then expected to be + # rejected by a host-side pre-flight before vmap. + if not isinstance(val_indices, _TRACER_TYPES) and num_nonzero_w_train == 0: raise ValueError( "The number of non-zero weights in the training set must be " "greater than zero." @@ -521,18 +625,18 @@ def _get_sum_w_train_and_num_nonzero_w_train( def _compute_training_stats( self, val_indices: npt.NDArray[np.int_], - X_val: Optional[np.ndarray], - Y_val: Optional[np.ndarray], + X_val: Optional[Array], + Y_val: Optional[Array], return_X_mean: bool, return_X_std: bool, return_Y_mean: bool, return_Y_std: bool, ) -> Tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.floating], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Scalar], ]: """ Computes the training set statistics. The training set corresponds @@ -572,7 +676,7 @@ def _compute_training_stats( Returns ------- - Tuple of Optional[np.ndarray] + Tuple of Optional[Array] A tuple containing the row of column-wise weighted means for `X`, the row of column-wise weighted standard deviations for `X`, the row of column-wise weighted means for `Y`, the row of column-wise weighted standard deviations @@ -586,27 +690,34 @@ def _compute_training_stats( and not return_Y_std ): return None, None, None, None, None + # Under jax.jit/vmap, `val_indices` is a tracer; data-dependent validity raises + # are then deferred to a host-side pre-flight (see `_get_sum_w_train_*` and + # `_compute_std_divisor`). The unweighted path derives its counts from static + # shapes, so it cannot rely on the count itself being a tracer. + traced = isinstance(val_indices, _TRACER_TYPES) sum_w_train, num_nonzero_w_train = ( self._get_sum_w_train_and_num_nonzero_w_train(val_indices) ) if return_X_mean or return_X_std: - sum_X_val = np.sum(X_val, axis=0, keepdims=True) + sum_X_val = self.xp.sum(X_val, axis=0, keepdims=True) sum_X_train = self._compute_train_mat_sum(sum_X_val, self.sum_X) X_train_mean = self._compute_training_mat_mean( sum_X_train, sum_w_train, ) if return_Y_mean or return_Y_std: - sum_Y_val = np.sum(Y_val, axis=0, keepdims=True) + sum_Y_val = self.xp.sum(Y_val, axis=0, keepdims=True) sum_Y_train = self._compute_train_mat_sum(sum_Y_val, self.sum_Y) Y_train_mean = self._compute_training_mat_mean( sum_Y_train, sum_w_train, ) if return_X_std or return_Y_std: - divisor = self._compute_std_divisor(sum_w_train, num_nonzero_w_train) + divisor = self._compute_std_divisor( + sum_w_train, num_nonzero_w_train, traced + ) if return_X_std: - sum_sq_X_val = np.sum(self.sq_X[val_indices], axis=0, keepdims=True) + sum_sq_X_val = self.xp.sum(self.sq_X[val_indices], axis=0, keepdims=True) sum_sq_X_train = self._compute_train_mat_sum(sum_sq_X_val, self.sum_sq_X) X_train_std = self._compute_training_mat_std( sum_sq_X_train, @@ -616,7 +727,7 @@ def _compute_training_stats( divisor, ) if return_Y_std: - sum_sq_Y_val = np.sum(self.sq_Y[val_indices], axis=0, keepdims=True) + sum_sq_Y_val = self.xp.sum(self.sq_Y[val_indices], axis=0, keepdims=True) sum_sq_Y_train = self._compute_train_mat_sum(sum_sq_Y_val, self.sum_sq_Y) Y_train_std = self._compute_training_mat_std( sum_sq_Y_train, @@ -636,12 +747,12 @@ def _compute_training_stats( def _training_matrices( self, return_XTX: bool, return_XTY: bool, val_indices: npt.NDArray[np.int_] ) -> Tuple[ - Union[np.ndarray, Tuple[np.ndarray, np.ndarray]], + Union[Array, Tuple[Array, Array]], Tuple[ - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], - Optional[np.ndarray], + Optional[Array], + Optional[Array], + Optional[Array], + Optional[Array], ], ]: r""" @@ -780,10 +891,10 @@ def _training_matrices( def _get_val_matrices( self, val_indices: npt.NDArray[np.int_], return_XTY: bool ) -> Tuple[ - np.ndarray, - np.ndarray, - Optional[np.ndarray], - Optional[np.ndarray], + Array, + Array, + Optional[Array], + Optional[Array], ]: """ Returns the validation set matrices for a given fold. @@ -824,16 +935,16 @@ def _get_val_matrices( def _training_kernel_matrix( self, - total_kernel_mat: np.ndarray, - X_val: np.ndarray, - mat2_val: np.ndarray, - X_train_mean: Optional[np.ndarray] = None, - mat2_train_mean: Optional[np.ndarray] = None, - X_train_std: Optional[np.ndarray] = None, - mat2_train_std: Optional[np.ndarray] = None, - sum_w_train: Optional[np.floating] = None, + total_kernel_mat: Array, + X_val: Array, + mat2_val: Array, + X_train_mean: Optional[Array] = None, + mat2_train_mean: Optional[Array] = None, + X_train_std: Optional[Array] = None, + mat2_train_std: Optional[Array] = None, + sum_w_train: Optional[Scalar] = None, center: bool = False, - ) -> np.ndarray: + ) -> Array: r""" Computes the training set kernel matrix for a given fold. @@ -893,9 +1004,9 @@ def _training_kernel_matrix( def _compute_train_mat_sum( self, - sum_mat_val: np.ndarray, - sum_mat: np.ndarray, - ) -> np.ndarray: + sum_mat_val: Array, + sum_mat: Array, + ) -> Array: """ Computes the row vector of column-wise sums of a matrix for a given fold. """ @@ -903,9 +1014,9 @@ def _compute_train_mat_sum( def _compute_training_mat_mean( self, - sum_mat_train: np.ndarray, - sum_w_train: np.floating, - ) -> np.ndarray: + sum_mat_train: Array, + sum_w_train: Scalar, + ) -> Array: """ Computes the row of column-wise means of a matrix for a given fold. @@ -925,8 +1036,8 @@ def _compute_training_mat_mean( return sum_mat_train / sum_w_train def _compute_std_divisor( - self, sum_w_train: np.floating, num_nonzero_w_train: np.floating - ) -> np.floating: + self, sum_w_train: Scalar, num_nonzero_w_train: Scalar, traced: bool = False + ) -> Scalar: """ Computes the divisor for the standard deviation calculation based on the number of samples in the training set and the number of non-zero weights. @@ -939,12 +1050,21 @@ def _compute_std_divisor( num_nonzero_w_train : int The number of non-zero weights in the training set. + traced : bool, default=False + Whether the computation is being traced by jax.jit/jax.vmap. When True, the + data-dependent degenerate-fold check is skipped (deferred to a host-side + pre-flight); a non-positive divisor then degrades gracefully via the clamp in + `_compute_training_mat_std`. + Returns ------- float The divisor for the standard deviation calculation. """ - if num_nonzero_w_train <= self.ddof: + # Validate eagerly (numpy, or concrete/eager jax). Under jax.jit/vmap tracing + # (`traced`, detected from the input `val_indices`) the check is deferred to a + # host-side pre-flight before vmap. + if not traced and num_nonzero_w_train <= self.ddof: raise ValueError( "The number of non-zero weights in the training set must be greater " "than `ddof`." @@ -953,12 +1073,12 @@ def _compute_std_divisor( def _compute_training_mat_std( self, - sum_sq_mat_train: np.ndarray, - mat_train_mean: np.ndarray, - sum_mat_train: np.ndarray, - sum_w_train: np.floating, - divisor: np.floating, - ) -> np.ndarray: + sum_sq_mat_train: Array, + mat_train_mean: Array, + sum_mat_train: Array, + sum_w_train: Scalar, + divisor: Scalar, + ) -> Array: r""" Computes the row of column-wise standard deviations of a matrix for a given fold. @@ -988,17 +1108,20 @@ def _compute_training_mat_std( Array of shape (1, K) or (1, M) The row of column-wise standard deviations of the training set matrix. """ + xp = self.xp mat_train_var = ( -2 * mat_train_mean * sum_mat_train + sum_w_train * mat_train_mean**2 + sum_sq_mat_train ) / divisor - mat_train_var[mat_train_var < 0] = 0 - mat_train_std = np.sqrt(mat_train_var) - mat_train_std[mat_train_std <= self.resolution] = 1 + # `xp.maximum`/`xp.where` instead of boolean-mask in-place assignment: identical + # results for numpy and trace-safe for jax (jit/vmap). + mat_train_var = xp.maximum(mat_train_var, 0) + mat_train_std = xp.sqrt(mat_train_var) + mat_train_std = xp.where(mat_train_std <= self.resolution, 1, mat_train_std) return mat_train_std - def _init_mat(self, mat: np.ndarray) -> np.ndarray: + def _init_mat(self, mat: Array) -> Array: """ Casts the matrix to the dtype specified in the constructor and reshapes it if the matrix is one-dimensional. @@ -1013,7 +1136,7 @@ def _init_mat(self, mat: np.ndarray) -> np.ndarray: Array of shape (N, K) or (N, M) or (N, 1) The initialized matrix. """ - mat = np.asarray(mat, dtype=self.dtype) + mat = self.xp.asarray(mat, dtype=self.dtype) if self.copy and mat.dtype == self.dtype: mat = mat.copy() if mat.ndim == 1: @@ -1055,7 +1178,7 @@ def _init_mats( if weights is not None: self.weights = self._init_mat(weights) - if np.any(self.weights < 0): + if bool(self.xp.any(self.weights < 0)): raise ValueError("Weights must be non-negative.") else: self.weights = None @@ -1092,22 +1215,22 @@ def _init_stats(self) -> None: """ if self.center_X or self.center_Y or self.scale_X or self.scale_Y: if self.weights is not None: - self.sum_w = np.sum(self.weights) - self.num_nonzero_w = np.count_nonzero(self.weights) + self.sum_w = self.xp.sum(self.weights) + self.num_nonzero_w = self.xp.count_nonzero(self.weights) else: self.sum_w = self.N self.num_nonzero_w = self.N if self.center_X or self.center_Y or self.scale_X: - self.sum_X = np.sum(self.WX, axis=0, keepdims=True) + self.sum_X = self.xp.sum(self.WX, axis=0, keepdims=True) if (self.center_X or self.center_Y or self.scale_Y) and self.Y is not None: - self.sum_Y = np.sum(self.WY, axis=0, keepdims=True) + self.sum_Y = self.xp.sum(self.WY, axis=0, keepdims=True) if self.scale_X: self.sq_X = self.WX * self.X - self.sum_sq_X = np.sum(self.sq_X, axis=0, keepdims=True) + self.sum_sq_X = self.xp.sum(self.sq_X, axis=0, keepdims=True) else: self.sum_sq_X = None if self.scale_Y and self.Y is not None: self.sq_Y = self.WY * self.Y - self.sum_sq_Y = np.sum(self.sq_Y, axis=0, keepdims=True) + self.sum_sq_Y = self.xp.sum(self.sq_Y, axis=0, keepdims=True) else: self.sum_sq_Y = None diff --git a/examples/training_matrices_jax.py b/examples/training_matrices_jax.py new file mode 100644 index 0000000..e9110d8 --- /dev/null +++ b/examples/training_matrices_jax.py @@ -0,0 +1,78 @@ +r""" +This file demonstrates the optional **JAX backend** of CVMatrix (``backend="jax"``). +With the JAX backend, the per-fold training matrices +:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{X}` and +:math:`\mathbf{X}^{\mathbf{T}}\mathbf{W}\mathbf{Y}` (with training-set centering/scaling +and weighting) are computed with ``jax.numpy``. Because each ``training_*`` call is a +pure function of the (closed-over) dataset-wide statistics and the validation indices, +it can be traced by ``jax.jit`` and -- most usefully -- batched over folds with +``jax.vmap`` to run all folds together on a CPU/GPU/TPU. + +The numerical results are identical to the default ``backend="numpy"``. + +Note: requires the optional JAX dependency, installed with ``pip install cvmatrix[jax]``. + +Author: Ole-Christian Galbo Engstrøm +E-mail: ocge@foss.dk +""" + +import jax +import jax.numpy as jnp +import numpy as np + +from cvmatrix import CVMatrix, Partitioner + +# Honor float64 (JAX defaults to float32). CVMatrix also enables this automatically when +# constructed with backend="jax" and a 64-bit dtype. +jax.config.update("jax_enable_x64", True) + + +if __name__ == "__main__": + rng = np.random.default_rng(42) + N, K, M = 100, 10, 3 + X = rng.uniform(size=(N, K)) + Y = rng.uniform(size=(N, M)) + weights = rng.uniform(size=(N,)) + 0.1 # non-negative + + # 5-fold cross-validation (equal-size folds -> a single vmap shape). + folds = np.arange(N) % 5 + + # Build a JAX-backed CVMatrix. The only change from the numpy backend is + # backend="jax"; the API is identical. + cvm = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, backend="jax" + ) + cvm.fit(X, Y, weights) + + # Partitioner does host-side fold/index bookkeeping (numpy). Stack the per-fold + # validation indices into a single (n_folds, fold_size) array and move it to the + # device once; jax.vmap then computes every fold's training matrices together. + p = Partitioner(folds) + val_index_batch = jnp.asarray( + np.stack([p.get_validation_indices(fold) for fold in p.folds_dict]) + ) + + # vmap (optionally under jit) the per-fold training-matrix computation over folds. + batched_training_XTX_XTY = jax.jit(jax.vmap(cvm.training_XTX_XTY)) + (XTWX, XTWY), (X_mean, X_std, Y_mean, Y_std) = batched_training_XTX_XTY( + val_index_batch + ) + + # The leading axis of each output indexes the folds. + print(f"Folds: {list(p.folds_dict.keys())}") + print(f"Batched training XTWX shape: {XTWX.shape} (n_folds, K, K)") + print(f"Batched training XTWY shape: {XTWY.shape} (n_folds, K, M)") + print(f"Batched training X mean shape: {X_mean.shape} (n_folds, 1, K)") + + # Cross-check the first fold against the numpy backend (identical results). + cvm_np = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, backend="numpy" + ) + cvm_np.fit(X, Y, weights) + first_fold = next(iter(p.folds_dict)) + (XTWX_np, XTWY_np), _ = cvm_np.training_XTX_XTY(p.get_validation_indices(first_fold)) + print( + "Max |jax - numpy| for fold " + f"{first_fold!r}: XTWX={np.max(np.abs(np.asarray(XTWX[0]) - XTWX_np)):.2e}, " + f"XTWY={np.max(np.abs(np.asarray(XTWY[0]) - XTWY_np)):.2e}" + ) diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 6054a26..0000000 --- a/poetry.lock +++ /dev/null @@ -1,87 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "numpy" -version = "2.4.0" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.11" -files = [ - {file = "numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e"}, - {file = "numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db"}, - {file = "numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b"}, - {file = "numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7"}, - {file = "numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548"}, - {file = "numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346"}, - {file = "numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25"}, - {file = "numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a"}, - {file = "numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53"}, - {file = "numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479"}, - {file = "numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6"}, - {file = "numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037"}, - {file = "numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83"}, - {file = "numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344"}, - {file = "numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6"}, - {file = "numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb"}, - {file = "numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63"}, - {file = "numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95"}, - {file = "numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6"}, - {file = "numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c"}, - {file = "numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98"}, - {file = "numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667"}, - {file = "numpy-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a152d86a3ae00ba5f47b3acf3b827509fd0b6cb7d3259665e63dafbad22a75ea"}, - {file = "numpy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39b19251dec4de8ff8496cd0806cbe27bf0684f765abb1f4809554de93785f2d"}, - {file = "numpy-2.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:009bd0ea12d3c784b6639a8457537016ce5172109e585338e11334f6a7bb88ee"}, - {file = "numpy-2.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5fe44e277225fd3dff6882d86d3d447205d43532c3627313d17e754fb3905a0e"}, - {file = "numpy-2.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f935c4493eda9069851058fa0d9e39dbf6286be690066509305e52912714dbb2"}, - {file = "numpy-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cfa5f29a695cb7438965e6c3e8d06e0416060cf0d709c1b1c1653a939bf5c2a"}, - {file = "numpy-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba0cb30acd3ef11c94dc27fbfba68940652492bc107075e7ffe23057f9425681"}, - {file = "numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60e8c196cd82cbbd4f130b5290007e13e6de3eca79f0d4d38014769d96a7c475"}, - {file = "numpy-2.4.0-cp313-cp313-win32.whl", hash = "sha256:5f48cb3e88fbc294dc90e215d86fbaf1c852c63dbdb6c3a3e63f45c4b57f7344"}, - {file = "numpy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a899699294f28f7be8992853c0c60741f16ff199205e2e6cdca155762cbaa59d"}, - {file = "numpy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9198f447e1dc5647d07c9a6bbe2063cc0132728cc7175b39dbc796da5b54920d"}, - {file = "numpy-2.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74623f2ab5cc3f7c886add4f735d1031a1d2be4a4ae63c0546cfd74e7a31ddf6"}, - {file = "numpy-2.4.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0804a8e4ab070d1d35496e65ffd3cf8114c136a2b81f61dfab0de4b218aacfd5"}, - {file = "numpy-2.4.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:02a2038eb27f9443a8b266a66911e926566b5a6ffd1a689b588f7f35b81e7dc3"}, - {file = "numpy-2.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1889b3a3f47a7b5bee16bc25a2145bd7cb91897f815ce3499db64c7458b6d91d"}, - {file = "numpy-2.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85eef4cb5625c47ee6425c58a3502555e10f45ee973da878ac8248ad58c136f3"}, - {file = "numpy-2.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6dc8b7e2f4eb184b37655195f421836cfae6f58197b67e3ffc501f1333d993fa"}, - {file = "numpy-2.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:44aba2f0cafd287871a495fb3163408b0bd25bbce135c6f621534a07f4f7875c"}, - {file = "numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93"}, - {file = "numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4"}, - {file = "numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c"}, - {file = "numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b"}, - {file = "numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e"}, - {file = "numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e"}, - {file = "numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51"}, - {file = "numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce"}, - {file = "numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f"}, - {file = "numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded"}, - {file = "numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059"}, - {file = "numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db"}, - {file = "numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e"}, - {file = "numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63"}, - {file = "numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df"}, - {file = "numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9"}, - {file = "numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9"}, - {file = "numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471"}, - {file = "numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544"}, - {file = "numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c"}, - {file = "numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac"}, - {file = "numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f"}, - {file = "numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4"}, - {file = "numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98"}, - {file = "numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b"}, - {file = "numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934"}, -] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.11, <3.15" -content-hash = "cebd365999c19877d1f150434d37f379d33e6b0becd4c88c452ce71dfdf124d1" diff --git a/pyproject.toml b/pyproject.toml index cc49a10..72e57ba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,27 +1,47 @@ -[tool.poetry] +[project] name = "cvmatrix" -version = "3.1.6" +version = "3.2.0" description = "Fast computation of possibly weighted and possibly centered/scaled training set kernel matrices in a cross-validation setting." -authors = ["Sm00thix "] -maintainers = ["Sm00thix "] +authors = [{ name = "Sm00thix", email = "oleemail@icloud.com" }] +maintainers = [{ name = "Sm00thix", email = "oleemail@icloud.com" }] license = "Apache-2.0" readme = "README.md" -homepage = "https://cvmatrix.readthedocs.io/en/latest/" -repository = "https://github.com/Sm00thix/CVMatrix" +requires-python = ">=3.11, <3.15" classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] +dependencies = [ + "numpy>=2.0.0,<3.0.0", +] + +[project.optional-dependencies] +# Optional JAX backend (backend="jax"): enables tracing the per-fold training-matrix +# computations under jax.jit/jax.vmap on CPU/GPU/TPU. Install with `cvmatrix[jax]`. +jax = ["jax>=0.4.0"] + +[project.urls] +Homepage = "https://cvmatrix.readthedocs.io/en/latest/" +Repository = "https://github.com/Sm00thix/CVMatrix" -[tool.poetry.dependencies] -python = ">=3.11, <3.15" -numpy = "^2.0.0" +# Development/test-only dependencies (PEP 735). Installed by `uv sync`. +[dependency-groups] +dev = [ + "pandas", + "pytest", + "pytest-cov", + "typeguard", + "ruff", +] [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["cvmatrix"] [tool.pytest.ini_options] addopts = [ @@ -30,4 +50,4 @@ addopts = [ "--cov-report=xml", "--cov-report=html", "--cov-report=term-missing", -] \ No newline at end of file +] diff --git a/tests/test_cvmatrix.py b/tests/test_cvmatrix.py index 5cdc929..3e067a6 100644 --- a/tests/test_cvmatrix.py +++ b/tests/test_cvmatrix.py @@ -59,6 +59,18 @@ class tests for equivalency between the naive, straight-forward algorithms # ones_weights = ones_weights[indices] # random_weights = random_weights[indices] + @pytest.fixture(params=["numpy", "jax"], autouse=True) + def _set_backend(self, request): + """ + Run every test with both the "numpy" and "jax" CVMatrix backends. The jax + parametrization is skipped if JAX is not installed. `fit_fast` builds the + CVMatrix model with `backend=self.backend`, so all equivalence and behavior + tests exercise both backends. + """ + if request.param == "jax": + pytest.importorskip("jax") + self.backend = request.param + def load_X(self) -> npt.NDArray[np.float64]: """ Loads the raw spectral data. @@ -286,7 +298,10 @@ def fit_fast( made. If `True` a copy is always made. If no copy is made, then external modifications to `X` or `Y` will result in undefined behavior. """ - fast = CVMatrix(center_X, center_Y, scale_X, scale_Y, ddof, dtype, copy) + fast = CVMatrix( + center_X, center_Y, scale_X, scale_Y, ddof, dtype, copy, + backend=getattr(self, "backend", "numpy"), + ) fast.fit(X, Y, weights) return fast @@ -559,6 +574,114 @@ def test_all_preprocessing_combinations(self): ) self.check_equivalent_matrices(naive, fast, p) + def test_jax_backend_requires_jax(self, monkeypatch): + """ + Tests that requesting backend="jax" raises ImportError with an install hint when + JAX is not installed. JAX absence is simulated by hiding it from `sys.modules`, + so this test is valid regardless of whether JAX is installed in the environment. + """ + monkeypatch.setitem(sys.modules, "jax", None) + monkeypatch.setitem(sys.modules, "jax.numpy", None) + with pytest.raises(ImportError, match="cvmatrix\\[jax\\]"): + CVMatrix(backend="jax") + + def test_invalid_backend(self): + """ + Tests that an unknown backend string raises ValueError (the sibling of the + ImportError path tested in `test_jax_backend_requires_jax`). Exercised via + `_resolve_backend` directly: the constructor's `Literal["numpy", "jax"]` hint is + intercepted by typeguard before the ValueError is reached, whereas + `_resolve_backend(backend: str)` accepts the str and reaches the real check. + """ + from cvmatrix.cvmatrix import _resolve_backend + + with pytest.raises(ValueError, match="Invalid backend"): + _resolve_backend("torch") + + def test_jax_jit_vmap_matches_numpy(self): + """ + Exercises the JAX backend under jax.jit + jax.vmap (the headline feature and the + trace-safety machinery: the deferred degenerate-fold checks and the xp.maximum/ + xp.where standard-deviation clamp all run under tracing here). Batches the per- + fold training_XTX_XTY over equal-size folds with jax.vmap and checks the results + match the eager numpy backend, for both unweighted and weighted folds. The + unweighted case specifically covers the path where the training-set count is a + static int rather than a tracer. + """ + if getattr(self, "backend", "numpy") != "jax": + pytest.skip("JAX-specific trace test; runs under the jax parametrization.") + pytest.importorskip("jax") + import jax + import jax.numpy as jnp + + X = self.load_X()[:, :20] + Y = self.load_Y(["Protein", "Moisture"]) + # Equal-size folds so jax.vmap sees a fixed (n_folds, fold_size) index batch. + n_folds = 5 + n = (X.shape[0] // n_folds) * n_folds + X, Y = X[:n], Y[:n] + folds = np.arange(n) % n_folds + p = Partitioner(folds) + val_index_batch = jnp.asarray( + np.stack([p.get_validation_indices(f) for f in p.folds_dict]) + ) + + for weights in (None, self.load_weights(random=True)[:n]): + cvm_jax = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, + ddof=1, backend="jax", + ) + cvm_jax.fit(X, Y, weights) + cvm_np = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, + ddof=1, backend="numpy", + ) + cvm_np.fit(X, Y, weights) + (XTWX_b, XTWY_b), (Xm_b, Xs_b, Ym_b, Ys_b) = jax.jit( + jax.vmap(cvm_jax.training_XTX_XTY) + )(val_index_batch) + for i, fold in enumerate(p.folds_dict): + (XTWX, XTWY), (Xm, Xs, Ym, Ys) = cvm_np.training_XTX_XTY( + p.get_validation_indices(fold) + ) + assert_allclose(np.asarray(XTWX_b[i]), XTWX, atol=1e-8, rtol=1e-8) + assert_allclose(np.asarray(XTWY_b[i]), XTWY, atol=1e-8, rtol=1e-8) + assert_allclose(np.asarray(Xm_b[i]), Xm, atol=1e-8, rtol=1e-8) + assert_allclose(np.asarray(Xs_b[i]), Xs, atol=1e-8, rtol=1e-8) + assert_allclose(np.asarray(Ym_b[i]), Ym, atol=1e-8, rtol=1e-8) + assert_allclose(np.asarray(Ys_b[i]), Ys, atol=1e-8, rtol=1e-8) + + def test_jax_trace_defers_degenerate_check(self): + """ + Under jax.jit/jax.vmap, the data-dependent degenerate-fold ValueError must be + skipped (deferred to a host-side pre-flight); eager execution must still raise. + This guards the UNWEIGHTED path specifically, where the training-set count is a + static int (not a tracer) and so the skip must key on the input `val_indices` + being a tracer rather than on the count. + """ + if getattr(self, "backend", "numpy") != "jax": + pytest.skip("JAX-specific trace test; runs under the jax parametrization.") + pytest.importorskip("jax") + import jax + import jax.numpy as jnp + + X = self.load_X()[:6, :4] + Y = self.load_Y(["Protein"])[:6] + cvm = CVMatrix( + center_X=True, center_Y=True, scale_X=True, scale_Y=True, + ddof=1, backend="jax", + ) + cvm.fit(X, Y, None) # unweighted + # A degenerate fold: validating on 5 of 6 rows leaves 1 training sample (<= ddof). + degenerate_val = np.arange(5) + # Eager (concrete) execution validates and raises. + with pytest.raises(ValueError, match="greater than `ddof`"): + cvm.training_XTX_XTY(degenerate_val) + # Under jit, val_indices is a tracer: the check is deferred, so tracing must + # complete without raising (the degenerate std degrades via the clamp). + (XTWX, _), _ = jax.jit(cvm.training_XTX_XTY)(jnp.asarray(degenerate_val)) + assert XTWX.shape == (4, 4) + def test_naive_hadamard_vs_matmul(self): """ Tests if the NaiveCVMatrix model gives the same result when using Hadamard @@ -1026,6 +1149,11 @@ def test_dtype(self): Tests that different dtypes can be used and that the output preserves the dtype. """ + if getattr(self, "backend", "numpy") == "jax": + pytest.skip( + "NumPy-specific dtype coverage (incl. float16/float128); the jax backend " + "supports only float32/float64." + ) X = np.array([1, 2, 3, 4, 5]) Y = np.array([5, 4, 3, 2, 1]) folds = np.array([0, 0, 1, 1, 2]) @@ -1080,6 +1208,11 @@ def test_copy(self): """ Tests that the copy parameter works as expected. """ + if getattr(self, "backend", "numpy") == "jax": + pytest.skip( + "`copy`/`np.shares_memory` are NumPy-specific; the jax backend always " + "transfers inputs to device arrays." + ) dtype = np.float64 X = np.array([1, 2, 3, 4, 5]).astype(dtype) Y = np.array([5, 4, 3, 2, 1]).astype(dtype) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..7d0e429 --- /dev/null +++ b/uv.lock @@ -0,0 +1,800 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.15" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] + +[[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 = "coverage" +version = "7.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/91/0a7c28934e50d8ac9a7b117712d176f2953c3170bccced5eaacfa3e96175/coverage-7.14.3.tar.gz", hash = "sha256:1a7563a443f3d53fdeb040ec8c9f7466aed7ca3dc5891aa09d3ca3625fa4387f", size = 924398, upload-time = "2026-06-22T23:10:25.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/24/efb17eb94018dd3415d0e8a76a4786a866e8964aa9c50f033399d23939c2/coverage-7.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e574801e1d643561594aa021206c46d80b257e9853087090ba97bed8b0a509d3", size = 220501, upload-time = "2026-06-22T23:08:02.182Z" }, + { url = "https://files.pythonhosted.org/packages/76/93/32f1bfca6cdd34259c8af42820a034b7a28dfb44969a13ed38c17e0ba5b0/coverage-7.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f82b6bb7d75a2613e85d07cefa3a8c973d0544a8993337f6e2728e4a1e94c305", size = 221008, upload-time = "2026-06-22T23:08:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/eb/88/0d0f974855ff905d15a64f7873d00bdc4182e2736267486c6634f4af293c/coverage-7.14.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2335ea5fed26af2e831094964fa3f8fae60b45f7e37fcc2d3b615b2add3ad87", size = 251420, upload-time = "2026-06-22T23:08:05.211Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/117dd2ec65e4140576f8ef991d88220f9b806769f7a8c20e0550c0f924e2/coverage-7.14.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fbb8c3a98e779013786ae01d229662aeacbc77100efbd3f2f245219ace5af700", size = 253331, upload-time = "2026-06-22T23:08:06.672Z" }, + { url = "https://files.pythonhosted.org/packages/87/55/f0bd6d6538e3f16829fb8a44b6c0d2fe9da638bbfdd6a20f8b5da8f4fa81/coverage-7.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac082660de8f429ba0ea363595abb838998570b9a7546777c60f413ab902bbde", size = 255441, upload-time = "2026-06-22T23:08:08.208Z" }, + { url = "https://files.pythonhosted.org/packages/1e/98/aa71f7879019c846a8a9662579ea4484b0202cf1e252ffeed647075e7eca/coverage-7.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac012839ff7e396030f1e94e10553a431d14e4de2ab65cb3acb72bbd5628ca2", size = 257398, upload-time = "2026-06-22T23:08:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/4f/5fd367e59844190f5965015d7bee899e67a89d13eb2760118479bf836f2f/coverage-7.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5952f8c1bda2a5347154450379316e6dfa4d934d62ca35f6784451e6f55074fb", size = 251558, upload-time = "2026-06-22T23:08:11.37Z" }, + { url = "https://files.pythonhosted.org/packages/8f/de/5383a6ee5a6376701fe07d980fa8e4a66c0c377fead16712720340d701a3/coverage-7.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8cf0f2509acb4619e2471a1951089054dd58ebea7a912066d2ea56dd4c24ca4a", size = 253134, upload-time = "2026-06-22T23:08:13.04Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/09542b1a99f788e3daec7f0fadc288821e71aca9ea298d51bfa1ba79fed5/coverage-7.14.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2e41fd3aab806770008279a93879b0924b16247e09ab537c043d08bbca53b4ab", size = 251195, upload-time = "2026-06-22T23:08:14.606Z" }, + { url = "https://files.pythonhosted.org/packages/02/9d/722fe8c13f0fbb064491b9e8656e56a606286792e5068c47ca1042e773e8/coverage-7.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a47095963cfe054e0df178daca95aec21e680d6076da807c3add28dfe920f7", size = 254959, upload-time = "2026-06-22T23:08:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/fb/58/943627179ff1d82da9e54d0a5b0bb907bb19cf19515599ccd921de50b469/coverage-7.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a090cbf9521e78ffdb2fcf448b72902afe9f5923ff6a12d5c0d0120200348af9", size = 250914, upload-time = "2026-06-22T23:08:18.03Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d4/803efcbf9ae5567454a0c71e983589529448e2704ee0da2dc0163d482f18/coverage-7.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d310baf69a4fbe8a098ce727e4808a34866ac718a6f759ae659cbd3221358bc", size = 251824, upload-time = "2026-06-22T23:08:19.704Z" }, + { url = "https://files.pythonhosted.org/packages/32/79/3f78ea9563132746eed5cecb75d2e576f9d8fec45a47242b5ae0950b82a3/coverage-7.14.3-cp311-cp311-win32.whl", hash = "sha256:74fdd718d88fe144f4579b8747873a07ec3f04cb837d5faec5a25d9e22fa31a8", size = 222594, upload-time = "2026-06-22T23:08:21.311Z" }, + { url = "https://files.pythonhosted.org/packages/85/22/9ebbc5a2ab42ac5d0eea1f48648629e1de9bbe41ec243ed6b93d55a5a53f/coverage-7.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:cc96aa922e21d4bc5d5ed3c915cef27dfcbc13686f47d5e378d647fbfba655a2", size = 223073, upload-time = "2026-06-22T23:08:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/71/af/69d5fcc16cb555153f99cec5467922f226be0369f7335a9506856d2a7bd0/coverage-7.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:c66f9f9d4f1e9712eb9b1de5310f881d4e2188cfcba5065e1a8490f38687f2c4", size = 222617, upload-time = "2026-06-22T23:08:25.054Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b0/8a911f6ffe6974dac4df95b468ab9a2899d0e59f0f99a489afeec39f00bc/coverage-7.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d74ff26299c4879ce3a4d826f9d3d4d556fd285fde7bbce3c0ef5a8ab1cec24", size = 220672, upload-time = "2026-06-22T23:08:26.621Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/0fc0cb52538783dbbae0934b834f5a58fd5354380ee6cad4a07b15dc845d/coverage-7.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:96150a9cf3468ea20f0bc5d0e21b3df8972c31480ef90fa7614b773cc6429665", size = 221035, upload-time = "2026-06-22T23:08:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/77/e2/421ccfbb48335ac49e93301478cf5d623b0c2bf1c0cadd8e2b2fc6c0c710/coverage-7.14.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27d07a46500ba23515b838dbcf52512026af04090755cf6cc64166d88c9b9a1a", size = 252540, upload-time = "2026-06-22T23:08:30.226Z" }, + { url = "https://files.pythonhosted.org/packages/06/c2/05b8c890097c61a7f4406b35396b997a635200ded0339eda83dfbe526c5f/coverage-7.14.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:621e13c6108234d7960aaf5762ab5c3c00f33c30c15af06dcbff0c73bf112727", size = 255274, upload-time = "2026-06-22T23:08:31.876Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/b6d9efe447f8ba3c3c854195f326bd64c54b907d936cd2fdebf8767ec72e/coverage-7.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b60ca6d8af70473491a15a343cbabab2e8f9ea66a4376e81c7aa24876a6f977", size = 256389, upload-time = "2026-06-22T23:08:33.843Z" }, + { url = "https://files.pythonhosted.org/packages/d4/3c/f26e50acc429e608bc534ac06f0a3c169019c798178ec5e9de3dbc0df9c9/coverage-7.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c90a7cdd5e380e1ce02f19792e2ac2fbfbf177e35a27e69fd3e873b30d895c0c", size = 258648, upload-time = "2026-06-22T23:08:35.481Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a2/01c1fabf816c8e1dae197e258edf878a3d3ddc86fbda34b76e5794277d8f/coverage-7.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d788e5fd55347eef06ca0732c77d04a264de67e8ff24631270cdff3767a60cf", size = 252949, upload-time = "2026-06-22T23:08:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/89/c6/941166dd79c31fd44a13063780ae8d552eee0089a0a0930b9bdb7df554ed/coverage-7.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62c7f79db2851c95ef020e5d28b97afde3daf9f7febcd35b53e05638f729063f", size = 254310, upload-time = "2026-06-22T23:08:39.174Z" }, + { url = "https://files.pythonhosted.org/packages/10/31/80b1fd028201a961033ce95be3cd1e39e521b3762e6b4a1ac1616cb291e7/coverage-7.14.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:90f7608aeb5d9b60b523b9fb2a4ee1973867cc4865a3f26fe6c7577073b70205", size = 252453, upload-time = "2026-06-22T23:08:40.84Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/c3d9addd94c4b524f3f4af0232075f5fe7170ce99a1386edff803e5934db/coverage-7.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1e3b91f9c4740aeb571ecf82e5e8d8e4ab62d34fcb5a5d4e5baa38c6f7d2857c", size = 256522, upload-time = "2026-06-22T23:08:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/14/e5a0575f73795af3a7a9ae13dadf812e17d32422896839987dc3f86947e1/coverage-7.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c946099774a7699de03cbd0ff0a64e21aed4525eed9d959adde4afe6d15758ef", size = 252023, upload-time = "2026-06-22T23:08:44.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/9b/9652ee531937ce3b8a63a8896885b2b4a2d56adc30e53c9540c666286d88/coverage-7.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:16b206e521feb8b7133a45754643dead0538489cf8b783b90cf5f4e3299625fd", size = 253893, upload-time = "2026-06-22T23:08:46.113Z" }, + { url = "https://files.pythonhosted.org/packages/b1/05/42678841c8c38e4b08bdfc48269f5a16dfbf5806000fe6a89b4cece3c691/coverage-7.14.3-cp312-cp312-win32.whl", hash = "sha256:ea3169c7116eb6cdf7608c6c7da9ecfcb3da40688e3a510fac2d1d2bafd6dc35", size = 222734, upload-time = "2026-06-22T23:08:47.858Z" }, + { url = "https://files.pythonhosted.org/packages/df/87/07a4fcee55177a25f1b52331a8e92cf4f2c53b1a9c75ce2981fd59c684ad/coverage-7.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:7ea52fc08f007bcc494d4bb3df3851e95843d881860ba38fe2c64dc100db5e7d", size = 223266, upload-time = "2026-06-22T23:08:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/aa/34/2b8b66a989282ea7b370beb49f50bab29470dc30bb0b03935b6b802782f7/coverage-7.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:8cec0ad652ec57790970d817490105bd917d783c2f7b38d6b58a0ca312e1a336", size = 222655, upload-time = "2026-06-22T23:08:51.766Z" }, + { url = "https://files.pythonhosted.org/packages/a9/83/7fefbf5df23ed2b7f489907564a7b34b9b07098128e12e0fdfa92626e456/coverage-7.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47968988b367990ae4ab17523790c38cd125e02c6bfd379b6022be2d40bdc38c", size = 220699, upload-time = "2026-06-22T23:08:53.522Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/38c3653ff6d56d704b29241362387ca824e38e15b76fdcb7096538195790/coverage-7.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0ee68f5c34812780f3a7063382c0a9fcbb99985b7ddcdcaa626e4f3fb2e0783a", size = 221068, upload-time = "2026-06-22T23:08:55.571Z" }, + { url = "https://files.pythonhosted.org/packages/20/86/4f5c45d51c5cd10a128933f0fd235393c9146abbfd2ce2dfa68b3267ead3/coverage-7.14.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fa9e5c6857a7e80fa22ace5cf3550ae392bbfc322f1d8dd2d2d5a8be38cec027", size = 252060, upload-time = "2026-06-22T23:08:57.464Z" }, + { url = "https://files.pythonhosted.org/packages/82/50/dfce42eff2cecabcd5a9bbad5489449c87db3415f408d23ffee417ce01f6/coverage-7.14.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:98a0859b0e98e43e1178a9402e19c8127766b14f7109a374d976e5a62c0e5c73", size = 254657, upload-time = "2026-06-22T23:08:59.453Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d2/639ceb1bc8038fd0d66768278d5dc22df3391918b8278c2a21aa2602a531/coverage-7.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69918344541ed9c8368566c2adc03c0e33d4550d7faa87d1b35e49b6a3286ea9", size = 255892, upload-time = "2026-06-22T23:09:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/8b/96/002094a10e113512500dc1e10430a449417e17b0f90f7d496bcb820208b7/coverage-7.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f300ac92cd4b570724c8ffbbd0c130fee298d2447f41d5a3abf58976fae1de", size = 258026, upload-time = "2026-06-22T23:09:03.017Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ec/286a5d2fad9c4bee59bd724feeb7d5bf8303c6c9200b51d1dd945a9c72b0/coverage-7.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11a7ec9f97ab950f4c5af62229befc7faf208fdbc0116d3902d7e306cf2c5abd", size = 252285, upload-time = "2026-06-22T23:09:04.773Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/a17753a0b12dd48d0d50f5fab079ad99d3be1eac790494d89f3a417ca0b9/coverage-7.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a571bd889cd36c5922ce8e42e059f9d37d02301531d11374afa4c87a578625d5", size = 254023, upload-time = "2026-06-22T23:09:06.513Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/a76c6ceba6a2c313f905310abf2701d534cada22d372db11731831e9e209/coverage-7.14.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:de76caefc8deabb0dd1678b6a980be97d14c8d87e213ac194dbf8b09e96d63fb", size = 251989, upload-time = "2026-06-22T23:09:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/d9/39/353013a75fec0fb49f7553519f9d52b4441e902e5178c93f38eb6c07cedb/coverage-7.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d20a15c622194234161535459affa8f7905830391c9ccfa060d495dbfe3a1c7f", size = 256144, upload-time = "2026-06-22T23:09:10.369Z" }, + { url = "https://files.pythonhosted.org/packages/29/0e/613878555d734def11c5b20a2701a15cb3781b9e9ea749da27c5f436e928/coverage-7.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b488bd4b23397db62e7a9459129d01ff06a846582a732efd24834b24a6ada498", size = 251808, upload-time = "2026-06-22T23:09:12.057Z" }, + { url = "https://files.pythonhosted.org/packages/af/76/359c058c9cfdcf1e8b107663881225b03b364a320017eda24a2a66e55102/coverage-7.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a3693b4153394d265f44fb855fdc80e72403024d4d6f91c4871b334d028e4e0", size = 253579, upload-time = "2026-06-22T23:09:13.858Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d9/4ba2f060933a30ebe363cef9f67a365b0a317e580c0d5d9169d56a73ef1c/coverage-7.14.3-cp313-cp313-win32.whl", hash = "sha256:338b19131ab1a6b767b462bfcbaa692e7ae22f24463e39d49b02a83410ff6b37", size = 222741, upload-time = "2026-06-22T23:09:15.636Z" }, + { url = "https://files.pythonhosted.org/packages/76/e8/196ebc25d8f34c06d43a6e9c8513c9266ef8dbf3b5672beb1a00cf5e29fa/coverage-7.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:b3d77f7f196abdef7e01415de1bce09f216189e83e58159cfeef2b92d0464994", size = 223283, upload-time = "2026-06-22T23:09:17.478Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/51d2aac6417523a286f10fb25f09eb9518a84df9f1151e93ff6871f34849/coverage-7.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:e6230e688c7c3e65cedd41a774eb4ec221adc6bfee13768231015b702d5e4150", size = 222678, upload-time = "2026-06-22T23:09:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/61/56/14e3b97facbfa1304dd19e676e26599ad359f04714bed32f7f1c5a88efdc/coverage-7.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:605ab2b566a22bd94834529d66d295c364aba84afd3e5498285c7a524017b1fc", size = 220741, upload-time = "2026-06-22T23:09:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/12/1d/db378b5cca433b90b893f26dab728b280ddd89f272a1fdfed4aeaa05c686/coverage-7.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3c2134809e80fac091bfed18a6991b5a5eb5df5ae32b17ac4f4f99864b73dd7", size = 221068, upload-time = "2026-06-22T23:09:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/47/f0/3f8421b20d9c4fcd39be9a8ca3c3fda8bc204b44efbd09fede153afd3e2f/coverage-7.14.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c02efd507227bde9969cab0db8f48890eb3b5dcad6afac57a4792df4133543ce", size = 252117, upload-time = "2026-06-22T23:09:25.458Z" }, + { url = "https://files.pythonhosted.org/packages/27/ca/59ea35fb99743549ec8b37eff141ece4431fea590c89e536ed8032ef45cf/coverage-7.14.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1bb93c2aa61d2a5b38f1526546d95cf4132cb681e541a337bf8dfd092be816e5", size = 254622, upload-time = "2026-06-22T23:09:27.523Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/ec6de51ae7493b92a1cf74d1b763121c29636759167e2a593ba4db5881e4/coverage-7.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f502e948e03e866538048bba081c075caaa62e5bda6ea5b7432e45f587eb462a", size = 255968, upload-time = "2026-06-22T23:09:29.43Z" }, + { url = "https://files.pythonhosted.org/packages/5d/05/c8bfc77823f42b4664fb25842f13b567022f6f84a4c83c8ecbb16734b7cb/coverage-7.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9973ef2463f8e6cfb61a6324126bb3e17d67a85f22f58d856e583ea2e3ca6501", size = 258284, upload-time = "2026-06-22T23:09:31.397Z" }, + { url = "https://files.pythonhosted.org/packages/f6/15/1d1b242027124a32b26ef01f82018b8c4ef34ef174aa6aeba7b1eeef48e8/coverage-7.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9be4e7d4c5ca0427889f8f9d614bd630c2be741b1de7699bca3b2b6c0e41003e", size = 252143, upload-time = "2026-06-22T23:09:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/d2a9842fd2a5d7d27f1ac851c043a734a494ad75402c5331db3da79ed691/coverage-7.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a574912f3bde4b0619f6e97d01aa590b70998859244793769eb3a6df78ee56d3", size = 253976, upload-time = "2026-06-22T23:09:35.351Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/e1600ddf7e226db5558bb5323d2186fff00f505c4b764643ec89ce5d8175/coverage-7.14.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e343fb086c9cd780b38622fea7c369acd64c1a0724312149b5d769c387a2b1f5", size = 251942, upload-time = "2026-06-22T23:09:37.313Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2c/9159de64f9dd648e324328d588a44cfab1e331eb5259ce1141afe2a92dfb/coverage-7.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3c68df8e61f1e09633fefc7538297145623957a048534368c9d212782aa5e845", size = 256220, upload-time = "2026-06-22T23:09:39.165Z" }, + { url = "https://files.pythonhosted.org/packages/91/67/b7f536cc2c124f48e91b22fbb741d2261f4e3d310faf6f76007f47566e5d/coverage-7.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3e5b550a128419373c2f6cec28a244207013ef15f5cbcff6a5ca09d1dfaaf027", size = 251756, upload-time = "2026-06-22T23:09:41.056Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f3718038e2d4860c715a55428377ca7f6c75872caf98cabd982e1d76967d/coverage-7.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2bfc4dd0a912329eccc7484a7d0b2a38032b38c40663b1e1ac595f10c457954b", size = 253413, upload-time = "2026-06-22T23:09:43.306Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a5/91f11efeef89b3cc9b30461128db15b0511ef813ab889a7b7ab636b3a497/coverage-7.14.3-cp314-cp314-win32.whl", hash = "sha256:0423d64c013057a06e70f070f073cec4b0cbc7d2b27f3c7007292f2ff1d52965", size = 222946, upload-time = "2026-06-22T23:09:45.261Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/98ac9f524d9ec378de831c034dbdeb544ca7ef7d2d9c9996daf232a037fd/coverage-7.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:92c22e19ce64ca3f2ad751f16f14df1468b4c231bd6af97185063a9c292a0cb3", size = 223436, upload-time = "2026-06-22T23:09:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/7cd612d650a772a0ae80144443406bf61981c896c3d57c9e6e79fb2cdbd1/coverage-7.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:41de778bd41780586e2b04912079c73089ab5d839624e28db3bdb26de638da92", size = 222861, upload-time = "2026-06-22T23:09:49.384Z" }, + { url = "https://files.pythonhosted.org/packages/55/57/017353fab573779c0d00448e47d102edd36c792f7b6f233a4d89a7a08384/coverage-7.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8427f370ca67db4c975d2a26acfc0e5783ca0b52444dbc50278ace0f35445949", size = 221474, upload-time = "2026-06-22T23:09:51.417Z" }, + { url = "https://files.pythonhosted.org/packages/69/92/90cf1f1a5c468a9c1b7ba2716e0e205293ad9b02f5f573a6de4318b15ba1/coverage-7.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d8e88f335544a47e22ae2e45b344772925ec65166555c958720d5ed971880891", size = 221738, upload-time = "2026-06-22T23:09:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/4df964fa539f8399fd7679c09c472d73744de334686fd3f01e3a2465ce4e/coverage-7.14.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:beaab199b9e5ceaf5a225e16a9d4df136f2a1eae0a5c20de1e277c8a5225f388", size = 263101, upload-time = "2026-06-22T23:09:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/06/76/e5d33b2576ae3bf2be2058cd1cae57774b61e400f2c3c58f3783dc2ffb4a/coverage-7.14.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3ff255799f5a1676c71c1c32ec01fd043aa09d57b3d95764b24992757184784", size = 265225, upload-time = "2026-06-22T23:09:57.904Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/e52419afe391a39ba27fdefaf0737d8e34bf03faef6ab3b3006545bbd0d0/coverage-7.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:878832eaac515b62decfa76965aed558775f86bf1fc8cca76993c0c84ae31aed", size = 267643, upload-time = "2026-06-22T23:09:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/58/7a/f2625d8d5006b6b20fba5afaef00b24a763fe96476ea798a3076cbc1f84e/coverage-7.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:611e62cb9386096d81b63e0a05330750268617231e7bd598e1fe77482a2c58a5", size = 268762, upload-time = "2026-06-22T23:10:01.943Z" }, + { url = "https://files.pythonhosted.org/packages/7d/bf/908024006bba57127354d74e938954b9c3cd765cc2e0412dc9c37b415cda/coverage-7.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:02c41de2a88011b893050fc9830267d927a50a215f7ad5ec17349db7090ccf26", size = 262208, upload-time = "2026-06-22T23:10:03.954Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/d4f9296441b909817442fdb26bd77a698f08272ec683a7394b00eb2e47a0/coverage-7.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:526ce9721116af23b1065089f0b75046fe521e7772ab94b641cd66b7a0421889", size = 265096, upload-time = "2026-06-22T23:10:05.936Z" }, + { url = "https://files.pythonhosted.org/packages/e8/da/4ae4f3f4e477b56a4ce1e5c48a35eff38a94b50130ce5bdc897024741cfc/coverage-7.14.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e4ed44705ca4bead6fc977a8b741f2145608289b33c8a9b42a95d0f15aedbf4d", size = 262699, upload-time = "2026-06-22T23:10:07.973Z" }, + { url = "https://files.pythonhosted.org/packages/d8/7a/6927148073ff32856d78baa77b4ddc07a9be7e90020f9db0661c4ca523a1/coverage-7.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2415902f385a23dcc4ccd26e0ba803249a169af6a930c003a4c715eeb9a5444e", size = 266433, upload-time = "2026-06-22T23:10:10.145Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a7/774f658dbe9c4c3f5daa86a87e0459ac3832e4e3cc67affe078547f727b9/coverage-7.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b75ee850fc2d7c831e883220c445b035f2224de2ba6103f1e56dbd237ab913f7", size = 261547, upload-time = "2026-06-22T23:10:12.191Z" }, + { url = "https://files.pythonhosted.org/packages/3d/14/a0c18c0376c43cbf973f43ef6ca20019c950597180e6396232f7b6a27102/coverage-7.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dc9b4e35e7c3920e925ba7f14886fd5fbe481232754624e832ddba66c7535635", size = 263859, upload-time = "2026-06-22T23:10:14.492Z" }, + { url = "https://files.pythonhosted.org/packages/10/ac/43a3d0f460af524b131a6191805bc5d18b806ab4e828fbf82e8c8c3af446/coverage-7.14.3-cp314-cp314t-win32.whl", hash = "sha256:7b27c822a8161afbe48e99f1adfb098d270ae7e0f7d7b0555ce110529bdb69cc", size = 223250, upload-time = "2026-06-22T23:10:16.758Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5f/d5e5c56b0712e96ce8f69fe7dbf229ff938b437bc50862743c8a0d2cea84/coverage-7.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:39e1dbbb6ff2c338e0196a482558a792a1de3aa64261196f5cdb3da016ad9cda", size = 224082, upload-time = "2026-06-22T23:10:19.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/35/947cbd5be1d3bcbbdc43d6791de8a56c6501903311d42915ae06a82815f0/coverage-7.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:68520c90babfa2d560eca6d497921ed3a4f469623bd709733124491b2aa8ef3f", size = 223400, upload-time = "2026-06-22T23:10:21.24Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e3/a0aa32bfa3a081951f60a23bc0e7b512891ef0eecda1153cf1d8ba36c6b1/coverage-7.14.3-py3-none-any.whl", hash = "sha256:fb7e18afb6e903c1a92401a2f0501ac277dca527bb9ca6fe1f691a8a0026a0e8", size = 212469, upload-time = "2026-06-22T23:10:23.405Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cvmatrix" +version = "3.2.0" +source = { editable = "." } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] + +[package.optional-dependencies] +jax = [ + { name = "jax" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pandas" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "typeguard" }, +] + +[package.metadata] +requires-dist = [ + { name = "jax", marker = "extra == 'jax'", specifier = ">=0.4.0" }, + { name = "numpy", specifier = ">=2.0.0,<3.0.0" }, +] +provides-extras = ["jax"] + +[package.metadata.requires-dev] +dev = [ + { name = "pandas" }, + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "typeguard" }, +] + +[[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 = "jax" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaxlib" }, + { name = "ml-dtypes" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "opt-einsum" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { 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/d2/73/eb91d98fcadfa2cbcfdd4e417ab116e47eb20882acc5ee678e47c35d6b57/jax-0.10.2.tar.gz", hash = "sha256:bf77428a8c2e6904c4f46d5ab12aa5cfc6cad2179f07f7e4c0fc75ac86ef0639", size = 2775110, upload-time = "2026-06-17T23:44:57.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/82/5ab5211079a151b6f661529369c0c8e98ec64cabf5c0cf22a0a05af124d8/jax-0.10.2-py3-none-any.whl", hash = "sha256:724d73c4678d8b06f6a6ab4db1b8a2fea8cd4f1e2c2564f99601634ec7b8d1c6", size = 3219515, upload-time = "2026-06-17T23:42:41.259Z" }, +] + +[[package]] +name = "jaxlib" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { 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/b2/2c/7038fc73154307389631b5b2dbe5ac529e1918eecc19a27e6644ad114bbf/jaxlib-0.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5a98873fc867623b81f2bee15d554b8edd6588a183d01fa50d21b1e3db96ff2b", size = 61429039, upload-time = "2026-06-17T23:43:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/66/c6/d69a0a33046f84930b89387861c061996d5207671b35080898679ca9960a/jaxlib-0.10.2-cp311-cp311-manylinux_2_27_aarch64.whl", hash = "sha256:d44565dcfd1b4f60f76d911c6512118a8a4fc764bdef92663fecb8bfccd54f23", size = 81079180, upload-time = "2026-06-17T23:43:48.245Z" }, + { url = "https://files.pythonhosted.org/packages/e2/27/fb54e3265c0ffcb687f93e9fb761c589acebbe958c3fed1b2c74c3f0e782/jaxlib-0.10.2-cp311-cp311-manylinux_2_27_x86_64.whl", hash = "sha256:1faca3c5d4662cb4a6130a68105d68bb520764817e165d6eebfd6786c0d1f30f", size = 85448560, upload-time = "2026-06-17T23:43:51.724Z" }, + { url = "https://files.pythonhosted.org/packages/21/bc/31fbb3d892c3cb97c73af9226eca63d60d8e224017145bdb6871d1d24da6/jaxlib-0.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e7a9214e6b0b9e0825d905573d1bbf2253c20e9d7464a63e085b60519975553f", size = 65867603, upload-time = "2026-06-17T23:43:54.939Z" }, + { url = "https://files.pythonhosted.org/packages/ca/93/ee9cc8743191544f65d26ab7eeb82d65968fe60905662d1a5554d056654b/jaxlib-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47bb7c011515ea862be7e8313f40f9c56cbec09dc98a0fcb5016785fcd454c01", size = 61434612, upload-time = "2026-06-17T23:43:57.808Z" }, + { url = "https://files.pythonhosted.org/packages/11/06/8cc36021bf74d617c312eeed94c280282bb1bcbb32b63f2a42b10ae41575/jaxlib-0.10.2-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:53b72977ae582c03a9e8e1cdee1efbf8ebc1418270965b0e69eade57acf40331", size = 81085366, upload-time = "2026-06-17T23:44:01.067Z" }, + { url = "https://files.pythonhosted.org/packages/48/17/38b718af2353dba7753300871e83fbb64a88a772e12727ae27373ab675ce/jaxlib-0.10.2-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:fe88ec443714c4379968b6c109f9fa617c7ad19b802828e4d7bf861cd66da4b7", size = 85467828, upload-time = "2026-06-17T23:44:04.238Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/d41d13826ebdfe62e56cd87ba70fab3bb9fcbea4a6c9086739a91667e5bf/jaxlib-0.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:4b08f5fbc596b83f76308181863996f93d901d1f09cfd4e130a65c1998e1b371", size = 65900139, upload-time = "2026-06-17T23:44:07.476Z" }, + { url = "https://files.pythonhosted.org/packages/c2/68/eaa4cebe253359196a8e80a33b242959e27d8d2a6ae3d09339f21da2acb8/jaxlib-0.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4df530afa354a22dc1747a5d560640450cbb895d49889338a3f58c76a4c76c8e", size = 61434805, upload-time = "2026-06-17T23:44:10.511Z" }, + { url = "https://files.pythonhosted.org/packages/25/c1/4b884ea5962b6beb3c0f93742db54246bbf8b3274e48b0aca47908e454be/jaxlib-0.10.2-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:45b28b0238697ab74bbcf20411aafb6db42acc31836cc2fd711e5cf056bf9556", size = 81084260, upload-time = "2026-06-17T23:44:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/4ee28c65861605945223145fbcd3c9362ec2255ddf7d917574e205548c82/jaxlib-0.10.2-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:9e4818b4a8756fd3918766ca2aa5342125809f4f08a6fe46026d4386e7c23644", size = 85467706, upload-time = "2026-06-17T23:44:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/79/54/9918b0f77a25a1299818c0610305ca2bea38ed90584f4489b60357e2dd39/jaxlib-0.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:c75d6f1df1c9cff08e110b4a21c79560fdc502f4288972d6b117d25dafd44352", size = 65897894, upload-time = "2026-06-17T23:44:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/70df11da52a8b1a826184cccc05a3fec8aed76058a980021873fba3069cb/jaxlib-0.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4c202d8ff7c1f3b5049dbd8f1e30e52759cd4e0a5835f0b3c7ae076a05818e28", size = 61564746, upload-time = "2026-06-17T23:44:24.421Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/184a648ea5db6c8b1a08fc5784c157b4c557255e009fb56091393df3c6de/jaxlib-0.10.2-cp313-cp313t-manylinux_2_27_aarch64.whl", hash = "sha256:b7b029bb95d981566750475b9719a9d6b66ed5dd2748851667899b6cfe075299", size = 81204888, upload-time = "2026-06-17T23:44:27.57Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5c/539596a55265711d74147913278bcdc38412980be7d74d9c9d860297c486/jaxlib-0.10.2-cp313-cp313t-manylinux_2_27_x86_64.whl", hash = "sha256:e8b126097d609b0c6e6786e89f6dd6978adc02ebd5f63a1c61293fbac7821305", size = 85583810, upload-time = "2026-06-17T23:44:30.962Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/27471ec9f1d04674f6e62de809412371e097aed3eca7d9483e677c54c214/jaxlib-0.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:72eba28b12fee02616fa42aa4b881b4ab62d7757c7843c462401d3fb34a27be4", size = 61446097, upload-time = "2026-06-17T23:44:34.196Z" }, + { url = "https://files.pythonhosted.org/packages/af/c8/941a7f7f37510f51290a5bd1a413aeef977fb8ba8adc0cfe8391233a764c/jaxlib-0.10.2-cp314-cp314-manylinux_2_27_aarch64.whl", hash = "sha256:f18f56fee90699cfba9b6627045a7a299702cb0e2af82ce180d9a6a7c8048093", size = 81096546, upload-time = "2026-06-17T23:44:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/69/77/ac054882c220872512df28d16aeb648fe0e651efbb5be4fd7c4817fd88b0/jaxlib-0.10.2-cp314-cp314-manylinux_2_27_x86_64.whl", hash = "sha256:ca34f363197fb0ac4082582ca755007910369e33f8a8ba3d35ed94b71070107d", size = 85472993, upload-time = "2026-06-17T23:44:40.904Z" }, + { url = "https://files.pythonhosted.org/packages/0d/7d/c592d1fa69c210be0d2743fffc598dfc2f54efa9671c5f6f5d1e151c6f4a/jaxlib-0.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:99818b0a18adc0b899abf4873795e8d65169441d87ab2e5cbb228e73d0f25808", size = 68376553, upload-time = "2026-06-17T23:44:44.968Z" }, + { url = "https://files.pythonhosted.org/packages/54/9b/91b00ec74985d29708b50420b4103c1f651c8f1c253d4fcb49d1bbb532cd/jaxlib-0.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fc62997fce8831819551a2a5469a818169b09582b5b648c102d11ac7205bb812", size = 61564620, upload-time = "2026-06-17T23:44:48.217Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c7/49d2b19c3b3105c30e1d3af2062e82e1977fb239d3dc3cbb583ef676dda7/jaxlib-0.10.2-cp314-cp314t-manylinux_2_27_aarch64.whl", hash = "sha256:a24d6e3cba263978293eae8b41330d5ccf24d6cdd1a6bcd4e82aff34e767620d", size = 81206365, upload-time = "2026-06-17T23:44:51.419Z" }, + { url = "https://files.pythonhosted.org/packages/bf/99/006cedf443f4a01f2088651facce79b2105bfb4905bfe9162eb0920a6dfb/jaxlib-0.10.2-cp314-cp314t-manylinux_2_27_x86_64.whl", hash = "sha256:5a2ac7aed7c4e661f67600bbcdec9e589151c1efec91f4cdb8d484af1a45c895", size = 85584458, upload-time = "2026-06-17T23:44:55.377Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", 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/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" }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +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/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { 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.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + +[[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 = "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 = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/c321f13b5ba1819fc8dca456c7fce578da2dcfecff1abbf0eaddf8406c0f/pandas-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6674ab18ad8c57802867264b00e15e7bb904700cdd9046e3b2fa1fce237439ea", size = 9907609, upload-time = "2026-05-11T18:52:30.982Z" }, + { url = "https://files.pythonhosted.org/packages/53/85/1b7f563ebc6357c27233a02a96b589bcce1fa9c6eb89fb4f0e56421d277e/pandas-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:5cc09a68b3120e0f54870dede8287a7bb1fa463907e4fcec1ea77cab6179bf7a", size = 9165596, upload-time = "2026-05-11T18:52:33.334Z" }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, + { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, + { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, + { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, + { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, + { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, + { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, + { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, + { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, + { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +] + +[[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 = "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 = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[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 = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, +] +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" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'emscripten'", + "python_full_version == '3.12.*' and sys_platform == 'emscripten'", + "python_full_version == '3.13.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +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" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + +[[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 = "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/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { 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 = "typeguard" +version = "4.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/dfba5c4633cafc4c701f237d2ba63b416805047fd6d96aab4cfc40969f98/typeguard-4.5.2.tar.gz", hash = "sha256:5a16dcac23502039299c97c8941651bc33d7ea8cc4b2f7d6bbb1b528f6eea423", size = 80240, upload-time = "2026-05-14T12:59:40.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl", hash = "sha256:fcf9de18bd945cdb4c7b996e12b4c51ce83f92f191314a6d7cf1739586ec98cf", size = 36748, upload-time = "2026-05-14T12:59:39.473Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +]