diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8cb8cdf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "friday" + + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + day: "friday" diff --git a/.github/workflows/code_style.yml b/.github/workflows/code_style.yml new file mode 100644 index 0000000..ed4e01b --- /dev/null +++ b/.github/workflows/code_style.yml @@ -0,0 +1,35 @@ +name: Lint and Format + +on: + push: + branches: [ master ] + + pull_request: + branches: [ master ] + paths: [ '**/*.py' ] + + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}/${{ github.ref }} + cancel-in-progress: true + +jobs: + python-format: + name: Ruff Lint and Format + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v6 + + - name: Setup Ruff + uses: astral-sh/ruff-action@v3 + with: + args: --version + + - name: Enforce Lint + run: ruff check . + + - name: Enforce Format + run: ruff format --diff . diff --git a/.github/workflows/code_test.yml b/.github/workflows/code_test.yml new file mode 100644 index 0000000..372a1a6 --- /dev/null +++ b/.github/workflows/code_test.yml @@ -0,0 +1,96 @@ +name: Test + +on: + push: + branches: [ master ] + + pull_request: + branches: [ master ] + + workflow_dispatch: + workflow_call: + outputs: + artifact_name: + value: ${{ jobs.fresh_build.outputs.artifact_name }} + artifact_run_id: + value: ${{ jobs.fresh_build.outputs.run_id }} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + + setup: + runs-on: ubuntu-latest + + steps: + - name: Fetch Supported Python Versions + id: fetch-versions + run: | + versions=$(curl -fsSL https://endoflife.date/api/v1/products/python/ \ + | jq '[ .result.releases[] | select(.isEol == false) | .label ]') + + echo "supported_versions=$(echo $versions | jq -c '.')" >> $GITHUB_OUTPUT + echo "bound_versions=$(echo $versions | jq -c '[.[0],.[-1]]')" >> $GITHUB_OUTPUT + + outputs: + supported_versions: ${{ steps.fetch-versions.outputs.supported_versions }} + bound_versions: ${{ steps.fetch-versions.outputs.bound_versions }} + + fresh_build: + uses: ./.github/workflows/dist_build.yml + + testing: + runs-on: ${{ matrix.os }} + + permissions: + id-token: write + + strategy: + matrix: + os: [ ubuntu-latest ] + py-version: ${{ fromJson(needs.setup.outputs.supported_versions) }} + include: + - os: windows-latest + py-version: ${{ fromJson(needs.setup.outputs.bound_versions)[0] }} + - os: windows-latest + py-version: ${{ fromJson(needs.setup.outputs.bound_versions)[1] }} + - os: macos-latest + py-version: ${{ fromJson(needs.setup.outputs.bound_versions)[0] }} + - os: macos-latest + py-version: ${{ fromJson(needs.setup.outputs.bound_versions)[1] }} + + needs: + - fresh_build + - setup + + steps: + - uses: actions/checkout@v6 + + - name: Download Build Artifacts + uses: actions/download-artifact@v8 + with: + name: ${{ needs.fresh_build.outputs.artifact_name }} + run-id: ${{ needs.fresh_build.outputs.run_id }} + path: ./dist + + - uses: astral-sh/setup-uv@v7 + with: + python-version: ${{ matrix.py-version }} + + - name: Install Packages + shell: bash + run: | + uv sync --no-dev --group test + uv pip install -v ./dist/*.whl + + - name: Run Tests + run: uv run --no-sync pytest -v --cov --cov-report=xml tests + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v6 + with: + fail_ci_if_error: true + use_oidc: true + verbose: true diff --git a/.github/workflows/dist_build.yml b/.github/workflows/dist_build.yml new file mode 100644 index 0000000..acc7c2c --- /dev/null +++ b/.github/workflows/dist_build.yml @@ -0,0 +1,40 @@ +name: Build Distribution + +on: + workflow_call: + outputs: + artifact_name: + value: ${{ jobs.build.outputs.artifact_name }} + run_id: + value: ${{ github.run_id }} + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup uv + uses: astral-sh/setup-uv@v7 + + - name: Build distribution + run: uv build -v --clear . + + - name: Set Artifact Name + id: artifact-name + run: | + echo "artifact_name=${{ github.run_id }}[${{ github.run_attempt }}]--build-artifacts" \ + | tee -a $GITHUB_OUTPUT + + - name: Upload Build Artifacts + uses: actions/upload-artifact@v7 + with: + name: ${{ steps.artifact-name.outputs.artifact_name }} + path: dist + if-no-files-found: error + + outputs: + artifact_name: ${{ steps.artifact-name.outputs.artifact_name }} diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml new file mode 100644 index 0000000..d0d0f2e --- /dev/null +++ b/.github/workflows/docs_build.yml @@ -0,0 +1,52 @@ +name: Documentation +permissions: + pages: write + id-token: write + +on: + pull_request: + branches: [ master ] + workflow_call: + inputs: &build-docs-inputs + deploy: + type: boolean + required: false + default: false + + workflow_dispatch: + inputs: *build-docs-inputs + +concurrency: + group: docs-build + +jobs: + + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Setup uv + uses: astral-sh/setup-uv@v7 + + - name: Build Documentation + run: uv run --no-dev --group docs zensical build --clean --strict + + - name: Set Artifact Name + id: artifact-name + run: | + echo "artifact_name=${{ github.run_id }}[${{ github.run_attempt }}]--docs-artifacts" \ + | tee -a $GITHUB_OUTPUT + + - name: Upload Pages Artifacts + uses: actions/upload-pages-artifact@v5 + with: + name: ${{ steps.artifact-name.outputs.artifact_name }} + path: site/ + + - name: Deploy to GitHub Pages + if: ${{ inputs.deploy }} + uses: actions/deploy-pages@v5 + with: + artifact_name: ${{ steps.artifact-name.outputs.artifact_name }} diff --git a/.github/workflows/publish_dist.yml b/.github/workflows/publish_dist.yml new file mode 100644 index 0000000..4f47ba8 --- /dev/null +++ b/.github/workflows/publish_dist.yml @@ -0,0 +1,56 @@ +name: Publish to PyPI + +on: + release: + types: [ published ] + +permissions: + id-token: write + +concurrency: + group: ${{ github.workflow }} + +jobs: + testing: + uses: ./.github/workflows/code_test.yml + + publish-to-pypi: + runs-on: ubuntu-latest + + environment: + name: pypi + + needs: testing + + steps: + + - &download_dist + name: Download Distributions + uses: actions/download-artifact@v8 + with: + name: ${{ needs.testing.outputs.artifact_name }} + run-id: ${{ needs.testing.outputs.artifact_run_id }} + path: ./dist + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + verbose: true + + - name: Smoke Test on PyPI + shell: bash + run: | + sleep 60 + pip install -v --index-url https://pypi.org/simple/ soerp + python -c "import soerp;print(soerp.__version__)" + + publish_docs: + needs: publish-to-pypi + + permissions: + pages: write + id-token: write + + uses: ./.github/workflows/docs_build.yml + with: + deploy: true diff --git a/.gitignore b/.gitignore index 3a15edc..b7faf40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,207 @@ -*.pyc -*.*~ -build -dist +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg MANIFEST -*.egg-info -.idea + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Cursor +# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to +# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data +# refer to https://docs.cursor.com/context/ignore-files +.cursorignore +.cursorindexingignore + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..af3583b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "howcasperwhat.comment-formula" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 089b432..94e6e5b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,22 @@ BSD 3-Clause License Copyright (c) 2010 - 2018, Abraham Lee +Copyright (c) 2026, eggzec All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. -* Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 84676f0..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,323 +0,0 @@ -Metadata-Version: 1.1 -Name: soerp -Version: 0.9.1 -Summary: Second Order ERror Propagation -Home-page: https://github.com/tisimst/soerp -Author: Abraham Lee -Author-email: tisimst@gmail.com -License: BSD License -Description: =============================== - ``soerp`` Package Documentation - =============================== - - Overview - ======== - - ``soerp`` is the Python implementation of the original Fortran code `SOERP` - by N. D. Cox to apply a second-order analysis to `error propagation`_ (or - uncertainty analysis). The ``soerp`` package allows you to **easily** and - **transparently** track the effects of uncertainty through mathematical - calculations. Advanced mathematical functions, similar to those in the standard - math_ module can also be evaluated directly. - - In order to correctly use ``soerp``, the **first eight statistical moments** - of the underlying distribution are required. These are the *mean*, *variance*, - and then the *standardized third through eighth moments*. These can be input - manually in the form of an array, but they can also be **conveniently - generated** using either the **nice constructors** or directly by using the - distributions from the ``scipy.stats`` sub-module. See the examples below for - usage examples of both input methods. The result of all calculations generates a - *mean*, *variance*, and *standardized skewness and kurtosis* coefficients. - - - Required Packages - ================= - - - ad_ : For first- and second-order automatic differentiation (install this first). - - Suggested Packages - ================== - - - NumPy_ : Numeric Python - - - SciPy_ : Scientific Python (the nice distribution constructors require this) - - - Matplotlib_ : Python plotting library - - Basic examples - ============== - - Let's begin by importing all the available constructors:: - - >>> from soerp import * # uv, N, U, Exp, etc. - - Now, we can see that there are several equivalent ways to specify a statistical distribution, say a Normal distribution with a mean value of 10 and a standard deviation of 1: - - - Manually input the first 8 moments (mean, variance, and 3rd-8th standardized central moments):: - - >>> x = uv([10, 1, 0, 3, 0, 15, 0, 105]) - - - Use the ``rv`` kwarg to input a distribution from the ``scipy.stats`` module:: - - >>> x = uv(rv=ss.norm(loc=10, scale=1)) - - - Use a built-in convenience constructor (typically the easiest if you can):: - - >>> x = N(10, 1) - - A Simple Example - ---------------- - - Now let's walk through an example of a three-part assembly stack-up:: - - >>> x1 = N(24, 1) # normally distributed - >>> x2 = N(37, 4) # normally distributed - >>> x3 = Exp(2) # exponentially distributed - >>> Z = (x1*x2**2)/(15*(1.5 + x3)) - - We can now see the results of the calculations in two ways: - - #. The usual ``print`` statement (or simply the object if in a terminal):: - - >>> Z # "print" is optional at the command-line - uv(1176.45, 99699.6822917, 0.708013052944, 6.16324345127) - - #. The ``describe`` class method that explains briefly what the values are:: - - >>> Z.describe() - SOERP Uncertain Value: - > Mean................... 1176.45 - > Variance............... 99699.6822917 - > Skewness Coefficient... 0.708013052944 - > Kurtosis Coefficient... 6.16324345127 - - Distribution Moments - -------------------- - - The eight moments of any input variable (and four of any output variable) can be accessed using the ``moments`` class method, as in:: - - >>> x1.moments() - [24.0, 1.0, 0.0, 3.0000000000000053, 0.0, 15.000000000000004, 0.0, 105.0] - >>> Z.moments() - [1176.45, 99699.6822917, 0.708013052944, 6.16324345127] - - Correlations - ------------ - - Statistical correlations are correctly handled, even after calculations have taken place:: - - >>> x1 - x1 - 0.0 - >>> square = x1**2 - >>> square - x1*x1 - 0.0 - - Derivatives - ----------- - - Derivatives with respect to original variables are calculated via the ad_ package and are accessed using the **intuitive class methods**:: - - >>> Z.d(x1) # dZ/dx1 - 45.63333333333333 - - >>> Z.d2(x2) # d^2Z/dx2^2 - 1.6 - - >>> Z.d2c(x1, x3) # d^2Z/dx1dx3 (order doesn't matter) - -22.816666666666666 - - When we need multiple derivatives at a time, we can use the ``gradient`` and ``hessian`` class methods:: - - >>> Z.gradient([x1, x2, x3]) - [45.63333333333333, 59.199999999999996, -547.6] - - >>> Z.hessian([x1, x2, x3]) - [[0.0, 2.466666666666667, -22.816666666666666], [2.466666666666667, 1.6, -29.6], [-22.816666666666666, -29.6, 547.6]] - - Error Components/Variance Contributions - --------------------------------------- - - Another useful feature is available through the ``error_components`` class method that has various ways of representing the first- and second-order variance components:: - - >>> Z.error_components(pprint=True) - COMPOSITE VARIABLE ERROR COMPONENTS - uv(37.0, 16.0, 0.0, 3.0) = 58202.9155556 or 58.378236% - uv(24.0, 1.0, 0.0, 3.0) = 2196.15170139 or 2.202767% - uv(0.5, 0.25, 2.0, 9.0) = -35665.8249653 or 35.773258% - - Advanced Example - ---------------- - - Here's a *slightly* more advanced example, estimating the statistical properties of volumetric gas flow through an orifice meter:: - - >>> from soerp.umath import * # sin, exp, sqrt, etc. - >>> H = N(64, 0.5) - >>> M = N(16, 0.1) - >>> P = N(361, 2) - >>> t = N(165, 0.5) - >>> C = 38.4 - >>> Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) - >>> Q.describe() - SOERP Uncertain Value: - > Mean................... 1330.99973939 - > Variance............... 58.210762839 - > Skewness Coefficient... 0.0109422068056 - > Kurtosis Coefficient... 3.00032693502 - - This seems to indicate that even though there are products, divisions, and the usage of ``sqrt``, the result resembles a normal distribution (i.e., Q ~ N(1331, 7.63), where the standard deviation = sqrt(58.2) = 7.63). - - Main Features - ============= - - 1. **Transparent calculations** with derivatives automatically calculated. - **No or little modification** to existing code required. - - 2. Basic `NumPy` support without modification. Vectorized calculations built-in - to the ``ad`` package. - - 3. Nearly all standard `math`_ module functions supported through the - ``soerp.umath`` sub-module. If you think a function is in there, it probably - is. - - 4. Nearly all derivatives calculated analytically using ``ad`` functionality. - - 5. **Easy continuous distribution constructors**: - - - ``N(mu, sigma)`` : `Normal distribution`_ - - - ``U(a, b)`` : `Uniform distribution`_ - - - ``Exp(lamda, [mu])`` : `Exponential distribution`_ - - - ``Gamma(k, theta)`` : `Gamma distribution`_ - - - ``Beta(alpha, beta, [a, b])`` : `Beta distribution`_ - - - ``LogN(mu, sigma)`` : `Log-normal distribution`_ - - - ``X2(k)`` : `Chi-squared distribution`_ - - - ``F(d1, d2)`` : `F-distribution`_ - - - ``Tri(a, b, c)`` : `Triangular distribution`_ - - - ``T(v)`` : `T-distribution`_ - - - ``Weib(lamda, k)`` : `Weibull distribution`_ - - The location, scale, and shape parameters follow the notation in the - respective Wikipedia articles. *Discrete distributions are not recommended - for use at this time. If you need discrete distributions, try the* mcerp_ - *python package instead.* - - Installation - ============ - - **Make sure you install the** `ad`_ **package first!** - - You have several easy, convenient options to install the ``soerp`` package - (administrative privileges may be required) - - 1. Download the package files below, unzip to any directory, and run:: - - $ [sudo] python setup.py install - - 2. Simply copy the unzipped ``soerp-XYZ`` directory to any other location that - python can find it and rename it ``soerp``. - - 3. If ``setuptools`` is installed, run:: - - $ easy_install --upgrade soerp - - 4. If ``pip`` is installed, run:: - - $ pip install --upgrade soerp - - Python 3 - -------- - - To use this package with Python 3.x, you will need to run the ``2to3`` - conversion tool at the command-line using the following syntax while in - the unzipped ``soerp`` directory:: - - $ 2to3 -w -f all *.py - - This should take care of the main changes required. Then, run - ``python3 setup.py install``. If bugs continue to pop up, - please email the author. - - See Also - ======== - - - uncertainties_ : First-order error propagation - - - mcerp_ : Real-time latin-hypercube sampling-based Monte Carlo error propagation - - Contact - ======= - - Please send **feature requests, bug reports, or feedback** to - `Abraham Lee`_. - - Acknowledgements - ================ - - The author wishes to thank `Eric O. LEBIGOT`_ who first developed the - `uncertainties`_ python package (for first-order error propagation), - from which many inspiring ideas (like maintaining object correlations, etc.) - are re-used and/or have been slightly evolved. *If you don't need second - order functionality, his package is an excellent alternative since it is - optimized for first-order uncertainty analysis.* - - References - ========== - - - N.D. Cox, 1979, *Tolerance Analysis by Computer*, Journal of Quality Technology, Vol. 11, No. 2, pp. 80-87 - - - - .. _error propagation: http://en.wikipedia.org/wiki/Propagation_of_uncertainty - .. _math: http://docs.python.org/library/math.html - .. _ad: http://pypi.python.org/pypi/ad - .. _mcerp: http://pypi.python.org/pypi/mcerp - .. _NumPy: http://www.numpy.org/ - .. _SciPy: http://scipy.org - .. _Matplotlib: http://matplotlib.org/ - .. _uncertainties: http://pypi.python.org/pypi/uncertainties - .. _Abraham Lee: mailto: tisimst@gmail.com - .. _Eric O. LEBIGOT: http://www.linkedin.com/pub/eric-lebigot/22/293/277 - .. _PEP8: http://www.python.org/dev/peps/pep-0008 - .. _Normal distribution: http://en.wikipedia.org/wiki/Normal_distribution - .. _Uniform distribution: http://en.wikipedia.org/wiki/Uniform_distribution_(continuous) - .. _Exponential distribution: http://en.wikipedia.org/wiki/Exponential_distribution - .. _Gamma distribution: http://en.wikipedia.org/wiki/Gamma_distribution - .. _Beta distribution: http://en.wikipedia.org/wiki/Beta_distribution - .. _Log-normal distribution: http://en.wikipedia.org/wiki/Log-normal_distribution - .. _Chi-squared distribution: http://en.wikipedia.org/wiki/Chi-squared_distribution - .. _F-distribution: http://en.wikipedia.org/wiki/F-distribution - .. _Triangular distribution: http://en.wikipedia.org/wiki/Triangular_distribution - .. _T-distribution: http://en.wikipedia.org/wiki/Student's_t-distribution - .. _Weibull distribution: http://en.wikipedia.org/wiki/Weibull_distribution - -Keywords: uncertainty analysis,uncertainties,error propagation,second order,derivative,statistics,method of moments,distribution -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Education -Classifier: Intended Audience :: Science/Research -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.0 -Classifier: Programming Language :: Python :: 3.1 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Topic :: Education -Classifier: Topic :: Scientific/Engineering -Classifier: Topic :: Scientific/Engineering :: Mathematics -Classifier: Topic :: Scientific/Engineering :: Physics -Classifier: Topic :: Software Development -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Topic :: Utilities diff --git a/README.md b/README.md new file mode 100644 index 0000000..a52c402 --- /dev/null +++ b/README.md @@ -0,0 +1,241 @@ +# ``soerp`` Second Order Error Propagation for Python + +[![Tests](https://github.com/eggzec/soerp/actions/workflows/code_test.yml/badge.svg)](https://github.com/eggzec/soerp/actions/workflows/code_test.yml) +[![Documentation](https://github.com/eggzec/soerp/actions/workflows/docs_build.yml/badge.svg)](https://github.com/eggzec/soerp/actions/workflows/docs_build.yml) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) + +[![codecov](https://codecov.io/gh/eggzec/soerp/branch/master/graph/badge.svg)](https://codecov.io/gh/eggzec/soerp) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=eggzec_soerp&metric=alert_status)](https://sonarcloud.io/project/overview?id=eggzec_soerp) +[![License](https://img.shields.io/badge/license-BSD%203--Clause-blue.svg)](./LICENSE) + +[![PyPI Downloads](https://img.shields.io/pypi/dm/soerp.svg?label=PyPI%20downloads)](https://pypi.org/project/soerp/) +[![Python versions](https://img.shields.io/pypi/pyversions/soerp.svg)](https://pypi.org/project/soerp/) + +## Overview + +``soerp`` is the Python implementation of the original Fortran code `SOERP` by N. D. Cox to apply a second-order analysis to [error propagation](http://en.wikipedia.org/wiki/Propagation_of_uncertainty) (or uncertainty analysis). The ``soerp`` package allows you to **easily** and **transparently** track the effects of uncertainty through mathematical calculations. Advanced mathematical functions, similar to those in the standard [math](http://docs.python.org/library/math.html) module can also be evaluated directly. + +In order to correctly use ``soerp``, the **first eight statistical moments** of the underlying distribution are required. These are the *mean*, *variance*, and then the *standardized third through eighth moments*. These can be input manually in the form of an array, but they can also be **conveniently generated** using either the **nice constructors** or directly by using the distributions from the ``scipy.stats`` sub-module. See the examples below for usage examples of both input methods. The result of all calculations generates a *mean*, *variance*, and *standardized skewness and kurtosis* coefficients. + +## Basic examples + +Let's begin by importing all the available constructors: + +```python +>>> from soerp import * # uv, normal, uniform, exponential, etc. +``` + +Now, we can see that there are several equivalent ways to specify a statistical distribution, say a Normal distribution with a mean value of 10 and a standard deviation of 1: + +- Manually input the first 8 moments (mean, variance, and 3rd-8th standardized central moments): + +```python +>>> x = uv([10, 1, 0, 3, 0, 15, 0, 105]) +``` + +- Use the ``rv`` kwarg to input a distribution from the ``scipy.stats`` module: + +```python +>>> x = uv(rv=ss.norm(loc=10, scale=1)) +``` + +- Use a built-in convenience constructor (typically the easiest if you can): + +```python +>>> x = normal(10, 1) +``` + +### A Simple Example + +Now let's walk through an example of a three-part assembly stack-up: + +```python +>>> x1 = normal(24, 1) # normally distributed +>>> x2 = normal(37, 4) # normally distributed +>>> x3 = exponential(2) # exponentially distributed +>>> Z = (x1*x2**2)/(15*(1.5 + x3)) +``` + +We can now see the results of the calculations in two ways: + +1. The usual ``print`` statement (or simply the object if in a terminal): + +```python +>>> Z # "print" is optional at the command-line +uv(1176.45, 99699.6822917, 0.708013052944, 6.16324345127) +``` + +2. The ``describe`` class method that explains briefly what the values are: + +```python +>>> Z.describe() +SOERP Uncertain Value: + > Mean................... 1176.45 + > Variance............... 99699.6822917 + > Skewness Coefficient... 0.708013052944 + > Kurtosis Coefficient... 6.16324345127 +``` + +### Distribution Moments + +The eight moments of any input variable (and four of any output variable) can be accessed using the ``moments`` class method, as in: + +```python +>>> x1.moments() +[24.0, 1.0, 0.0, 3.0000000000000053, 0.0, 15.000000000000004, 0.0, 105.0] +>>> Z.moments() +[1176.45, 99699.6822917, 0.708013052944, 6.16324345127] +``` + +### Correlations + +Statistical correlations are correctly handled, even after calculations have taken place: + +```python +>>> x1 - x1 +0.0 +>>> square = x1**2 +>>> square - x1*x1 +0.0 +``` + +### Derivatives + +Derivatives with respect to original variables are calculated and are accessed using the **intuitive class methods**: + +```python +>>> Z.d(x1) # dZ/dx1 +45.63333333333333 + +>>> Z.d2(x2) # d^2Z/dx2^2 +1.6 + +>>> Z.d2c(x1, x3) # d^2Z/dx1dx3 (order doesn't matter) +-22.816666666666666 +``` + +When we need multiple derivatives at a time, we can use the ``gradient`` and ``hessian`` class methods: + +```python +>>> Z.gradient([x1, x2, x3]) +[45.63333333333333, 59.199999999999996, -547.6] + +>>> Z.hessian([x1, x2, x3]) +[[0.0, 2.466666666666667, -22.816666666666666], [2.466666666666667, 1.6, -29.6], [-22.816666666666666, -29.6, 547.6]] +``` + +### Error Components/Variance Contributions + +Another useful feature is available through the ``error_components`` class method that has various ways of representing the first- and second-order variance components: + +```python +>>> Z.error_components(pprint=True) +COMPOSITE VARIABLE ERROR COMPONENTS +uv(37.0, 16.0, 0.0, 3.0) = 58202.9155556 or 58.378236% +uv(24.0, 1.0, 0.0, 3.0) = 2196.15170139 or 2.202767% +uv(0.5, 0.25, 2.0, 9.0) = -35665.8249653 or 35.773258% +``` + +### Advanced Example + +Here's a *slightly* more advanced example, estimating the statistical properties of volumetric gas flow through an orifice meter: + +```python +>>> from soerp import normal, umath # sin, exp, sqrt, etc. +>>> H = normal(64, 0.5) +>>> M = normal(16, 0.1) +>>> P = normal(361, 2) +>>> t = normal(165, 0.5) +>>> C = 38.4 +>>> Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) +>>> Q.describe() +SOERP Uncertain Value: + > Mean................... 1330.99973939 + > Variance............... 58.210762839 + > Skewness Coefficient... 0.0109422068056 + > Kurtosis Coefficient... 3.00032693502 +``` + +This seems to indicate that even though there are products, divisions, and the usage of ``sqrt``, the result resembles a normal distribution (i.e., Q ~ N(1331, 7.63), where the standard deviation = sqrt(58.2) = 7.63). + +## Main Features + +1. **Transparent calculations** with derivatives automatically calculated. **No or little modification** to existing code required. +2. Basic `NumPy` support without modification. +3. Nearly all standard [math](http://docs.python.org/library/math.html) module functions supported through the ``soerp.umath`` sub-module. If you think a function is in there, it probably is. +4. Nearly all derivatives calculated analytically. +5. **Easy continuous distribution constructors**: + - ``normal(mu, sigma)`` or ``N(mu, sigma)`` : [Normal distribution](http://en.wikipedia.org/wiki/Normal_distribution) + - ``uniform(a, b)`` or ``U(a, b)`` : [Uniform distribution](http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)) + - ``exponential(lamda, [mu])`` or ``Exp(lamda, [mu])`` : [Exponential distribution](http://en.wikipedia.org/wiki/Exponential_distribution) + - ``gamma(k, theta)`` or ``Gamma(k, theta)`` : [Gamma distribution](http://en.wikipedia.org/wiki/Gamma_distribution) + - ``beta(alpha, beta, [a, b])`` or ``Beta(alpha, beta, [a, b])`` : [Beta distribution](http://en.wikipedia.org/wiki/Beta_distribution) + - ``log_normal(mu, sigma)`` or ``LogN(mu, sigma)`` : [Log-normal distribution](http://en.wikipedia.org/wiki/Log-normal_distribution) + - ``chi_squared(k)`` or ``Chi2(k)`` : [Chi-squared distribution](http://en.wikipedia.org/wiki/Chi-squared_distribution) + - ``f_distribution(d1, d2)`` or ``F(d1, d2)`` : [F-distribution](http://en.wikipedia.org/wiki/F-distribution) + - ``triangular(a, b, c)`` or ``Tri(a, b, c)`` : [Triangular distribution](http://en.wikipedia.org/wiki/Triangular_distribution) + - ``student_t(v)`` or ``T(v)`` : [T-distribution](http://en.wikipedia.org/wiki/Student's_t-distribution) + - ``weibull(lamda, k)`` or ``Weib(lamda, k)`` : [Weibull distribution](http://en.wikipedia.org/wiki/Weibull_distribution) + + The location, scale, and shape parameters follow the notation in the respective Wikipedia articles. *Discrete distributions are not recommended for use at this time. If you need discrete distributions, try the* [mcerp](http://pypi.python.org/pypi/mcerp) *python package instead.* + +## Installation + +You have several easy, convenient options to install the ``soerp`` package. + +### pip + +```bash +pip install soerp +``` + +To install with plotting support: +```bash +pip install soerp[plot] +``` + +To install all optional dependencies: +```bash +pip install soerp[all] +``` + + +### uv + +```bash +uv add soerp +uv sync +``` + +Or in an existing uv environment: +```bash +uv pip install soerp +``` + + +### git + +To install the latest version from git: +```bash +pip install --upgrade "git+https://github.com/eggzec/soerp.git#egg=soerp" +``` + +#### Requirements + +- Python >=3.10 +- [NumPy](http://www.numpy.org/) : Numeric Python +- [SciPy](http://scipy.org) : Scientific Python (the nice distribution constructors require this) +- [Matplotlib](http://matplotlib.org/) : Python plotting library (optional) + +## See Also + +- [uncertainties](http://pypi.python.org/pypi/uncertainties) : First-order error propagation. +- [mcerp](http://pypi.python.org/pypi/mcerp) : Real-time latin-hypercube sampling-based Monte Carlo error propagation. + +## Acknowledgements + +The author wishes to thank [Eric O. LEBIGOT](http://www.linkedin.com/pub/eric-lebigot/22/293/277) who first developed the [uncertainties](http://pypi.python.org/pypi/uncertainties) python package (for first-order error propagation), from which many inspiring ideas (like maintaining object correlations, etc.) are re-used and/or have been slightly evolved. *If you don't need second order functionality, his package is an excellent alternative since it is optimized for first-order uncertainty analysis.* + +## References + +- N.D. Cox, 1979, *Tolerance Analysis by Computer*, Journal of Quality Technology, Vol. 11, No. 2, pp. 80-87 diff --git a/README.rst b/README.rst deleted file mode 100644 index c302e2d..0000000 --- a/README.rst +++ /dev/null @@ -1,290 +0,0 @@ -=============================== -``soerp`` Package Documentation -=============================== - -Overview -======== - -``soerp`` is the Python implementation of the original Fortran code `SOERP` -by N. D. Cox to apply a second-order analysis to `error propagation`_ (or -uncertainty analysis). The ``soerp`` package allows you to **easily** and -**transparently** track the effects of uncertainty through mathematical -calculations. Advanced mathematical functions, similar to those in the standard -math_ module can also be evaluated directly. - -In order to correctly use ``soerp``, the **first eight statistical moments** -of the underlying distribution are required. These are the *mean*, *variance*, -and then the *standardized third through eighth moments*. These can be input -manually in the form of an array, but they can also be **conveniently -generated** using either the **nice constructors** or directly by using the -distributions from the ``scipy.stats`` sub-module. See the examples below for -usage examples of both input methods. The result of all calculations generates a -*mean*, *variance*, and *standardized skewness and kurtosis* coefficients. - - -Required Packages -================= - -- ad_ : For first- and second-order automatic differentiation (install this first). - -- NumPy_ : Numeric Python - -- SciPy_ : Scientific Python (the nice distribution constructors require this) - -- Matplotlib_ : Python plotting library - -Basic examples -============== - -Let's begin by importing all the available constructors:: - - >>> from soerp import * # uv, N, U, Exp, etc. - -Now, we can see that there are several equivalent ways to specify a statistical distribution, say a Normal distribution with a mean value of 10 and a standard deviation of 1: - -- Manually input the first 8 moments (mean, variance, and 3rd-8th standardized central moments):: - - >>> x = uv([10, 1, 0, 3, 0, 15, 0, 105]) - -- Use the ``rv`` kwarg to input a distribution from the ``scipy.stats`` module:: - - >>> x = uv(rv=ss.norm(loc=10, scale=1)) - -- Use a built-in convenience constructor (typically the easiest if you can):: - - >>> x = N(10, 1) - -A Simple Example ----------------- - -Now let's walk through an example of a three-part assembly stack-up:: - - >>> x1 = N(24, 1) # normally distributed - >>> x2 = N(37, 4) # normally distributed - >>> x3 = Exp(2) # exponentially distributed - >>> Z = (x1*x2**2)/(15*(1.5 + x3)) - -We can now see the results of the calculations in two ways: - -#. The usual ``print`` statement (or simply the object if in a terminal):: - - >>> Z # "print" is optional at the command-line - uv(1176.45, 99699.6822917, 0.708013052944, 6.16324345127) - -#. The ``describe`` class method that explains briefly what the values are:: - - >>> Z.describe() - SOERP Uncertain Value: - > Mean................... 1176.45 - > Variance............... 99699.6822917 - > Skewness Coefficient... 0.708013052944 - > Kurtosis Coefficient... 6.16324345127 - -Distribution Moments --------------------- - -The eight moments of any input variable (and four of any output variable) can be accessed using the ``moments`` class method, as in:: - - >>> x1.moments() - [24.0, 1.0, 0.0, 3.0000000000000053, 0.0, 15.000000000000004, 0.0, 105.0] - >>> Z.moments() - [1176.45, 99699.6822917, 0.708013052944, 6.16324345127] - -Correlations ------------- - -Statistical correlations are correctly handled, even after calculations have taken place:: - - >>> x1 - x1 - 0.0 - >>> square = x1**2 - >>> square - x1*x1 - 0.0 - -Derivatives ------------ - -Derivatives with respect to original variables are calculated via the ad_ package and are accessed using the **intuitive class methods**:: - - >>> Z.d(x1) # dZ/dx1 - 45.63333333333333 - - >>> Z.d2(x2) # d^2Z/dx2^2 - 1.6 - - >>> Z.d2c(x1, x3) # d^2Z/dx1dx3 (order doesn't matter) - -22.816666666666666 - -When we need multiple derivatives at a time, we can use the ``gradient`` and ``hessian`` class methods:: - - >>> Z.gradient([x1, x2, x3]) - [45.63333333333333, 59.199999999999996, -547.6] - - >>> Z.hessian([x1, x2, x3]) - [[0.0, 2.466666666666667, -22.816666666666666], [2.466666666666667, 1.6, -29.6], [-22.816666666666666, -29.6, 547.6]] - -Error Components/Variance Contributions ---------------------------------------- - -Another useful feature is available through the ``error_components`` class method that has various ways of representing the first- and second-order variance components:: - - >>> Z.error_components(pprint=True) - COMPOSITE VARIABLE ERROR COMPONENTS - uv(37.0, 16.0, 0.0, 3.0) = 58202.9155556 or 58.378236% - uv(24.0, 1.0, 0.0, 3.0) = 2196.15170139 or 2.202767% - uv(0.5, 0.25, 2.0, 9.0) = -35665.8249653 or 35.773258% - -Advanced Example ----------------- - -Here's a *slightly* more advanced example, estimating the statistical properties of volumetric gas flow through an orifice meter:: - - >>> from soerp.umath import * # sin, exp, sqrt, etc. - >>> H = N(64, 0.5) - >>> M = N(16, 0.1) - >>> P = N(361, 2) - >>> t = N(165, 0.5) - >>> C = 38.4 - >>> Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) - >>> Q.describe() - SOERP Uncertain Value: - > Mean................... 1330.99973939 - > Variance............... 58.210762839 - > Skewness Coefficient... 0.0109422068056 - > Kurtosis Coefficient... 3.00032693502 - -This seems to indicate that even though there are products, divisions, and the usage of ``sqrt``, the result resembles a normal distribution (i.e., Q ~ N(1331, 7.63), where the standard deviation = sqrt(58.2) = 7.63). - -Main Features -============= - -1. **Transparent calculations** with derivatives automatically calculated. - **No or little modification** to existing code required. - -2. Basic `NumPy` support without modification. Vectorized calculations built-in - to the ``ad`` package. - -3. Nearly all standard `math`_ module functions supported through the - ``soerp.umath`` sub-module. If you think a function is in there, it probably - is. - -4. Nearly all derivatives calculated analytically using ``ad`` functionality. - -5. **Easy continuous distribution constructors**: - - - ``N(mu, sigma)`` : `Normal distribution`_ - - - ``U(a, b)`` : `Uniform distribution`_ - - - ``Exp(lamda, [mu])`` : `Exponential distribution`_ - - - ``Gamma(k, theta)`` : `Gamma distribution`_ - - - ``Beta(alpha, beta, [a, b])`` : `Beta distribution`_ - - - ``LogN(mu, sigma)`` : `Log-normal distribution`_ - - - ``Chi2(k)`` : `Chi-squared distribution`_ - - - ``F(d1, d2)`` : `F-distribution`_ - - - ``Tri(a, b, c)`` : `Triangular distribution`_ - - - ``T(v)`` : `T-distribution`_ - - - ``Weib(lamda, k)`` : `Weibull distribution`_ - - The location, scale, and shape parameters follow the notation in the - respective Wikipedia articles. *Discrete distributions are not recommended - for use at this time. If you need discrete distributions, try the* mcerp_ - *python package instead.* - -Installation -============ - -**Make sure you install the** `ad`_ **package first!** (If you use options -3 or 4 below, this should be done automatically.) - -You have several easy, convenient options to install the ``soerp`` package -(administrative privileges may be required) - -1. Download the package files below, unzip to any directory, and run:: - - $ [sudo] python setup.py install - -2. Simply copy the unzipped ``soerp-XYZ`` directory to any other location that - python can find it and rename it ``soerp``. - -3. If ``setuptools`` is installed, run:: - - $ [sudo] easy_install [--upgrade] soerp - -4. If ``pip`` is installed, run:: - - $ [sudo] pip install [--upgrade] soerp - -Uninstallation -============== - -To remove the package, there are really two good ways to do this: - -1. Go to the folder ``site-packages`` or ``dist-packages`` and simply delete - the folder ``soerp`` and ``soerp-XYZ-egg-info``. - -2. If ``pip`` is installed, run:: - - $ [sudo] pip uninstall soerp - -See Also -======== - -- uncertainties_ : First-order error propagation - -- mcerp_ : Real-time latin-hypercube sampling-based Monte Carlo error - propagation - -Contact -======= - -Please send **feature requests, bug reports, or feedback** to -`Abraham Lee`_. - -Acknowledgements -================ - -The author wishes to thank `Eric O. LEBIGOT`_ who first developed the -`uncertainties`_ python package (for first-order error propagation), -from which many inspiring ideas (like maintaining object correlations, etc.) -are re-used and/or have been slightly evolved. *If you don't need second -order functionality, his package is an excellent alternative since it is -optimized for first-order uncertainty analysis.* - -References -========== - -- N.D. Cox, 1979, *Tolerance Analysis by Computer*, Journal of Quality Technology, Vol. 11, No. 2, pp. 80-87 - - - -.. _error propagation: http://en.wikipedia.org/wiki/Propagation_of_uncertainty -.. _math: http://docs.python.org/library/math.html -.. _ad: http://pypi.python.org/pypi/ad -.. _mcerp: http://pypi.python.org/pypi/mcerp -.. _NumPy: http://www.numpy.org/ -.. _SciPy: http://scipy.org -.. _Matplotlib: http://matplotlib.org/ -.. _uncertainties: http://pypi.python.org/pypi/uncertainties -.. _Abraham Lee: mailto: tisimst@gmail.com -.. _Eric O. LEBIGOT: http://www.linkedin.com/pub/eric-lebigot/22/293/277 -.. _PEP8: http://www.python.org/dev/peps/pep-0008 -.. _Normal distribution: http://en.wikipedia.org/wiki/Normal_distribution -.. _Uniform distribution: http://en.wikipedia.org/wiki/Uniform_distribution_(continuous) -.. _Exponential distribution: http://en.wikipedia.org/wiki/Exponential_distribution -.. _Gamma distribution: http://en.wikipedia.org/wiki/Gamma_distribution -.. _Beta distribution: http://en.wikipedia.org/wiki/Beta_distribution -.. _Log-normal distribution: http://en.wikipedia.org/wiki/Log-normal_distribution -.. _Chi-squared distribution: http://en.wikipedia.org/wiki/Chi-squared_distribution -.. _F-distribution: http://en.wikipedia.org/wiki/F-distribution -.. _Triangular distribution: http://en.wikipedia.org/wiki/Triangular_distribution -.. _T-distribution: http://en.wikipedia.org/wiki/Student's_t-distribution -.. _Weibull distribution: http://en.wikipedia.org/wiki/Weibull_distribution diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 3b9370d..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Starter pipeline -# Start with a minimal pipeline that you can customize to build and deploy your code. -# Add steps that build, run tests, deploy, and more: -# https://aka.ms/yaml - -pool: - vmImage: 'Ubuntu 16.04' - -steps: -- script: echo Hello, world! - displayName: 'Run a one-line script' - -- script: | - echo Add other tasks to build, test, and deploy your project. - echo See https://aka.ms/yaml - displayName: 'Run a multi-line script' diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..4d3fe6d --- /dev/null +++ b/docs/api.md @@ -0,0 +1,52 @@ +# API Reference + +soerp provides a Python implementation of the SOERP method (Cox 1979) for second-order error propagation. See the [Theory](theory.md) and [Quickstart](quickstart.md) for mathematical background and usage. + +## Main Features + +- Transparent calculations with automatic derivatives +- Basic NumPy support +- Nearly all standard math module functions supported via `soerp.umath` (e.g., `sin`, `exp`, `sqrt`, etc.) +- Analytical derivatives up to second order +- Easy continuous distribution constructors: + - `normal(mu, sigma)` or `N(mu, sigma)`: Normal + - `uniform(a, b)` or `U(a, b)`: Uniform + - `exponential(lamda, [mu])` or `Exp(lamda, [mu])`: Exponential + - `gamma(k, theta)` or `Gamma(k, theta)`: Gamma + - `beta(alpha, beta, [a, b])` or `Beta(alpha, beta, [a, b])`: Beta + - `log_normal(mu, sigma)` or `LogN(mu, sigma)`: Log-normal + - `chi_squared(k)` or `Chi2(k)`: Chi-squared + - `f_distribution(d1, d2)` or `F(d1, d2)`: F-distribution + - `triangular(a, b, c)` or `Tri(a, b, c)`: Triangular + - `student_t(v)` or `T(v)`: T-distribution + - `weibull(lamda, k)` or `Weib(lamda, k)`: Weibull + +## Core Classes and Functions + +- `uv`: Uncertain variable constructor (accepts moments or a scipy.stats distribution) +- `normal`, `uniform`, `exponential`, `gamma`, `chi_squared`, ...: Distribution functions +- `N`, `U`, `Exp`, `Gamma`, `Chi2`, ...: Same functions with uppercase names +- `umath`: Math functions for uncertain variables +- `describe()`: Print mean, variance, skewness, kurtosis +- `moments()`: Return moments of a variable +- `d()`, `d2()`, `d2c()`: First and second derivatives, mixed derivatives +- `gradient()`, `hessian()`: Vector/matrix of derivatives +- `error_components(pprint=True/False)`: Variance decomposition and error component breakdown + +## Example Workflows + +soerp supports both direct moment input and distribution-based construction. You can: + +- Create uncertain variables from moments, scipy.stats distributions, or constructors +- Combine variables using arithmetic and math functions +- Compute and print all moments, derivatives, and error components + +See the [Quickstart](quickstart.md) for full code examples, including: + +- Assembly stack-up +- Orifice flow +- Manufacturing tolerance stackup +- Scheduling facilities +- Two-bar truss + +All examples demonstrate both moment-based and distribution-based usage, as well as advanced features like error decomposition. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..4c07ae8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,94 @@ +# soerp + +**Second Order Error Propagation for Python** + +--- + +## Overview + +`soerp` is the Python implementation of the original Fortran code SOERP by N. D. Cox to apply a second-order analysis to [error propagation](http://en.wikipedia.org/wiki/Propagation_of_uncertainty) (or uncertainty analysis). The package allows you to **easily** and **transparently** track the effects of uncertainty through mathematical calculations. Advanced mathematical functions, similar to those in the standard [math](http://docs.python.org/library/math.html) module, can also be evaluated directly. + +To use `soerp`, the **first eight statistical moments** of the underlying distribution are required: *mean*, *variance*, and the *standardized third through eighth moments*. These can be input manually as an array, or generated using the provided constructors or directly from `scipy.stats` distributions. The result of all calculations generates a *mean*, *variance*, and *standardized skewness and kurtosis* coefficients. + +## Requirements + +- [NumPy](http://www.numpy.org/) +- [SciPy](http://scipy.org) +- [Matplotlib](http://matplotlib.org/) (optional, for plotting) + +## Example Usage + +```python +from soerp import uv, normal, uniform, exponential, umath + +# Normal distribution, mean=10, std=1 +x = uv([10, 1, 0, 3, 0, 15, 0, 105]) +# x = uv(rv=ss.norm(loc=10, scale=1)) +x = normal(10, 1) + +# Three-part assembly +x1 = normal(24, 1) +x2 = normal(37, 4) +x3 = exponential(2) +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print(Z) +Z.describe() + +# Moments +print(x1.moments()) +print(Z.moments()) + +# Correlations +print(x1 - x1) +square = x1**2 +print(square - x1 * x1) + +# Derivatives +print(Z.d(x1)) +print(Z.d2(x2)) +print(Z.d2c(x1, x3)) +print(Z.gradient([x1, x2, x3])) +print(Z.hessian([x1, x2, x3])) +Z.error_components(pprint=True) + +# Orifice flow example +H = normal(64, 0.5) +M = normal(16, 0.1) +P = normal(361, 2) +t = normal(165, 0.5) +C = 38.4 +Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) +Q.describe() +``` + +## Main Features + +1. **Transparent calculations** with automatic derivatives. +2. Basic NumPy support. +3. Nearly all standard math module functions supported via `soerp.umath`. +4. Analytical derivatives up to second order. +5. **Easy continuous distribution constructors:** + - `normal(mu, sigma)` or `N(mu, sigma)` : Normal + - `uniform(a, b)` or `U(a, b)` : Uniform + - `exponential(lamda, [mu])` or `Exp(lamda, [mu])` : Exponential + - `gamma(k, theta)` or `Gamma(k, theta)` : Gamma + - `beta(alpha, beta, [a, b])` or `Beta(alpha, beta, [a, b])` : Beta + - `log_normal(mu, sigma)` or `LogN(mu, sigma)` : Log-normal + - `chi_squared(k)` or `Chi2(k)` : Chi-squared + - `f_distribution(d1, d2)` or `F(d1, d2)` : F-distribution + - `triangular(a, b, c)` or `Tri(a, b, c)` : Triangular + - `student_t(v)` or `T(v)` : T-distribution + - `weibull(lamda, k)` or `Weib(lamda, k)` : Weibull + +## See Also + +- [uncertainties](http://pypi.python.org/pypi/uncertainties): First-Order Error Propagation +- [mcerp](http://pypi.python.org/pypi/mcerp): Real-time Latin-Hypercube Sampling-based Monte Carlo Error Propagation + +## Acknowledgements + +The author thanks [Eric O. LEBIGOT](http://www.linkedin.com/pub/eric-lebigot/22/293/277) for the original [uncertainties](http://pypi.python.org/pypi/uncertainties) package, which inspired many ideas reused or evolved here. If you only need first-order uncertainty analysis, his package is an excellent alternative. + +## References + +- N.D. Cox, 1979, *Tolerance Analysis by Computer*, Journal of Quality Technology, Vol. 11, No. 2, pp. 80-87 diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..343a0e4 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,134 @@ + +# Installation + +`soerp` can be installed from `pypi`, `conda-forge`, and `git`. + +## [PyPI](https://pypi.org/project/soerp) + +For using the PyPI package in your project, you can update your configuration file by adding following snippet. + +=== "pyproject.toml" + + ```toml + [project.dependencies] + soerp = "*" # (1)! + ``` + + 1. Specifying a version is recommended + +=== "requirements.txt" + + ``` + soerp>=0.1.0 + ``` + +### pip + +=== "Installation for user" + + ```bash + pip install --upgrade --user soerp # (1)! + ``` + + 1. You may need to use `pip3` instead of `pip` depending on your python installation. + +=== "Installation in virtual environment" + + ```bash + python -m venv .venv + source .venv/bin/activate + pip install --require-virtualenv --upgrade soerp # (1)! + ``` + + 1. You may need to use `pip3` instead of `pip` depending on your python installation. + + !!! note + Command to activate the virtual env depends on your platform and shell. [More info](https://docs.python.org/3/library/venv.html#how-venvs-work) + +### pipenv + + pipenv install soerp + +### uv + +=== "Adding to uv project" + + ```bash + uv add soerp + uv sync + ``` + +=== "Installing to uv environment" + + ```bash + uv venv + uv pip install soerp + ``` + +### poetry + +```bash +poetry add soerp +``` + +### pdm + +```bash +pdm add soerp +``` + +### hatch + +```bash +hatch add soerp +``` + +## [conda-forge](https://anaconda.org/conda-forge/soerp) + +You can update your environment spec file by adding following snippets. + +```yaml title="environment.yml" +channels: + - conda-forge +dependencies: + - pip + - pip: + - soerp # (1)! +``` +1. Specifying a version is recommended + +Installation can be done using the updated environment spec file. + +=== "conda" + ```bash + conda env update --file environment.yml + ``` +=== "micromamba" + ```bash + micromamba env update --file environment.yml + ``` + +!!! note + replace `environment.yml` with your actual environment spec file name if it's different. + +=== "conda" + ```bash + conda install -c conda-forge soerp + ``` +=== "micromamba" + ```bash + micromamba install -c conda-forge soerp + ``` + +## [git](https://github.com/eggzec/soerp) + +```bash +pip install --upgrade "git+https://github.com/eggzec/soerp.git#egg=soerp" +``` + +## Dependencies + +- Python >=3.10 +- [numpy](https://pypi.org/project/numpy) +- [scipy](https://pypi.org/project/scipy) +- [matplotlib](https://pypi.org/project/matplotlib) (optional) diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..dfd1af5 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,246 @@ +# Quickstart +This quickstart demonstrates how to use `soerp` for second-order error propagation, following the examples in Cox (1979) and the provided soerp_examples.py. For mathematical background, see the [Theory](theory.md) section. + +## Importing and Creating Variables + +```python +# Normal distribution, mean=10, std=1 (using moments) +from soerp import uv + +x = uv([10, 1, 0, 3, 0, 15, 0, 105]) +print(x) +``` + +```python +# Normal distribution, using scipy.stats +from soerp import uv +import scipy.stats as ss + +x = uv(rv=ss.norm(loc=10, scale=1)) +print(x) +``` + +```python +# Normal distribution, using constructor +from soerp import normal + +x = normal(10, 1) +print(x) +``` + +```python +from soerp import * # uv, normal, uniform, exponential, gamma, chi_squared, umath + +# Normal distribution, mean=10, std=1 (using moments) +x = uv([10, 1, 0, 3, 0, 15, 0, 105]) +# Normal distribution, using scipy.stats +import scipy.stats as ss + +x = uv(rv=ss.norm(loc=10, scale=1)) +# Normal distribution, using constructor +x = normal(10, 1) # or N(10, 1) +``` + +## Example: Three-Part Assembly + +```python +# Using moments +from soerp import uv + +x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) +x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) +x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print(Z) +# Results: Mean ≈ 1176.45, Variance ≈ 99699.68, Skewness ≈ 0.71, Kurtosis ≈ 6.16 +``` + +```python +# Using distribution constructors +from soerp import normal, exponential + +x1 = normal(24, 1) +x2 = normal(37, 4) +x3 = exponential(2) +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print(Z) +Z.describe() +``` + +## Example: Orifice Flow + +```python +# Using moments +from soerp import uv, umath + +H = uv([64, 0.25, 0, 3, 0, 15, 0, 105]) +M = uv([16, 0.01, 0, 3, 0, 15, 0, 105]) +P = uv([361, 4, 0, 3, 0, 15, 0, 105]) +t = uv([165, 0.25, 0, 3, 0, 15, 0, 105]) +C = 38.4 +Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) +print(Q) +# Results: Mean ≈ 1331.0, Variance ≈ 58.21, Skewness ≈ 0.011, Kurtosis ≈ 3.00 +``` + +```python +# Using constructors +from soerp import normal, umath + +H = normal(64, 0.5) +M = normal(16, 0.1) +P = normal(361, 2) +t = normal(165, 0.5) +C = 38.4 +Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) +print(Q) +Q.describe() +``` + +## Example: Manufacturing Tolerance Stackup + +```python +# Using moments (gamma distributed) +from soerp import uv + +x = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) +y = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) +z = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) +w = x + y + z +print(w) +# Results: Mean ≈ 4.5, Variance ≈ 0.75, Skewness ≈ 0.385, Kurtosis ≈ 3.22 +``` + +```python +# Using Gamma constructor +from soerp import gamma + +mn = 1.5 +vr = 0.25 +scale = vr / mn +shape = mn**2 / vr +x = gamma(shape, scale) +y = gamma(shape, scale) +z = gamma(shape, scale) +w = x + y + z +print(w) +``` + +## Example: Scheduling Facilities (Six Stations) + +```python +# Using moments +from soerp import uv + +s1 = uv([10, 1, 0, 3, 0, 0, 0, 0]) +s2 = uv([20, 2, 0, 3, 0, 0, 0, 0]) +s3 = uv([1.5, 0.25, 0.67, 3.67, 0, 0, 0, 0]) +s4 = uv([10, 10, 0.63, 3.6, 0, 0, 0, 0]) +s5 = uv([0.2, 0.04, 2, 9, 0, 0, 0, 0]) +s6 = uv([10, 20, 0.89, 4.2, 0, 0, 0, 0]) +T = s1 + s2 + s3 + s4 + s5 + s6 +print(T) +# Results: Mean ≈ 51.7, Variance ≈ 33.3, Skewness ≈ 0.52, Kurtosis ≈ 3.49 +``` + +```python +# Using constructors +from soerp import normal, gamma, exponential, chi_squared + +s1 = normal(10, 1) +s2 = normal(20, 2**0.5) +mn1 = 1.5 +vr1 = 0.25 +scale1 = vr1 / mn1 +shape1 = mn1**2 / vr1 +s3 = gamma(shape1, scale1) +mn2 = 10 +vr2 = 10 +scale2 = vr2 / mn2 +shape2 = mn2**2 / vr2 +s4 = gamma(shape2, scale2) +s5 = exponential(5) +s6 = chi_squared(10) +T = s1 + s2 + s3 + s4 + s5 + s6 +print(T) +``` + +## Example: Two-Bar Truss + +```python +# Two-bar truss example +import math +from soerp import normal, umath + +H = normal(30, 5 / 3.0, tag="H") +B = normal(60, 0.5 / 3.0, tag="B") +d = normal(3, 0.1 / 3, tag="d") +t = normal(0.15, 0.01 / 3, tag="t") +E = normal(30000, 1500 / 3.0, tag="E") +rho = normal(0.3, 0.01 / 3.0, tag="rho") +P = normal(66, 3 / 3.0, tag="P") +pi = math.pi +wght = 2 * pi * rho * d * t * umath.sqrt((B / 2) ** 2 + H**2) +strs = (P * umath.sqrt((B / 2) ** 2 + H**2)) / (2 * pi * d * t * H) +buck = (pi**2 * E * (d**2 + t**2)) / (8 * ((B / 2) ** 2 + H**2)) +defl = (P * ((B / 2) ** 2 + H**2) ** (1.5)) / (2 * pi * d * t * H**2 * E) +print("wght:", wght) +print("strs:", strs) +print("buck:", buck) +print("defl:", defl) +wght.error_components(pprint=True) +``` + +## Moments, Correlations, Derivatives + +You can access moments, correlations, and derivatives for any uncertain variable: + +```python +# Example: moments and derivatives +from soerp import normal, exponential +import scipy.stats as ss + +x1 = normal(24, 1) +x2 = normal(37, 4) +x3 = normal(10, 1) +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print(Z.moments()) +print(x1 - x1) +square = x1**2 +print(square - x1 * x1) +print(Z.d(x1)) # First derivative +print(Z.d2(x2)) # Second derivative +print(Z.d2c(x1, x3)) # Mixed second derivative +print(Z.gradient([x1, x2, x3])) +print(Z.hessian([x1, x2, x3])) +Z.error_components(pprint=True) + +x1.moments() +Z.moments() +x1 - x1 +square = x1**2 +square - x1 * x1 +Z.d(x1) # First derivative +Z.d2(x2) # Second derivative +Z.d2c(x1, x3) # Mixed second derivative +Z.gradient([x1, x2, x3]) +Z.hessian([x1, x2, x3]) +Z.error_components(pprint=True) + +x = uv(rv=ss.norm(loc=10, scale=1)) +x = N(10, 1) +x2 = N(37, 4) +x3 = Exp(2) +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print(Z) +Z.moments() +x1 - x1 +square = x1**2 +square - x1 * x1 +Z.d(x1) +Z.d2(x2) +Z.d2c(x1, x3) +Z.gradient([x1, x2, x3]) +Z.hessian([x1, x2, x3]) +Z.error_components(pprint=True) +``` diff --git a/docs/references.md b/docs/references.md new file mode 100644 index 0000000..0b08244 --- /dev/null +++ b/docs/references.md @@ -0,0 +1,20 @@ +# References + +- N.D. Cox, 1979, *Tolerance Analysis by Computer*, Journal of Quality Technology, Vol. 11, No. 2, pp. 80-87 +- A. H. Bowker and G. J. Lieberman, *Engineering Statistics*, Prentice--Hall, 1959. +- R. S. Burington and D. C. May, *Handbook of Probability and Statistics*, McGraw--Hill, 1970. +- N. D. Cox, "Comparison of Two Uncertainty Analysis Methods," *Nuclear Science and Engineering*, vol. 64, no. 1, 1977. +- G. J. Hahn and S. S. Shapiro, *Statistical Models in Engineering*, Wiley, 1967. +- N. L. Johnson and S. Kotz, *Continuous Univariate Distributions*, Houghton Mifflin, 1970. +- H. H. Ku, "Notes on the Use of Propagation of Error Formulas," NBS Special Publication 300, 1969. +- W. Volk, *Applied Statistics for Engineers*, McGraw--Hill, 1969. + +--- + +**Related Packages:** + +- [uncertainties](http://pypi.python.org/pypi/uncertainties): First-order error propagation +- [mcerp](http://pypi.python.org/pypi/mcerp): Real-time latin-hypercube sampling-based Monte Carlo error propagation +- [NumPy](http://www.numpy.org/) +- [SciPy](http://scipy.org) +- [Matplotlib](http://matplotlib.org/) diff --git a/docs/theory.md b/docs/theory.md new file mode 100644 index 0000000..02ca9d3 --- /dev/null +++ b/docs/theory.md @@ -0,0 +1,354 @@ +# Theory + +## Overview + +`soerp` implements **second-order error propagation (SOERP)**, a method for estimating the +statistical moments of a function of random variables. Given a function +$z = h(x_1, x_2, \ldots, x_n)$ where each $x_i$ is an independent random variable with +known moments, SOERP computes the first four moments of $z$ using a second-order Taylor +series approximation. These moments characterize the output distribution — its mean, +variance, skewness, and kurtosis — without requiring Monte Carlo simulation. + +The method is particularly useful for: + +- **Tolerance analysis**: predicting how part-to-part variation propagates to system output +- **Uncertainty analysis**: quantifying output uncertainty from imprecisely known input parameters +- **Sensitivity analysis**: identifying which input variables contribute most to output variance + +--- + +## Second-Order Taylor Series Approximation + +### System Output Polynomial + +Let $h(x_1, x_2, \ldots, x_n)$ be the function of interest and let $\nu_{i1}$ denote the +expected value (mean) of the $i$-th random variable $x_i$. The function is expanded in a +second-order Taylor series about the point $(\nu_{11}, \nu_{21}, \ldots, \nu_{n1})$: + +$$ +h(x_1, \ldots, x_n) \approx z[\nu_{11}, \ldots, \nu_{n1}] ++ \sum_{i=1}^n \frac{\partial h}{\partial x_i}(x_i - \nu_{i1}) ++ \frac{1}{2}\sum_{i=1}^n \frac{\partial^2 h}{\partial x_i^2}(x_i - \nu_{i1})^2 ++ \sum_{\substack{i,j \\ i < j}} \frac{\partial^2 h}{\partial x_i \partial x_j}(x_i - \nu_{i1})(x_j - \nu_{j1}) +$$ + +where all partial derivatives are evaluated at the mean values $\nu_{i1}$. + +This expansion is written compactly as a second-order polynomial $z$ in the centered +variables $(x_i - \nu_{i1})$: + +$$ +z = b_0 + \sum_{i=1}^n b_i(x_i - \nu_{i1}) + + \sum_{i=1}^n b_{ii}(x_i - \nu_{i1})^2 + + \sum_{\substack{i,j \\ i < j}} b_{ij}(x_i - \nu_{i1})(x_j - \nu_{j1}) +$$ + +### Polynomial Coefficients + +The coefficients are determined directly from the partial derivatives of $h$ evaluated +at the mean values: + +| Coefficient | Expression | Description | +|---|---|---| +| $b_0$ | $h(\nu_{11}, \nu_{21}, \ldots, \nu_{n1})$ | Intercept (nominal output) | +| $b_i$ | $\displaystyle\left.\frac{\partial h}{\partial x_i}\right\|_{\boldsymbol{\nu}}$ | Linear (first-order) coefficient | +| $b_{ii}$ | $\displaystyle\frac{1}{2}\left.\frac{\partial^2 h}{\partial x_i^2}\right\|_{\boldsymbol{\nu}}$ | Quadratic (second-order) coefficient | +| $b_{ij}$ | $\displaystyle\left.\frac{\partial^2 h}{\partial x_i \partial x_j}\right\|_{\boldsymbol{\nu}}$ | Cross-product (interaction) coefficient | + +### Centered Output Variable + +For the purpose of moment calculations, a centered output variable $y$ is defined by +subtracting the nominal output from $z$: + +$$ +y = z(x_1, \ldots, x_n) - z(\nu_{11}, \ldots, \nu_{n1}) +$$ + +The central moments of $y$ (order 2 and above) are identical to those of $z$. Only +the mean differs; the mean of $z$ is recovered as: + +$$ +\nu_{z1} = \nu_{y1} + z(\nu_{11}, \nu_{21}, \ldots, \nu_{n1}) +$$ + +--- + +## Input Variable Moments + +Each input variable $x_i$ is independently distributed with central moments +$\mu_{ij}$ for $2 \le j \le 8$. The central moments are related to moments about the +origin by: + +$$ +\begin{align} +\mu_{i0} &= 1 \\ +\mu_{i1} &= 0 \\ +\mu_{i2} &= \nu_{i2} - \nu_{i1}^2 \qquad\text{(variance)} \\ +\mu_{i3} &= \nu_{i3} - 3\nu_{i2}\nu_{i1} + 2\nu_{i1}^3 \\ +\mu_{i4} &= \nu_{i4} - 4\nu_{i3}\nu_{i1} + 6\nu_{i2}\nu_{i1}^2 - 3\nu_{i1}^4 +\end{align} +$$ + +A rigorous second-order propagation requires moments up through **eighth order** +($\mu_{i2}$ through $\mu_{i8}$). Truncating at fourth-order central moments produces +an approximation equivalent to the simplified formulas found in the earlier literature. + +### Standardized Input Variables + +It is convenient to work with standardized (transformed) variables: + +$$ +w_i = \frac{x_i - \nu_{i1}}{\sigma_i} +$$ + +where $\sigma_i$ is the standard deviation of $x_i$. Each $w_i$ then has zero mean and +unit variance, and its central moments depend only on the shape of the distribution, +not its scale. For a **normally distributed** variable, the standardized central moments are: + +$$ +\mu_1 = 0, \quad +\mu_2 = 1, \quad +\mu_3 = 0, \quad +\mu_4 = 3, \quad +\mu_5 = 0, \quad +\mu_6 = 15, \quad +\mu_7 = 0, \quad +\mu_8 = 105 +$$ + +When using standardized variables, the polynomial coefficients are also rescaled. +The quadratic coefficient for $w_i$ becomes: + +$$ +b_{ii}' = b_{ii}\,\sigma_i^2 +$$ + +and the cross-product coefficient for $w_i w_j$ becomes: + +$$ +b_{ij}' = b_{ij}\,\sigma_i\,\sigma_j +$$ + +--- + +## Moment Equations + +The $k$-th moment of $y$ about the origin is: + +$$ +\nu_{yk} = \int \cdots \int y^k \left(\prod_{i=1}^n f_i\right) dx_1 \cdots dx_n +$$ + +where $f_i$ is the probability density function of $x_i$. Substituting the second-order +polynomial approximation and exploiting the independence of the $x_i$, this integral +reduces to an algebraic function of the polynomial coefficients and the central moments +of the input variables. + +The first four moments are given below. These are the equations solved by SOERP. + +### First Moment (Mean of $y$) + +$$ +\nu_{y1} = \sum_{i=1}^n b_{ii}\,\mu_{i2} +\tag{A-6} +$$ + +Note that the linear terms $b_i$ do not contribute to the mean because the $x_i$ are +centered at their expected values. Only the quadratic terms $b_{ii}$ shift the mean. + +### Second Moment + +$$ +\nu_{y2} = \sum_{i=1}^n \left[b_i^2\,\mu_{i2} + 2b_i b_{ii}\,\mu_{i3} + b_{ii}^2\,\mu_{i4}\right] + + \sum_{\substack{i,j \\ i < j}} \left[2b_{ii}b_{jj} + b_{ij}^2\right]\mu_{i2}\,\mu_{j2} +\tag{A-7} +$$ + +### Third Moment + +$$ +\begin{align} +\nu_{y3} &= \sum_{i=1}^n \left[b_i^3\,\mu_{i3} + b_{ii}^3\,\mu_{i6} + + 3b_i^2 b_{ii}\,\mu_{i4} + 3b_i b_{ii}^2\,\mu_{i5}\right] \\ +&\quad + \sum_{\substack{i,j \\ i < j}} \left[b_{ij}^2\,\mu_{i3}\,\mu_{j3} + + 6b_i b_j b_{ij}\,\mu_{i2}\,\mu_{j2} + + 6b_{ii}b_{jj}b_{ij}\,\mu_{i3}\,\mu_{j3}\right] \\ +&\quad + \sum_{i=1}^n \sum_{\substack{j=1 \\ j \ne i}}^n + \left[3b_{ii}^2\,\mu_{i4}\,b_{jj}\,\mu_{j2} + + 6b_i b_{ii} b_{ij}\,\mu_{i2}\,\mu_{j3} + + 3b_{ii}b_j^2\,\mu_{i2}\,\mu_{j2} + + 6b_i b_{ii} b_{jj}\,\mu_{i3}\,\mu_{j2} + + 3b_i b_{ij}^2\,\mu_{i3}\,\mu_{j2} + + 3b_{ii}b_{ij}^2\,\mu_{i4}\,\mu_{j2}\right] \\ +&\quad + \sum_{i=1}^{n-2}\sum_{j=i+1}^{n-1}\sum_{k=j+1}^{n} + \left\{6b_{ii}b_{jj}b_{kk} + + 6b_{ij}b_{ik}b_{jk} + + 3\left[b_{ii}b_{jk}^2 + b_{jj}b_{ik}^2 + b_{kk}b_{ij}^2\right] + \right\}\mu_{i2}\,\mu_{j2}\,\mu_{k2} +\tag{A-8} +\end{align} +$$ + +### Fourth Moment + +The fourth moment involves single-variable sums, double sums over pairs $(i,j)$, triple +sums over triplets $(i,j,k)$, and quadruple sums over quadruplets $(i,j,k,m)$. + +**Single-variable terms:** + +$$ +\sum_{i=1}^n \left[b_i^4\,\mu_{i4} + b_{ii}^4\,\mu_{i8} ++ 4b_i^3 b_{ii}\,\mu_{i5} + 4b_i b_{ii}^3\,\mu_{i7} ++ 6b_i^2 b_{ii}^2\,\mu_{i6}\right] +$$ + +**Pair terms** ($i < j$): + +$$ +\sum_{\substack{i,j \\ i= 1.26", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "soerp" +dynamic = ["version"] +description = "Second Order Error Propagation for Python" +authors = [ + { name = "Abraham Lee", email = "tisimst@gmail.com" }, +] +maintainers = [ + { name = "Saud Zahir", email = "m.saud.zahir@gmail.com" }, + { name = "M Laraib Ali", email = "laraibg786@outlook.com" } +] +readme = "README.md" +license = "BSD-3-Clause" + +keywords = [ + "uncertainty analysis", + "uncertainties", + "error propagation", + "second order", + "derivative", + "statistics", + "method of moments", + "distribution" +] + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Education", + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Education", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Scientific/Engineering :: Physics", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities", + "Operating System :: OS Independent", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", +] + +dependencies = [ + "numpy", + "scipy" +] + +requires-python = ">=3.10" + +[project.urls] +homepage = "https://eggzec.github.io/soerp/" +documentation = "https://eggzec.github.io/soerp/" +source = "https://github.com/eggzec/soerp" +releasenotes = "https://github.com/eggzec/soerp/releases/latest" +issues = "https://github.com/eggzec/soerp/issues" + +[dependency-groups] +dev = [ + {include-group = "docs"}, + {include-group = "lint"}, + {include-group = "test"} +] +docs = [ + "zensical>=0.0.23" +] +lint = [ + "ruff==0.15.*", +] +test = [ + "pytest>=8.3.5", + "pytest-cov>=7.0.0", + "pytest-xdist>=3.6.1", +] + +[project.optional-dependencies] +plot = [ + "matplotlib" +] +all = [ + "matplotlib" +] + +[tool.hatch.version] +source = "vcs" + +[tool.hatch.build.targets.wheel] +include = [ + "/soerp" +] + +[tool.ruff] +line-length = 80 +indent-width = 4 +preview = true + +# Output serialization format for violations. The default serialization +# format is "full" [env: RUFF_OUTPUT_FORMAT=] [possible values: +# concise, full, json, json-lines, junit, grouped, github, gitlab, +# pylint, rdjson, azure, sarif] +output-format = "grouped" + +[tool.ruff.lint] +isort.lines-after-imports = 2 +isort.split-on-trailing-comma = false + +select = [ + "ANN", # flake8-annotations (required strict type annotations for public functions) + "S", # flake8-bandit (checks basic security issues in code) + "BLE", # flake8-blind-except (checks the except blocks that do not specify exception) + "FBT", # flake8-boolean-trap (ensure that boolean args can be used with kw only) + "E", # pycodestyle errors (PEP 8 style guide violations) + "W", # pycodestyle warnings (e.g., extra spaces, indentation issues) + "DOC", # pydoclint issues (e.g., extra or missing return, yield, warnings) + "A", # flake8-buitins (check variable and function names to not shadow builtins) + "N", # Naming convention checks (e.g., PEP 8 variable and function names) + "F", # Pyflakes errors (e.g., unused imports, undefined variables) + "I", # isort (Ensures imports are sorted properly) + "B", # flake8-bugbear (Detects likely bugs and bad practices) + "TID", # flake8-tidy-imports (Checks for banned or misplaced imports) + "UP", # pyupgrade (Automatically updates old Python syntax) + "YTT", # flake8-2020 (Detects outdated Python 2/3 compatibility issues) + "FLY", # flynt (Converts old-style string formatting to f-strings) + "PIE", # flake8-pie + "PL", # pylint + "RUF", # Ruff-specific rules (Additional optimizations and best practices) +] + +ignore = [ + "N803", # [pep8-naming](https://docs.astral.sh/ruff/rules/invalid-argument-name/) + "N806", # [pep8-naming](https://docs.astral.sh/ruff/rules/non-lowercase-variable-in-function/) + "PLR2004", # [pylint](https://docs.astral.sh/ruff/rules/magic-value-comparison/) + "S311", # [flake8-bandit](https://docs.astral.sh/ruff/rules/suspicious-non-cryptographic-random-usage/) +] + + +# Ignore strict naming rules for soerp scientific code, but keep them for other modules. +[tool.ruff.lint.per-file-ignores] +"tests/*.py" = [ + "ANN001", # [flake8-annotations](https://docs.astral.sh/ruff/rules/missing-type-function-argument/) + "ANN201", # [flake8-annotations](https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/) + "ANN202", # [flake8-annotations](https://docs.astral.sh/ruff/rules/missing-return-type-private-function/) + "S101", # [flake8-bandit](https://docs.astral.sh/ruff/rules/assert/) + "PLR6301", # [pylint](https://docs.astral.sh/ruff/rules/no-self-use/) +] + +[tool.ruff.format] +docstring-code-format = true +skip-magic-trailing-comma = true + +[tool.pytest.ini_options] +addopts = "--doctest-modules" +testpaths = ["tests"] +python_files = ["test_*.py"] diff --git a/revision_history.txt b/revision_history.txt deleted file mode 100644 index 19d5d30..0000000 --- a/revision_history.txt +++ /dev/null @@ -1,48 +0,0 @@ -soerp-0.9.1 -=========== - -- Fixed some minor bugs - -- Improved documentation - -soerp-0.9 -========= - -- Added distribution convenience constructors (N, U, Exp, etc.) - -soerp-0.8.2 -=========== - -- Modified 'error_components' method so that if 'pprint=True', nothing is - returned. - -- Added pickling functionality (an object's before and after counterparts are - not yet correlated, but objects unpickled together maintain prior - correlations) - -- Updated display of class objects from "UF(...)" and "UV(...)" to simply - "uv(...)" for both and created an auxiliary method "describe" to provide a - textual explanation of the distribution moments (i.e., "Mean...", - "Variance...", etc.) - -- Corrected a bug in the "error_components" method that caused the cross-product - components to point to the linear components - -- Removed "raw2central" utility function from being imported automatically since - it is mostly used in internal calculations anyway. - -- Added more documentation to class methods/functions - -- Added ``covariance_matrix`` and ``correlation_matrix`` methods. - -soerp-0.8.1 -=========== - -- Fixed some minor formatting issues to comply with PEP8. - -soerp-0.8 -========= - -- First public release. - - diff --git a/setup.py b/setup.py deleted file mode 100644 index 77213cf..0000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -from setuptools import setup - -def read(fname): - return open(os.path.join(os.path.dirname(__file__), fname)).read() - -setup( - name='soerp', - version='0.9.6', - author='Abraham Lee', - description='Second Order Error Propagation', - author_email='tisimst@gmail.com', - url='https://github.com/tisimst/soerp', - license='BSD License', - long_description=read('README.rst'), - packages=[ - 'soerp', - 'soerp.umath' - ], - install_requires=[ - 'ad', - 'numpy', - 'scipy', - 'matplotlib', - ], - keywords=[ - 'uncertainty analysis', - 'uncertainties', - 'error propagation', - 'second order', - 'derivative', - 'statistics', - 'method of moments', - 'distribution'], - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Education', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.0', - 'Programming Language :: Python :: 3.1', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Topic :: Education', - 'Topic :: Scientific/Engineering', - 'Topic :: Scientific/Engineering :: Mathematics', - 'Topic :: Scientific/Engineering :: Physics', - 'Topic :: Software Development', - 'Topic :: Software Development :: Libraries', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Utilities' - ] - ) diff --git a/soerp/__init__.py b/soerp/__init__.py index b25fdb6..7baf784 100644 --- a/soerp/__init__.py +++ b/soerp/__init__.py @@ -1,1142 +1,100 @@ -# -*- coding: utf-8 -*- """ Created on Tue Apr 9 15:48:17 2013 Overview -------- The ``soerp`` package is the python equivalent of N. D. Cox's original SOERP -code written in Fortran. See the documentation in UncertainVariable for more +code written in Fortran. See the documentation in UncertainVariable for more details and a reference to his work. Credits ------- -A lot of code here was inspired/evolved from the `uncertainties`_ package by +A lot of code here was inspired/evolved from the `uncertainties`_ package by `Eric O. LEBIGOT`_. I'm grateful to him for his support and good work. .. _uncertainties: http://pypi.python.org/pypi/uncertainties .. _Eric O. LEBIGOT: http://www.linkedin.com/pub/eric-lebigot/22/293/277 """ -import math -import numpy as np -try: - from ad import ADF, ADV -except ImportError: - raise -finally: - pass -from .method_of_moments import soerp_numeric -from .method_of_moments import variance_components -from .method_of_moments import variance_contrib -import scipy.stats as ss - -try: - import matplotlib.pyplot as plt -except ImportError: - matplotlib_installed = False -else: - matplotlib_installed = True - -__version_info__ = (0, 9, 6) -__version__ = '.'.join(list(map(str, __version_info__))) - -__author__ = 'Abraham Lee' +from importlib.metadata import PackageNotFoundError, version + +from .distributions import ( + Beta, + Chi2, + Exp, + F, + Gamma, + LogN, + N, + T, + Tri, + U, + Weib, + beta, + chi_squared, + exponential, + f_distribution, + gamma, + log_normal, + normal, + student_t, + triangular, + uniform, + weibull, +) +from .method_of_moments import raw2central +from .statistics import correlation_matrix, covariance_matrix +from .uncertain_function import ( + CONSTANT_TYPES, + UncertainFunction, + _combine_op, + _unary_op, + make_uf_compatible_object, + to_uncertain_func, +) +from .uncertain_variable import UncertainVariable, uv + + +__author__ = "Abraham Lee" + +try: # noqa: RUF067, RUF100 + __version__ = version(__name__) +except PackageNotFoundError: + __version__ = "unknown" __all__ = [ - # the core functions - 'uv', - 'covariance_matrix', - 'correlation_matrix', - # continuous distribution constructors - 'N', - 'U', - 'Exp', - 'Gamma', - 'Beta', - 'LogN', - 'Chi2', - 'F', - 'Tri', - 'T', - 'Weib', - ] - -CONSTANT_TYPES = (float, int, complex, np.number) - -def to_uncertain_func(x): - """ - Transforms x into a constant automatically differentiated UncertainFunction - (UF), unless it already is (in which case x is returned unchanged). - - Raises an exception unless 'x' belongs to some specific classes of - objects that are known not to depend on UncertainFunction objects - (which then cannot be considered as constants). - """ - - if isinstance(x, UncertainFunction): - return x - - #! In Python 2.6+, numbers.Number could be used instead, here: - if isinstance(x, CONSTANT_TYPES): - # No variable => no derivative to define: - return UncertainFunction(x, {}, {}, {}) - -class UncertainFunction(ADF): - """ - UncertainFunction objects represent the uncertainty of a result of - calculations with uncertain variables. Nearly all basic mathematical - operations are supported. - - This class is mostly intended for internal use. - - - """ -# def __init__(self, *args, **kwargs): -# -# # UncertainFunction doesn't need a value, but it must be defined as a -# # place-holder for pickling -# self._hash = None - - def __hash__(self): - return id(self) - - _dist = None - _moments = None - - @property - def mean(self): - """ - Mean value as a result of an uncertainty calculation - """ - mn = self.moments(0) - return mn - - @property - def var(self): - """ - Variance value as a result of an uncertainty calculation - """ - vr = self.moments(1) - return vr - - @property - def std(self): - """ - Standard deviation value as a result of an uncertainty calculation, - defined as:: - - ________ - std = \/variance - - """ - return self.var**0.5 - - @property - def skew(self): - """ - Skewness coefficient value as a result of an uncertainty calculation, - defined as:: - - _____ m3 - \/beta1 = ------ - std**3 - - where m3 is the third central moment and std is the standard deviation - """ - sk = self.moments(2) - return sk - - @property - def kurt(self): - """ - Kurtosis coefficient value as a result of an uncertainty calculation, - defined as:: - - m4 - beta2 = ------ - std**4 - - where m4 is the fourth central moment and std is the standard deviation - """ - kt = self.moments(3) - return kt - - def moments(self, idx=None): - """ - The first four standard moments of a distribution: mean, variance, and - standardized skewness and kurtosis coefficients. - """ - slc, sqc, scp, var_moments, f0 = self._get_inputs_for_soerp() - m = soerp_numeric(slc, sqc, scp, var_moments, f0, silent=True) - if idx is not None: - assert (idx<=3) and (idx>=0), 'idx must be 0, 1, 2, or 3 since ' + \ - 'only the first four moments can be calculated' - return m[idx] - else: - return m - - def _to_general_representation(self, str_func): - m = self.moments() - mn, vr, sk, kt = m[:4] - return ('uv({:}, {:}, {:}, {:})'.format(str_func(mn), str_func(vr), - str_func(sk), str_func(kt)) if any([vr, sk, kt]) else str_func(mn)) - - def __str__(self): - return self._to_general_representation(str) - - def __repr__(self): - return str(self) - - def describe(self): - """ - Cleanly show what the distribution moments are: - - Mean, Variance, Skewness and Kurtosis Coefficients - """ - mn, vr, sk, kt = [self.moments(i) for i in [0, 1, 2, 3]] - s = 'SOERP Uncertain Value:\n' - s += ' > Mean................... {: }\n'.format(mn) - s += ' > Variance............... {: }\n'.format(vr) - s += ' > Skewness Coefficient... {: }\n'.format(sk) - s += ' > Kurtosis Coefficient... {: }\n'.format(kt) - print(s) - - def _get_inputs_for_soerp(self): - """ - This hidden method prepares the related variable moments and derivatives - in preparation for method of moment calculations by standardizing the - derivatives by moving the distribution to the origin and normalizing - them with the distribution's standard deviation. - """ - # any original variables will have a first derivative, so we can grab - # them from that dictionary - variables = self.d().keys() - nvar = len(variables) - - # standardize the input derivatives - # - slc: linear terms - # - sqc: pure quadratic terms - # - scp: cross quadratic terms - slc = np.array([self.d(v)*v.std for v in variables]) - sqc = np.array([0.5*self.d2(v)*v.var for v in variables]) - scp = np.zeros((nvar, nvar)) - for i,v1 in enumerate(variables): - for j,v2 in enumerate(variables): - if hash(v1)!=hash(v2): -# if v1._trace!=v2._trace: - scp[i,j] = self.d2c(v1, v2)*v1.std*v2.std - else: - scp[i,j] = 0.0 - - # construct the arrays of standardized moments in since this is what - # the method of moments calculations require - var_moments = np.array([[1, 0, 1] + list(v._moments[2:]) - for v in variables]) - - f0 = self.x # from evaluation at input means - - inputs = (slc, sqc, scp, var_moments, f0) - - return inputs - - def error_components(self, pprint=False, as_eq_terms=False): - """ - The parts of the second order approximation of the variance function, - returned in three pieces if ``as_eq_terms`` = True, first-order - components, pure-quadratic components, and cross-product components, - otherwise the error components from the linear terms are added to the - corresponding error components from the quadratic terms. Any - cross-product term components are divided equally between the two - factors of the cross-product. - - Optional - -------- - pprint : bool, default is False, - Pretty-print the error components, showing both the component and - the percent contribution of the component - as_eq_terms : bool, default is False, - True to return the error components in the form of the equation - terms (pure linear, pure quadratic, and cross-product), where both - orders are available for the cross-product terms (i.e., (x, y) and - (y, x) will be returned in the cross-product terms), otherwise in - terms of the contributing UncertainVariables. - - Returns - ------- - err_comp : dict - A dictionary that maps the error components to the contributing - UncertainVariables. If ``as_eq_terms=True``, then a tuple of three - dictionaries is returned containing the 1) linear, 2) pure - quadratic, and 3) cross-product term contributions). - - Example - -------- - If we had a function of two variables ended up with the linear terms - (``as_eq_terms`` = False here), :: - - >>> lc = {x:0.5, y:0.25} - - the quadratic terms:: - - >>> qc = {x:0.2, y:0.1} - - and the cross-product term:: - - >>> cp = {(x, y}:0.14} - - then the variables would be given the error components like this:: - - >>> lc[x] + qc[x] + 0.5*cp[(x, y)] # first variable, x - 0.77 - >>> lc[y] + qc[y] + 0.5*cp[(x, y)] # second variable, y - 0.42 - - """ - variables = self.d().keys() - slc, sqc, scp, var_moments, f0 = self._get_inputs_for_soerp() - vz = self.moments() - - # convert standardized moments back to central moments - vz[2] = vz[2]*vz[1]**1.5 - vz[3] = vz[3]*vz[1]**2 - vz = [1] + vz # the [1] is needed in the method of moment calculations - vlc, vqc, vcp = variance_components(slc, sqc, scp, var_moments, vz) - - vc_lc = {} - vc_qc = {} - vc_cp = {} - - for i,v1 in enumerate(variables): - # first-order terms - vc_lc[v1] = vlc[i] - # second-order terms (pure) - vc_qc[v1] = vqc[i] - # second-order terms (cross-product, both orientations returned) - for j, v2 in enumerate(variables): - if ival - - def __nonzero__(self): - return self!=0 - - def sqrt(self): - return _make_UF_compatible_object(ADF.sqrt(self)) - -def _make_UF_compatible_object(tmp): - if isinstance(tmp, ADF): - return UncertainFunction(tmp.x, tmp.d(), tmp.d2(), tmp.d2c()) - else: # for scalars, etc. - return tmp - -################################################################################ - -class UncertainVariable(UncertainFunction, ADV): - """ - UncertainVariable objects track the effects of uncertainty, characterized - in terms of the first four standard moments of statistical distributions - (mean, variance, skewness and kurtosis coefficients). Most texts - only deal with first-order models, but this class uses a full second - order model, which requires a knowledge of the first eight central moments - of a distribution. - - Parameters - ---------- - moments : array-like, optional - The first eight moments (standardized) of the uncertain variable's - underlying statistical distribution (the first two values should be the - mean and variance) - - rv : scipy.stats.rv_continous, optional - If supplied, the ``moments`` kwarg is ignored and the first eight - standardized moments are calculated internally - - tag : str, optional - A string identifier when information about this variable is printed to - the screen - - Notes - ----- - - For a full report on the methods behind this class, see: - - N. D. Cox, "Tolerance Analysis by Computer," Journal of Quality - Technology, Vol. 11, No. 2, 1979. - - Here are the first eight moments of some standard distributions: - - - Normal Distribution: [0, 1, 0, 3, 0, 15, 0, 105] - - Uniform Distribution: [0, 1, 0, 1.8, 0, 3.857, 0, 9] - - Exponential Distribution: [0, 1, 2, 9, 44, 265, 1854, 14833] - - A distribution's raw moment (moment about the origin) is defined as:: - - oo - / - | - k | k - E(x ) = | x *f(x) dx - | - / - -oo - - where E(...) is the expectation operator, k is the order of the moment, and - f(x) is the probability density function (pdf) of x. - - To convert these to central moments (moment about the mean), we can simply - use the helper function:: - - >>> moments = raw2central(raw_moments) - - or we can use the mathematical definition to calculate the kth moment as:: - - oo - / - | - k | k - E((x-mu) ) = | (x-mu) *f(x) dx - | - / - -oo - - This then needs to be standardized by normalizing each of the moments - (starting with the third moment) using the standard deviation:: - - >>> sd = moment[1]**0.5 - >>> moment[k] = [moment[k]/sd**(k + 1) for k in range(2, 9)] - - The ``scipy.stats`` module contains many distributions from which we can - easily generate these moments for any distribution. Currently, only - ``rv_continuous`` distributions are supported. It is important to follow - the initialization syntax for creating any kind of rv_continuous object: - - - *Location* and *Scale* values must use the kwargs ``loc`` and - ``scale`` - - *Shape* values are passed in as arguments before the location and - scale - - The mathematical operations that can be performed on Uncertain... objects - will work for any moments or distribution supplied, but may not be - misleading if the supplied moments or distribution is not accurately - defined. Here are some guidelines for creating UncertainVariable objects - using some of the most common statistical distributions: - - +---------------------------+-------------+-------------------+-----+---------+ - | Distribution | scipy.stats | args | loc | scale | - | | class name | (shape params) | | | - +===========================+=============+===================+=====+=========+ - | Normal(mu, sigma) | norm | | mu | sigma | - +---------------------------+-------------+-------------------+-----+---------+ - | Uniform(a, b) | uniform | | a | b-a | - +---------------------------+-------------+-------------------+-----+---------+ - | Exponential(lamda) | expon | | | 1/lamda | - +---------------------------+-------------+-------------------+-----+---------+ - | Gamma(k, theta) | gamma | k | | theta | - +---------------------------+-------------+-------------------+-----+---------+ - | Beta(alpha, beta, [a, b]) | beta | alpha, beta | a | b-a | - +---------------------------+-------------+-------------------+-----+---------+ - | Log-Normal(mu, sigma) | lognorm | sigma | mu | | - +---------------------------+-------------+-------------------+-----+---------+ - | Chi-Square(k) | chi2 | k | | | - +---------------------------+-------------+-------------------+-----+---------+ - | F(d1, d2) | f | d1, d2 | | | - +---------------------------+-------------+-------------------+-----+---------+ - | Triangular(a, b, c) | triang | c | a | b-a | - +---------------------------+-------------+-------------------+-----+---------+ - | Student-T(v) | t | v | | | - +---------------------------+-------------+-------------------+-----+---------+ - | Weibull(lamda, k) | exponweib | lamda, k | | | - +---------------------------+-------------+-------------------+-----+---------+ - - Thus, each distribution above would have the same call signature:: - - >>> import scipy.stats as ss - >>> ss.your_dist_here(args,loc=loc,scale=scale) - - Convenient constructors have been created to make assigning these - distributions easier. They follow the parameter notation found in the - respective Wikipedia articles: - - +---------------------------+---------------------------------------------------------------+ - | MCERP Distibution | Wikipedia page | - +===========================+===============================================================+ - | N(mu, sigma) | http://en.wikipedia.org/wiki/Normal_distribution | - +---------------------------+---------------------------------------------------------------+ - | U(a, b) | http://en.wikipedia.org/wiki/Uniform_distribution_(continuous)| - +---------------------------+---------------------------------------------------------------+ - | Exp(lamda, [mu]) | http://en.wikipedia.org/wiki/Exponential_distribution | - +---------------------------+---------------------------------------------------------------+ - | Gamma(k, theta) | http://en.wikipedia.org/wiki/Gamma_distribution | - +---------------------------+---------------------------------------------------------------+ - | Beta(alpha, beta, [a, b]) | http://en.wikipedia.org/wiki/Beta_distribution | - +---------------------------+---------------------------------------------------------------+ - | LogN(mu, sigma) | http://en.wikipedia.org/wiki/Log-normal_distribution | - +---------------------------+---------------------------------------------------------------+ - | X2(df) | http://en.wikipedia.org/wiki/Chi-squared_distribution | - +---------------------------+---------------------------------------------------------------+ - | F(dfn, dfd) | http://en.wikipedia.org/wiki/F-distribution | - +---------------------------+---------------------------------------------------------------+ - | Tri(a, b, c) | http://en.wikipedia.org/wiki/Triangular_distribution | - +---------------------------+---------------------------------------------------------------+ - | T(df) | http://en.wikipedia.org/wiki/Student's_t-distribution | - +---------------------------+---------------------------------------------------------------+ - | Weib(lamda, k) | http://en.wikipedia.org/wiki/Weibull_distribution | - +---------------------------+---------------------------------------------------------------+ - - - Thus, the following are equivalent:: - - >>> x = uv([10, 1, 0, 3, 0, 15, 0, 105]) - >>> x = uv(rv=ss.norm(loc=10, scale=1)) - >>> x = N(10, 1) - - Examples - -------- - Using the first eight distribution moments:: - - >>> x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) # normally distributed - >>> x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) # normally distributed - >>> x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) # exp. distributed - >>> Z = (x1*x2**2)/(15*(1.5 + x3)) - >>> Z - uv(1176.45, 99699.6822919, 0.708013052954, 6.16324345122) - - The result shows the mean, variance, and standardized skewness and kurtosis - of the output variable Z. - - Same example, but now using ``scipy.stats`` objects:: - - >>> import scipy.stats as ss - >>> x1 = uv(rv=ss.norm(loc=24, scale=1)) # normally distributed - >>> x2 = uv(rv=ss.norm(loc=37, scale=4)) # normally distributed - >>> x3 = uv(rv=ss.expon(scale=0.5)) # exponentially distributed - - Or using the convenient distribution constructors:: - - >>> x1 = N(24, 1) - >>> x2 = N(37, 4) - >>> x3 = Exp(2) - - The results may be slightly different from using the moments manually since - moment calculations can suffer from numerical errors during the integration - of the expectation equations above, but they will be close enough. - - Basic math operations may be applied to distributions, where all - statistical calculations are performed using method of moments. Built-in - trig-, logarithm-, etc. functions should be used when possible since they - support both scalar values and uncertain objects. - - At any time, the 8 standardized moments of variables (or the 4 that result - from calculations) can be retrieved using:: - - >>> x1.moments() - [24.0, 1.0, 0.0, 3.0, 0.0, 15.0, 0.0, 105.0] - - Or any moment can be accessed directly by specifying its index:: - - >>> Z.moments(1) # variance - 99699.6822919 - - Important - --------- - - One final thing to note is that some answers suffer from the use of a - second-order approximation to the method of moment equations. For example, - the equation f(x) = x*sin(x) has this issue:: - - >>> x = N(0, 1) # standard normal distribution - >>> x*sin(x) - uv(1.0, 2.0, 2.82842712475, 15.0) - - This is the precise answer for f(x) = x**2, which just so happens to be the - second-order Taylor series approximation of x*sin(x). The correct answer - for [mean,variance,skewness,kurtosis] here can be calculated by:: - - >>> mu = 0.0 - >>> sigma = 1.0 - >>> n = ss.norm(loc=mu, scale=sigma) - >>> rm = [n.dist.expect(lambda x: (x*math.sin(x))**k, loc=mu, - ... scale=sigma) for k in (1, 2, 3, 4)] - >>> cm = raw2central(rm) - >>> mean = rm[0] - >>> var = cm[1] - >>> std = var**0.5 - >>> skew = cm[2]/std**3 - >>> kurt = cm[3]/std**4 - >>> [mean, var, skew, kurt] - [0.6065306597, 0.3351234837, 0.6539519888, 2.5584134397] - - Thus, care should be taken to make sure that the equations used are - effectively quadratic within the respective input variable distribution - ranges or you will see approximation errors like the example above. - - """ - - def __init__(self, moments=[], rv=None, tag=None): - assert not all([not moments, not rv]), 'Either the moments must be ' + \ - 'put in manually or a "rv_continuous" object from the ' + \ - '"scipy.stats" module must be supplied' - - if rv is not None: - loc = rv.kwds.get('loc', 0.0) - scale = rv.kwds.get('scale', 1.0) - shape = rv.args - - mn = rv.mean() - sd = rv.std() - - # since the distribution parameters can only passed in as an arg - # OR a kwd, check the kwd as well if there wasn't any luck with the - # args above, otherwise, default to loc = 0 and scale = 1 - - if shape: - assert rv.dist.numargs>=1, 'The distribution provided ' + \ - "doesn't support a 'shape' parameter" - - expect = lambda k: rv.dist.expect(lambda x: x**k, args=shape, - loc=loc, scale=scale) - raw_moments = [expect(k) for k in range(1, 9)] - moments = raw2central(list(raw_moments)) - for k in range(2, 8): - moments[k] = moments[k]/sd**(k + 1) - - else: - assert rv.dist.numargs==0, 'The distribution provided ' + \ - "requires a third 'shape' parameter" - - expect = lambda k: rv.dist.expect(lambda x: x**k) - raw_moments = [expect(k) for k in range(1, 9)] - moments = raw2central(list(raw_moments)) - - # update the 1st and second moment values for SOERP calculations - moments[0] = mn # mean - moments[1] = sd**2 # variance - - self._dist = rv - - else: - self._dist = None - - ADV.__init__(self, moments[0], tag=tag) - self._moments = moments - - def __hash__(self): - return id(self) - - @property - def mean(self): - return self.moments(0) - - @property - def var(self): - return self.moments(1) - - @property - def std(self): - return self.var**0.5 - - @property - def skew(self): - return self.moments(2) - - @property - def kurt(self): - return self.moments(3) - - def moments(self, idx=None): - if idx is not None and idx0, 'Sigma must be positive' - return uv(rv=ss.norm(loc=mu, scale=sigma), tag=tag) - -############################################################################### - -def U(a, b, tag=None): - """ - A Uniform random variate - - Parameters - ---------- - low : scalar - Lower bound of the distribution support. - high : scalar - Upper bound of the distribution support. - """ - assert a0 and theta>0, 'Gamma parameters must be greater than zero' - return uv(rv=ss.gamma(k, scale=theta), tag=tag) - -############################################################################### - -def Beta(alpha, beta, a=0, b=1, tag=None): - """ - A Beta random variate - - Parameters - ---------- - alpha : scalar - The first shape parameter - beta : scalar - The second shape parameter - - Optional - -------- - a : scalar - Lower bound of the distribution support (default=0) - b : scalar - Upper bound of the distribution support (default=1) - """ - assert alpha>0 and beta>0, 'Shape parameters must be greater than zero' - return uv(rv=ss.beta(alpha, beta, loc=a, scale=b-a), tag=tag) - -############################################################################### - -def LogN(mu, sigma, tag=None): - """ - A Log-Normal random variate - - Parameters - ---------- - mu : scalar - The location parameter - sigma : scalar - The scale parameter (must be positive and non-zero) - """ - assert sigma>0, 'Sigma must be positive' - return uv(rv=ss.lognorm(sigma, loc=mu), tag=tag) - -############################################################################### - -def Chi2(df, tag=None): - """ - A Chi-Squared random variate - - Parameters - ---------- - df : int - The degrees of freedom of the distribution (must be greater than one) - """ - assert isinstance(df, int) and df>1, 'DF must be an int greater than 1' - return uv(rv=ss.chi2(df), tag=tag) - -############################################################################### - -def F(d1, d2, tag=None): - """ - An F (fisher) random variate - - Parameters - ---------- - d1 : int - Numerator degrees of freedom - d2 : int - Denominator degrees of freedom - """ - assert isinstance(d1, int) and d1>1, 'd1 must be an int greater than 1' - assert isinstance(d2, int) and d2>1, 'd2 must be an int greater than 1' - return uv(rv=ss.f(d1, d2), tag=tag) - -############################################################################### - -def Tri(a, b, c, tag=None): - """ - A triangular random variate - - Parameters - ---------- - a : scalar - Lower bound of the distribution support (default=0) - b : scalar - Upper bound of the distribution support (default=1) - c : scalar - The location of the triangle's peak (a <= c <= b) - """ - assert a<=c<=b, 'peak must lie in between low and high' - return uv(rv=ss.triang(c, loc=a, scale=b-a), tag=tag) - -############################################################################### - -def T(v, tag=None): - """ - A Student-T random variate - - Parameters - ---------- - v : int - The degrees of freedom of the distribution (must be greater than one) - """ - assert isinstance(v, int) and v>1, 'v must be an int greater than 1' - return uv(rv=ss.t(v), tag=tag) - -############################################################################### - -def Weib(lamda, k, tag=None): - """ - A Weibull random variate - - Parameters - ---------- - lamda : scalar - The scale parameter - k : scalar - The shape parameter - """ - assert lamda>0 and k>0, 'Weibull scale and shape parameters must be greater than zero' - return uv(rv=ss.exponweib(lamda, k), tag=tag) - -############################################################################### - -def raw2central(v): - """Convert raw moments (1 to len(v)) to central moments""" - def nci(n,i): - return math.factorial(n)/(math.factorial(i)*math.factorial(n-i)) - - v = [1] + v - central_moments = [] - for k in range(len(v)): - val = 0.0 - - # use the recursion definition to transform - for j in range(k + 1): - val += (-1)**j*nci(k,j)*v[k - j]*v[1]**j - central_moments.append(val) - - return central_moments[1:] - -def covariance_matrix(nums_with_uncert): - """ - Calculate the covariance matrix of uncertain variables, oriented by the - order of the inputs - - Parameters - ---------- - nums_with_uncert : array-like - A list of variables that have an associated uncertainty - - Returns - ------- - cov_matrix : 2d-array-like - A nested list containing covariance values - - Example - ------- - - >>> x = N(1, 0.1) - >>> y = N(10, 0.1) - >>> z = x + 2*y - >>> covariance_matrix([x, y, z]) - [[ 0.01 0. 0.01] - [ 0. 0.01 0.02] - [ 0.01 0.02 0.05]] - - """ - ufuncs = list(map(to_uncertain_func, nums_with_uncert)) - cov_matrix = [] - for (i1, expr1) in enumerate(ufuncs): - derivatives1 = expr1._lc # Optimization - vars1 = set(derivatives1) - coefs_expr1 = [] - for (i2, expr2) in enumerate(ufuncs[:i1 + 1]): - derivatives2 = expr2._lc # Optimization - coef = 0. - for v in vars1.intersection(derivatives2): - # v is a variable common to both numbers with - # uncertainties: - coef += (derivatives1[v]*derivatives2[v]*v.var) - coefs_expr1.append(coef) - cov_matrix.append(coefs_expr1) - - # We symmetrize the matrix: - for (i, covariance_coefs) in enumerate(cov_matrix): - covariance_coefs.extend(cov_matrix[j][i] - for j in range(i + 1, len(cov_matrix))) - - return cov_matrix - -def correlation_matrix(nums_with_uncert): - """ - Calculate the correlation matrix of uncertain variables, oriented by the - order of the inputs - - Parameters - ---------- - nums_with_uncert : array-like - A list of variables that have an associated uncertainty - - Returns - ------- - corr_matrix : 2d-array-like - A nested list containing covariance values - - Example - ------- - - >>> x = N(1, 0.1) - >>> y = N(10, 0.1) - >>> z = x + 2*y - >>> correlation_matrix([x, y, z]) - [[ 1. 0. 0.4472136 ] - [ 0. 1. 0.89442719] - [ 0.4472136 0.89442719 1. ]] - - """ - ufuncs = list(map(to_uncertain_func, nums_with_uncert)) - cov_matrix = covariance_matrix(ufuncs) - corr_matrix = [] - for (i1, expr1) in enumerate(ufuncs): - row_data = [] - for (i2, expr2) in enumerate(ufuncs): - row_data.append(cov_matrix[i1][i2]/expr1.std/expr2.std) - corr_matrix.append(row_data) - return corr_matrix - + "CONSTANT_TYPES", + "Beta", + "Chi2", + "Exp", + "F", + "Gamma", + "LogN", + "N", + "T", + "Tri", + "U", + "UncertainFunction", + "UncertainVariable", + "Weib", + "_combine_op", + "_unary_op", + "beta", + "chi_squared", + "correlation_matrix", + "covariance_matrix", + "exponential", + "f_distribution", + "gamma", + "log_normal", + "make_uf_compatible_object", + "normal", + "raw2central", + "student_t", + "to_uncertain_func", + "triangular", + "uniform", + "uv", + "weibull", +] diff --git a/soerp/distributions.py b/soerp/distributions.py new file mode 100644 index 0000000..73166eb --- /dev/null +++ b/soerp/distributions.py @@ -0,0 +1,348 @@ +import scipy.stats as ss + +from .uncertain_variable import UncertainVariable, uv + + +def normal( + mu: float, sigma: float, tag: str | None = None +) -> UncertainVariable: + """ + A Normal (or Gaussian) random variate + + Parameters + ---------- + mu : scalar + The mean value of the distribution + sigma : scalar + The standard deviation (must be positive and non-zero) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Normal distribution. + + Raises + ------ + ValueError + If sigma <= 0. + """ + if sigma <= 0: + raise ValueError("Sigma must be positive") + return uv(rv=ss.norm(loc=mu, scale=sigma), tag=tag) + + +############################################################################### + + +def uniform(a: float, b: float, tag: str | None = None) -> UncertainVariable: + """ + A Uniform random variate + + Parameters + ---------- + a : scalar + Lower bound of the distribution support. + b : scalar + Upper bound of the distribution support. + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Uniform distribution. + + Raises + ------ + ValueError + If a >= b. + """ + if a >= b: + raise ValueError("Lower bound must be less than the upper bound") + return uv(rv=ss.uniform(loc=a, scale=b - a), tag=tag) + + +############################################################################### + + +def exponential(lamda: float, tag: str | None = None) -> UncertainVariable: + """ + An Exponential random variate + + Parameters + ---------- + lamda : scalar + The inverse scale (as shown on Wikipedia), FYI: mu = 1/lamda. + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with an Exponential distribution. + """ + return uv(rv=ss.expon(scale=1.0 / lamda), tag=tag) + + +############################################################################### + + +def gamma(k: float, theta: float, tag: str | None = None) -> UncertainVariable: + """ + A Gamma random variate + + Parameters + ---------- + k : scalar + The shape parameter (must be positive and non-zero) + theta : scalar + The scale parameter (must be positive and non-zero) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Gamma distribution. + + Raises + ------ + ValueError + If k <= 0 or theta <= 0. + """ + if not (k > 0 and theta > 0): + raise ValueError("Gamma parameters must be greater than zero") + return uv(rv=ss.gamma(k, scale=theta), tag=tag) + + +############################################################################### + + +def beta( + alpha: float, + beta: float, + a: float = 0, + b: float = 1, + tag: str | None = None, +) -> UncertainVariable: + """ + A Beta random variate + + Parameters + ---------- + alpha : scalar + The first shape parameter + beta : scalar + The second shape parameter + + Optional + -------- + a : scalar + Lower bound of the distribution support (default=0) + b : scalar + Upper bound of the distribution support (default=1) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Beta distribution. + + Raises + ------ + ValueError + If alpha <= 0 or beta <= 0. + """ + if not (alpha > 0 and beta > 0): + raise ValueError("Shape parameters must be greater than zero") + return uv(rv=ss.beta(alpha, beta, loc=a, scale=b - a), tag=tag) + + +############################################################################### + + +def log_normal( + mu: float, sigma: float, tag: str | None = None +) -> UncertainVariable: + """ + A Log-Normal random variate + + Parameters + ---------- + mu : scalar + The location parameter + sigma : scalar + The scale parameter (must be positive and non-zero) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Log-Normal distribution. + + Raises + ------ + ValueError + If sigma <= 0. + """ + if sigma <= 0: + raise ValueError("Sigma must be positive") + return uv(rv=ss.lognorm(sigma, loc=mu), tag=tag) + + +############################################################################### + + +def chi_squared(df: int, tag: str | None = None) -> UncertainVariable: + """ + A Chi-Squared random variate + + Parameters + ---------- + df : int + The degrees of freedom of the distribution (must be greater than one) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Chi-Squared distribution. + + Raises + ------ + ValueError + If df is not an int greater than 1. + """ + if not (isinstance(df, int) and df > 1): + raise ValueError("DF must be an int greater than 1") + return uv(rv=ss.chi2(df), tag=tag) + + +############################################################################### + + +def f_distribution( + d1: int, d2: int, tag: str | None = None +) -> UncertainVariable: + """ + An F (fisher) random variate + + Parameters + ---------- + d1 : int + Numerator degrees of freedom + d2 : int + Denominator degrees of freedom + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with an F distribution. + + Raises + ------ + ValueError + If d1 or d2 is not an int greater than 1. + """ + if not (isinstance(d1, int) and d1 > 1): + raise ValueError("d1 must be an int greater than 1") + if not (isinstance(d2, int) and d2 > 1): + raise ValueError("d2 must be an int greater than 1") + return uv(rv=ss.f(d1, d2), tag=tag) + + +############################################################################### + + +def triangular( + a: float, b: float, c: float, tag: str | None = None +) -> UncertainVariable: + """ + A triangular random variate + + Parameters + ---------- + a : scalar + Lower bound of the distribution support (default=0) + b : scalar + Upper bound of the distribution support (default=1) + c : scalar + The location of the triangle's peak (a <= c <= b) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Triangular distribution. + + Raises + ------ + ValueError + If c is not between a and b (inclusive). + """ + if not (a <= c <= b): + raise ValueError("peak must lie in between low and high") + return uv(rv=ss.triang(c, loc=a, scale=b - a), tag=tag) + + +############################################################################### + + +def student_t(v: int, tag: str | None = None) -> UncertainVariable: + """ + A Student-T random variate + + Parameters + ---------- + v : int + The degrees of freedom of the distribution (must be greater than one) + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Student-T distribution. + + Raises + ------ + ValueError + If v is not an int greater than 1. + """ + if not (isinstance(v, int) and v > 1): + raise ValueError("v must be an int greater than 1") + return uv(rv=ss.t(v), tag=tag) + + +############################################################################### + + +def weibull( + lamda: float, k: float, tag: str | None = None +) -> UncertainVariable: + """ + A Weibull random variate + + Parameters + ---------- + lamda : scalar + The scale parameter + k : scalar + The shape parameter + + Returns + ------- + rv : UncertainVariable + An UncertainVariable with a Weibull distribution. + + Raises + ------ + ValueError + If lamda <= 0 or k <= 0. + """ + if not (lamda > 0 and k > 0): + raise ValueError( + "Weibull scale and shape parameters must be greater than zero" + ) + return uv(rv=ss.exponweib(lamda, k), tag=tag) + + +N = normal +U = uniform +Exp = exponential +Gamma = gamma +Beta = beta +LogN = log_normal +Chi2 = chi_squared +F = f_distribution +Tri = triangular +T = student_t +Weib = weibull diff --git a/soerp/method_of_moments.py b/soerp/method_of_moments.py index c41a47e..6da700a 100644 --- a/soerp/method_of_moments.py +++ b/soerp/method_of_moments.py @@ -1,64 +1,77 @@ -import numpy as np +import math from copy import copy -assume_linear = False # if True, the sqc and scp parts are ignored +import numpy as np +from numpy.typing import NDArray + + +assume_linear = False # if True, the sqc and scp parts are ignored ############################################################################### -def standard_lc(lc, stdevs): + +def standard_lc(lc: NDArray, stdevs: NDArray) -> NDArray: """ Standardizes the first derivatives in preparation for moment calculation. - + Parameters ---------- lc : array The first partial derivatives of a single output. stdevs : array The standard deviations for each input - + Returns ------- slc : array The standardized first-order derivatives """ - return np.array([coef*stdev for coef, stdev in zip(lc, stdevs)]) + return np.array([ + coef * stdev for coef, stdev in zip(lc, stdevs, strict=False) + ]) + ############################################################################### -def standard_qc(qc, stdevs): + +def standard_qc(qc: NDArray, stdevs: NDArray) -> NDArray: """ - Standardizes the pure second derivatives in preparation for moment + Standardizes the pure second derivatives in preparation for moment calculation. - + Parameters ---------- qc : array The pure second partial derivatives of a single output. stdevs : array The standard deviations for each input - + Returns ------- sqc : array The standardized pure second-order derivatives """ - return np.array([coef*stdev**2 for coef, stdev in zip(qc, stdevs)]) + return np.array([ + coef * stdev**2 for coef, stdev in zip(qc, stdevs, strict=False) + ]) + ############################################################################### -def standard_cp(cp, stdevs): + +def standard_cp(cp: NDArray, stdevs: NDArray) -> NDArray: """ - Standardizes the cross-product second derivatives in preparation for + Standardizes the cross-product second derivatives in preparation for moment calculation. - + Parameters ---------- cp : 2d-array - The cross-product second-order partial derivatives matrix of a single + The cross-product second-order partial derivatives matrix of a single output. stdevs : array The standard deviations for each input - + Returns ------- scp : 2d-array @@ -68,19 +81,23 @@ def standard_cp(cp, stdevs): scp = np.empty_like(cp) if nvars >= 2: for i in range(nvars): - for j in range(i+1, nvars): - scp[i,j] = cp[i,j]*stdevs[i]*stdevs[j] - scp[j,i] = scp[i,j] + for j in range(i + 1, nvars): + scp[i, j] = cp[i, j] * stdevs[i] * stdevs[j] + scp[j, i] = scp[i, j] return scp + ############################################################################### -def standardize(lc, qc, cp, stdevs): + +def standardize( + lc: NDArray, qc: NDArray, cp: NDArray, stdevs: NDArray +) -> tuple[NDArray, NDArray, NDArray]: """ - A helper function to convert normal first and second-order partial + A helper function to convert normal first and second-order partial derivatives to "standardized" partial derivatives, in preparation for moment calculations. - + Parameters ---------- lc : array @@ -88,11 +105,11 @@ def standardize(lc, qc, cp, stdevs): qc : array The pure second partial derivatives of a single output. cp : 2d-array - The cross-product second-order partial derivatives matrix of a single + The cross-product second-order partial derivatives matrix of a single output. stdevs : array The standard deviations for each input - + Returns ------- slc : array @@ -107,21 +124,25 @@ def standardize(lc, qc, cp, stdevs): scp = standard_cp(cp, stdevs) return slc, sqc, scp + ############################################################################### -def rawmoment(slc, sqc, scp, vm, k): + +def rawmoment( # noqa: PLR0912, PLR0915 + slc: NDArray, sqc: NDArray, scp: NDArray, vm: NDArray, k: int +) -> float: """ - This is where the resultant distribution moments are calculated. MODIFY + This is where the resultant distribution moments are calculated. MODIFY THIS CODE AT YOUR OWN RISK. These equations have been verified with published equations and example problems by N.D. Cox. - + Each of the derivative components need to be standardized prior to input to this function. This means multiplying them by their respective standard deviations, depending on the order of the derivative. Helper functions have been defined for this purpose (standard_lc, standard_qc, and standard_cp). However, this is only necessary if manually calling this function rather than using soerp_numeric below. - + Parameters ---------- slc : array @@ -131,12 +152,12 @@ def rawmoment(slc, sqc, scp, vm, k): scp : 2d-array The standardized cross-product second derivative terms vm : 2d-array - The first 9 (starting at 0) standardized distribution moments (one - row for each input variable, corresponding to the derivative array + The first 9 (starting at 0) standardized distribution moments (one + row for each input variable, corresponding to the derivative array order). See the documentation for ``soerp_numeric`` for more details. k : int The kth distribution moment to calculate. - + Returns ------- rm : scalar @@ -146,276 +167,587 @@ def rawmoment(slc, sqc, scp, vm, k): qc = copy(sqc) cp = copy(scp) n = len(lc) - + if assume_linear: qc[:] = 0.0 - cp[:,:] = 0.0 - + cp[:, :] = 0.0 + ans = 0.0 - + ############################ # The 0th raw moment - - if k==0: + + if k == 0: # noqa: PLR1702 ans = 1 - + ############################ # The 1st raw moment - - elif k==1: + + elif k == 1: for i in range(n): - ans += qc[i]*vm[i,2] - + ans += qc[i] * vm[i, 2] + ############################ # The 2nd raw moment - - elif k==2: + + elif k == 2: for i in range(n): - ans += lc[i]**2*vm[i,2] + 2*lc[i]*qc[i]*vm[i,3] + qc[i]**2*vm[i,4] - - if n>=2: - for i in range(n-1): - for j in range(i+1, n): - ans += (2*qc[i]*qc[j] + cp[i,j]**2)*vm[i,2]*vm[j,2] - + ans += ( + lc[i] ** 2 * vm[i, 2] + + 2 * lc[i] * qc[i] * vm[i, 3] + + qc[i] ** 2 * vm[i, 4] + ) + + if n >= 2: + for i in range(n - 1): + for j in range(i + 1, n): + ans += ( + (2 * qc[i] * qc[j] + cp[i, j] ** 2) + * vm[i, 2] + * vm[j, 2] + ) + ############################ # The 3rd raw moment - - elif k==3: + + elif k == 3: for i in range(n): - ans += lc[i]**3*vm[i,3] + qc[i]**3*vm[i,6] + \ - 3*lc[i]**2*qc[i]*vm[i,4] + 3*lc[i]*qc[i]**2*vm[i,5] - - if n>=2: - for i in range(n-1): - for j in range(i+1, n): - ans += cp[i,j]**3*vm[i,3]*vm[j,3] + \ - 6*lc[i]*lc[j]*cp[i,j]*vm[i,2]*vm[j,2] + \ - 6*qc[i]*qc[j]*cp[i,j]*vm[i,3]*vm[j,3] + ans += ( + lc[i] ** 3 * vm[i, 3] + + qc[i] ** 3 * vm[i, 6] + + 3 * lc[i] ** 2 * qc[i] * vm[i, 4] + + 3 * lc[i] * qc[i] ** 2 * vm[i, 5] + ) + + if n >= 2: + for i in range(n - 1): + for j in range(i + 1, n): + ans += ( + cp[i, j] ** 3 * vm[i, 3] * vm[j, 3] + + 6 * lc[i] * lc[j] * cp[i, j] * vm[i, 2] * vm[j, 2] + + 6 * qc[i] * qc[j] * cp[i, j] * vm[i, 3] * vm[j, 3] + ) for i in range(n): for j in range(n): - if j!=i: - ans += 3*qc[i]**2*vm[i,4]*qc[j]*vm[j,2] + \ - 6*lc[i]*qc[j]*cp[i,j]*vm[i,2]*vm[j,3] +\ - 3*qc[i]*lc[j]**2*vm[i,2]*vm[j,2] + \ - 6*lc[i]*qc[i]*qc[j]*vm[i,3]*vm[j,2] + \ - 3*lc[i]*cp[i,j]**2*vm[i,3]*vm[j,2] + \ - 3*qc[i]*cp[i,j]**2*vm[i,4]*vm[j,2] - - if n>=3: - for i in range(n-2): - for j in range(i+1, n-1): - for k in range(j+1, n): - ans += (6*qc[i]*qc[j]*qc[k] + \ - 6*cp[i,j]*cp[i,k]*cp[j,k] + - 3*(qc[i]*cp[j,k]**2 + \ - qc[j]*cp[i,k]**2 + \ - qc[k]*cp[i,j]**2))*vm[i,2]*vm[j,2]*vm[k,1] - + if j != i: + ans += ( + 3 * qc[i] ** 2 * vm[i, 4] * qc[j] * vm[j, 2] + + 6 * lc[i] * qc[j] * cp[i, j] * vm[i, 2] * vm[j, 3] + + 3 * qc[i] * lc[j] ** 2 * vm[i, 2] * vm[j, 2] + + 6 * lc[i] * qc[i] * qc[j] * vm[i, 3] * vm[j, 2] + + 3 * lc[i] * cp[i, j] ** 2 * vm[i, 3] * vm[j, 2] + + 3 * qc[i] * cp[i, j] ** 2 * vm[i, 4] * vm[j, 2] + ) + + if n >= 3: + for i in range(n - 2): + for j in range(i + 1, n - 1): + for m in range(j + 1, n): + ans += ( + ( + 6 * qc[i] * qc[j] * qc[m] + + 6 * cp[i, j] * cp[i, m] * cp[j, m] + + 3 + * ( + qc[i] * cp[j, m] ** 2 + + qc[j] * cp[i, m] ** 2 + + qc[m] * cp[i, j] ** 2 + ) + ) + * vm[i, 2] + * vm[j, 2] + * vm[m, 1] + ) + ############################ # The 4th raw moment - - elif k==4: + + elif k == 4: for i in range(n): - ans += lc[i]**4*vm[i,4] + qc[i]**4*vm[i,8] + \ - 4*lc[i]**3*qc[i]*vm[i,5] + 4*lc[i]*qc[i]**3*vm[i,7] + \ - 6*lc[i]**2*qc[i]**2*vm[i,6] - - if n>=2: - for i in range(n-1): - for j in range(i+1, n): - ans += 6*lc[i]**2*lc[j]**2*vm[i,2]*vm[j,2] + \ - 6*qc[i]**2*qc[j]**2*vm[i,4]*vm[j,4] + \ - cp[i,j]**4*vm[i,4]*vm[j,4] + \ - 12*cp[i,j]*(lc[i]**2*lc[j]*vm[i,3]*vm[j,2] + \ - lc[i]*lc[j]**2*vm[i,2]*vm[j,3]) + \ - 12*cp[i,j]*qc[i]*qc[j]*(qc[i]*vm[i,5]*vm[j,3] + \ - qc[j]*vm[i,3]*vm[j,6]) + \ - 12*qc[i]*qc[j]*(lc[i]**2*vm[i,4]*vm[j,2] + \ - lc[j]**2*vm[i,2]*vm[j,4] + \ - 2*lc[i]*lc[j]*vm[i,3]*vm[j,3]) + \ - 6*cp[i,j]**2*(lc[i]**2*vm[i,4]*vm[j,2] + \ - lc[j]**2*vm[i,2]*vm[j,4] + \ - 2*lc[i]*lc[j]*vm[i,3]*vm[j,3]) + \ - 6*cp[i,j]**2*(qc[i]**2*vm[i,6]*vm[j,2] + \ - qc[j]**2*vm[i,2]*vm[j,6] + \ - 2*qc[i]*qc[j]*vm[i,4]*vm[j,4]) + \ - 12*cp[i,j]*(lc[j]*qc[i]*(lc[j]*vm[i,3]*vm[j,3] + \ - 2*lc[i]*vm[i,4]*vm[j,2]) + \ - lc[i]*qc[j]*(lc[i]*vm[i,3]*vm[j,3] + \ - 2*lc[j]*vm[i,2]*vm[j,4])) +\ - 12*cp[i,j]*(lc[i]*qc[j]*(qc[j]*vm[i,2]*vm[j,5] + \ - 2*qc[i]*vm[i,4]*vm[j,3]) + \ - lc[j]*qc[i]*(qc[i]*vm[i,5]*vm[j,2] + \ - 2*qc[j]*vm[i,3]*vm[j,4])) +\ - 12*cp[i,j]**2*(qc[i]*(lc[i]*vm[i,5]*vm[i,2] + \ - lc[j]*vm[i,4]*vm[j,3]) + \ - qc[j]*(lc[i]*vm[i,3]*vm[j,4] + \ - lc[j]*vm[i,2]*vm[j,5])) + ans += ( + lc[i] ** 4 * vm[i, 4] + + qc[i] ** 4 * vm[i, 8] + + 4 * lc[i] ** 3 * qc[i] * vm[i, 5] + + 4 * lc[i] * qc[i] ** 3 * vm[i, 7] + + 6 * lc[i] ** 2 * qc[i] ** 2 * vm[i, 6] + ) + + if n >= 2: + for i in range(n - 1): + for j in range(i + 1, n): + ans += ( + 6 * lc[i] ** 2 * lc[j] ** 2 * vm[i, 2] * vm[j, 2] + + 6 * qc[i] ** 2 * qc[j] ** 2 * vm[i, 4] * vm[j, 4] + + cp[i, j] ** 4 * vm[i, 4] * vm[j, 4] + + 12 + * cp[i, j] + * ( + lc[i] ** 2 * lc[j] * vm[i, 3] * vm[j, 2] + + lc[i] * lc[j] ** 2 * vm[i, 2] * vm[j, 3] + ) + + 12 + * cp[i, j] + * qc[i] + * qc[j] + * ( + qc[i] * vm[i, 5] * vm[j, 3] + + qc[j] * vm[i, 3] * vm[j, 6] + ) + + 12 + * qc[i] + * qc[j] + * ( + lc[i] ** 2 * vm[i, 4] * vm[j, 2] + + lc[j] ** 2 * vm[i, 2] * vm[j, 4] + + 2 * lc[i] * lc[j] * vm[i, 3] * vm[j, 3] + ) + + 6 + * cp[i, j] ** 2 + * ( + lc[i] ** 2 * vm[i, 4] * vm[j, 2] + + lc[j] ** 2 * vm[i, 2] * vm[j, 4] + + 2 * lc[i] * lc[j] * vm[i, 3] * vm[j, 3] + ) + + 6 + * cp[i, j] ** 2 + * ( + qc[i] ** 2 * vm[i, 6] * vm[j, 2] + + qc[j] ** 2 * vm[i, 2] * vm[j, 6] + + 2 * qc[i] * qc[j] * vm[i, 4] * vm[j, 4] + ) + + 12 + * cp[i, j] + * ( + lc[j] + * qc[i] + * ( + lc[j] * vm[i, 3] * vm[j, 3] + + 2 * lc[i] * vm[i, 4] * vm[j, 2] + ) + + lc[i] + * qc[j] + * ( + lc[i] * vm[i, 3] * vm[j, 3] + + 2 * lc[j] * vm[i, 2] * vm[j, 4] + ) + ) + + 12 + * cp[i, j] + * ( + lc[i] + * qc[j] + * ( + qc[j] * vm[i, 2] * vm[j, 5] + + 2 * qc[i] * vm[i, 4] * vm[j, 3] + ) + + lc[j] + * qc[i] + * ( + qc[i] * vm[i, 5] * vm[j, 2] + + 2 * qc[j] * vm[i, 3] * vm[j, 4] + ) + ) + + 12 + * cp[i, j] ** 2 + * ( + qc[i] + * ( + lc[i] * vm[i, 5] * vm[i, 2] + + lc[j] * vm[i, 4] * vm[j, 3] + ) + + qc[j] + * ( + lc[i] * vm[i, 3] * vm[j, 4] + + lc[j] * vm[i, 2] * vm[j, 5] + ) + ) + ) for i in range(n): for j in range(n): - if i!=j: - ans += 4*qc[i]**3*qc[j]*vm[i,6]*vm[j,2] + \ - 4*qc[i]*lc[j]**3*vm[i,2]*vm[j,3] + \ - 12*lc[i]*qc[i]*lc[j]**2*vm[i,3]*vm[i,2] + \ - 12*lc[i]*qc[i]**2*qc[j]*vm[i,5]*vm[i,2] + \ - 12*lc[i]*qc[i]*qc[j]**2*vm[i,3]*vm[j,4] + \ - 4*lc[i]*cp[i,j]**3*vm[i,4]*vm[j,3] + \ - 4*qc[i]*cp[i,j]**3*vm[i,5]*vm[j,3] + \ - 6*qc[i]**2*lc[j]**2*vm[i,4]*vm[j,2] - - if n>=3: - for i in range(n-2): - for j in range(i+1, n-1): - for k in range(j+1, n): - ans += (12*qc[i]**2*qc[j]*qc[k] + \ - 6*cp[i,j]**2*cp[i,k]**2 + \ - 12*qc[i]*(qc[k]*cp[i,j]**2 + qc[j]*cp[i,k]**2) + \ - 6*qc[i]**2*cp[j,k])*vm[i,4]*vm[j,2]*vm[k,2] - ans += (12*qc[i]*qc[j]**2*qc[k] + \ - 6*cp[i,j]**2*cp[j,k]**2 + \ - 12*qc[j]*(qc[k]*cp[i,j]**2 + qc[i]*cp[j,k]**2) + \ - 6*qc[j]**2*cp[i,k]**2)*vm[i,2]*vm[j,4]*vm[k,2] - ans += (12*qc[i]*qc[j]*qc[k]**2 + \ - 6*cp[i,k]**2*cp[j,k]**2 + \ - 12*qc[k]*(qc[i]*cp[j,k]**2 + qc[j]*cp[i,k]**2) + \ - 6*qc[k]**2*cp[i,j]**2)*vm[i,2]*vm[j,2]*vm[k,4] - ans += (12*cp[i,j]**2*cp[i,k]*cp[j,k] + \ - 24*qc[i]*qc[j]*qc[k]*cp[i,j] + \ - 4*qc[k]*cp[i,j]**3 + \ - 24*qc[i]*qc[k]*cp[i,k]*cp[j,k])*vm[i,3]*vm[j,3]*vm[k,2] - ans += (12*cp[i,j]*cp[i,k]**2*cp[j,k] + \ - 24*qc[i]*qc[j]*qc[k]*cp[i,k] + \ - 4*qc[j]*cp[i,k]**3 + \ - 24*qc[i]*qc[k]*cp[i,j]*cp[j,k])*vm[i,3]*vm[j,2]*vm[k,3] - ans += (12*cp[i,j]*cp[i,k]*cp[j,k]**2 + \ - 24*qc[i]*qc[j]*qc[k]*cp[j,k] + \ - 4*qc[i]*cp[j,k]**3 + \ - 24*qc[j]*qc[j]*cp[i,j]*cp[i,k])*vm[i,2]*vm[j,3]*vm[k,3] - ans += (12*cp[i,j]*cp[i,k]*cp[j,k] + \ - 24*qc[i]*qc[j]*qc[k]*cp[j,k] + \ - 4*qc[i]*cp[j,k]**3 + \ - 24*qc[j]*qc[k]*cp[i,j]*cp[i,k])*vm[i,2]*vm[j,3]*vm[k,3] - ans += 24*(qc[i]*qc[j]*qc[k] + \ - cp[i,j]*cp[i,k]*cp[j,k])*(lc[i]*vm[i,3]*vm[j,2]*vm[k,2] + \ - lc[j]*vm[i,2]*vm[j,3]*vm[k,2] + \ - lc[k]*vm[i,2]*vm[j,2]*vm[k,3]) - ans += 12*(lc[i]*cp[j,k]**2*vm[i,2]*(cp[i,j]*vm[j,3]*vm[k,2] + \ - cp[i,k]*vm[j,2]*vm[k,3]) + lc[j]*cp[i,k]**2*vm[j,2]*(cp[i,j]*vm[i,3]*vm[k,2] + \ - cp[j,k]*vm[i,2]*vm[k,3]) + \ - lc[k]*cp[i,j]**2*vm[k,2]*(cp[i,k]*vm[i,3]*vm[j,2] + \ - cp[j,k]*vm[i,2]*vm[j,3])) - ans += 12*(qc[i]*cp[j,k]**2*vm[i,3]*(cp[i,j]*vm[j,3]*vm[k,2] + \ - cp[i,k]*vm[j,2]*vm[k,3]) + qc[j]*cp[i,k]**2*vm[j,3]*(cp[i,j]*vm[i,3]*vm[k,2] + \ - cp[j,k]*vm[i,2]*vm[k,3]) + \ - qc[k]*cp[i,j]**2*vm[k,3]*(cp[i,k]*vm[i,3]*vm[j,2] + \ - cp[j,k]*vm[i,2]*vm[j,3])) - ans += 24*cp[i,j]*cp[i,k]*cp[j,k]*(qc[i]*vm[i,4]*vm[j,2]*vm[k,2] + \ - qc[j]*vm[i,2]*vm[j,4]*vm[k,2] + qc[k]*vm[i,2]*vm[j,2]*vm[k,4]) - ans += vm[i,2]*vm[j,2]*vm[k,2]*(12*(qc[i]*qc[j]*lc[k]**2 + \ - qc[i]*qc[k]*lc[j]**2 + qc[j]*qc[k]*lc[i]**2) + \ - 6*(lc[i]**2*cp[j,k]**2 + lc[j]**2*cp[i,k]**2 + \ - lc[k]**2*cp[i,j]**2) + \ - 24*(cp[i,j]*cp[i,k]*lc[j]*lc[k] + \ - cp[i,j]*cp[j,k]*lc[i]*lc[k] + \ - cp[i,k]*cp[j,k]*lc[i]*lc[j]) + \ - 24*(lc[i]*lc[j]*qc[k]*cp[i,j] + \ - lc[i]*lc[k]*qc[j]*cp[i,k] + \ - lc[j]*lc[k]*qc[i]*cp[j,k])) - ans += vm[i,3]*vm[j,2]*vm[k,2]*(24*lc[j]*cp[i,j]*qc[i]*qc[k] + \ - 24*lc[k]*cp[i,k]*qc[i]*qc[j] + \ - 12*lc[i]*cp[j,k]**2*qc[i] + \ - 24*lc[j]*cp[i,k]*cp[j,k]*qc[i] + \ - 24*lc[k]*cp[i,j]*cp[j,k]*qc[i] + \ - 12*lc[i]*cp[i,k]**2*qc[j] + \ - 12*lc[i]*cp[i,j]**2*qc[k]) - ans += vm[i,2]*vm[j,3]*vm[k,2]*(24*lc[i]*cp[i,j]*qc[j]*qc[k] + \ - 24*lc[k]*cp[j,k]*qc[i]*qc[j] + \ - 12*lc[j]*cp[i,k]**2*qc[j] + \ - 24*lc[i]*cp[i,k]*cp[j,k]*qc[j] + \ - 24*lc[k]*cp[i,j]*cp[i,k]*qc[j] + \ - 12*lc[j]*cp[j,k]**2*qc[i] + \ - 12*lc[j]*cp[i,j]**2*qc[k]) - ans += vm[i,2]*vm[j,2]*vm[k,3]*(24*lc[i]*cp[i,k]*qc[j]*qc[k] + \ - 24*lc[j]*cp[j,k]*qc[i]*qc[k] + \ - 12*lc[k]*cp[i,j]**2*qc[k] + \ - 24*lc[i]*cp[i,j]*cp[j,k]*qc[k] + \ - 24*lc[j]*cp[i,j]*cp[i,k]*qc[k] + \ - 12*lc[k]*cp[j,k]**2*qc[i] + \ - 12*lc[k]*cp[i,k]**2*qc[j]) - - if n>=4: - for i in range(n-3): - for j in range(i+1, n-2): - for k in range(j+1, n-1): - for m in range(k+1, n): - ans += vm[i,2]*vm[j,2]*vm[k,2]*vm[m,2]*(24*(qc[i]*qc[j]*qc[k]*qc[m] +\ - cp[i,j]*cp[i,k]*cp[j,m]*cp[k,m] + \ - cp[i,j]*cp[i,m]*cp[j,k]*cp[k,m] + \ - cp[i,k]*cp[i,m]*cp[j,k]*cp[j,m] + \ - qc[i]*cp[j,k]*cp[j,m]*cp[k,m] + \ - qc[j]*cp[i,k]*cp[i,m]*cp[i,m] + \ - qc[k]*cp[i,j]*cp[i,m]*cp[j,m] + \ - qc[m]*cp[i,j]*cp[i,k]*cp[j,k]) + \ - 12*(qc[i]*qc[j]*cp[k,m]**2 + \ - qc[i]*qc[k]*cp[j,m]**2 + \ - qc[i]*qc[m]*cp[j,k]**2 + \ - qc[j]*qc[k]*cp[i,m]**2 + \ - qc[j]*qc[m]*cp[i,k]**2 + \ - qc[k]*qc[m]*cp[i,j]**2) + \ - 6*(cp[i,j]**2*cp[k,m]**2 + \ - cp[i,k]**2*cp[j,m]**2 + \ - cp[i,m]**2*cp[j,k]**2)) - + if i != j: + ans += ( + 4 * qc[i] ** 3 * qc[j] * vm[i, 6] * vm[j, 2] + + 4 * qc[i] * lc[j] ** 3 * vm[i, 2] * vm[j, 3] + + 12 + * lc[i] + * qc[i] + * lc[j] ** 2 + * vm[i, 3] + * vm[i, 2] + + 12 + * lc[i] + * qc[i] ** 2 + * qc[j] + * vm[i, 5] + * vm[i, 2] + + 12 + * lc[i] + * qc[i] + * qc[j] ** 2 + * vm[i, 3] + * vm[j, 4] + + 4 * lc[i] * cp[i, j] ** 3 * vm[i, 4] * vm[j, 3] + + 4 * qc[i] * cp[i, j] ** 3 * vm[i, 5] * vm[j, 3] + + 6 * qc[i] ** 2 * lc[j] ** 2 * vm[i, 4] * vm[j, 2] + ) + + if n >= 3: + for i in range(n - 2): + for j in range(i + 1, n - 1): + for kk in range(j + 1, n): + ans += ( + ( + 12 * qc[i] ** 2 * qc[j] * qc[kk] + + 6 * cp[i, j] ** 2 * cp[i, kk] ** 2 + + 12 + * qc[i] + * ( + qc[kk] * cp[i, j] ** 2 + + qc[j] * cp[i, kk] ** 2 + ) + + 6 * qc[i] ** 2 * cp[j, kk] + ) + * vm[i, 4] + * vm[j, 2] + * vm[kk, 2] + ) + ans += ( + ( + 12 * qc[i] * qc[j] ** 2 * qc[kk] + + 6 * cp[i, j] ** 2 * cp[j, kk] ** 2 + + 12 + * qc[j] + * ( + qc[kk] * cp[i, j] ** 2 + + qc[i] * cp[j, kk] ** 2 + ) + + 6 * qc[j] ** 2 * cp[i, kk] ** 2 + ) + * vm[i, 2] + * vm[j, 4] + * vm[kk, 2] + ) + ans += ( + ( + 12 * qc[i] * qc[j] * qc[kk] ** 2 + + 6 * cp[i, kk] ** 2 * cp[j, kk] ** 2 + + 12 + * qc[kk] + * ( + qc[i] * cp[j, kk] ** 2 + + qc[j] * cp[i, kk] ** 2 + ) + + 6 * qc[kk] ** 2 * cp[i, j] ** 2 + ) + * vm[i, 2] + * vm[j, 2] + * vm[kk, 4] + ) + ans += ( + ( + 12 * cp[i, j] ** 2 * cp[i, kk] * cp[j, kk] + + 24 * qc[i] * qc[j] * qc[kk] * cp[i, j] + + 4 * qc[kk] * cp[i, j] ** 3 + + 24 * qc[i] * qc[kk] * cp[i, kk] * cp[j, kk] + ) + * vm[i, 3] + * vm[j, 3] + * vm[kk, 2] + ) + ans += ( + ( + 12 * cp[i, j] * cp[i, kk] ** 2 * cp[j, kk] + + 24 * qc[i] * qc[j] * qc[kk] * cp[i, kk] + + 4 * qc[j] * cp[i, kk] ** 3 + + 24 * qc[i] * qc[kk] * cp[i, j] * cp[j, kk] + ) + * vm[i, 3] + * vm[j, 2] + * vm[kk, 3] + ) + ans += ( + ( + 12 * cp[i, j] * cp[i, kk] * cp[j, kk] ** 2 + + 24 * qc[i] * qc[j] * qc[kk] * cp[j, kk] + + 4 * qc[i] * cp[j, kk] ** 3 + + 24 * qc[j] * qc[j] * cp[i, j] * cp[i, kk] + ) + * vm[i, 2] + * vm[j, 3] + * vm[kk, 3] + ) + ans += ( + ( + 12 * cp[i, j] * cp[i, kk] * cp[j, kk] + + 24 * qc[i] * qc[j] * qc[kk] * cp[j, kk] + + 4 * qc[i] * cp[j, kk] ** 3 + + 24 * qc[j] * qc[kk] * cp[i, j] * cp[i, kk] + ) + * vm[i, 2] + * vm[j, 3] + * vm[kk, 3] + ) + ans += ( + 24 + * ( + qc[i] * qc[j] * qc[kk] + + cp[i, j] * cp[i, kk] * cp[j, kk] + ) + * ( + lc[i] * vm[i, 3] * vm[j, 2] * vm[kk, 2] + + lc[j] * vm[i, 2] * vm[j, 3] * vm[kk, 2] + + lc[kk] * vm[i, 2] * vm[j, 2] * vm[kk, 3] + ) + ) + ans += 12 * ( + lc[i] + * cp[j, kk] ** 2 + * vm[i, 2] + * ( + cp[i, j] * vm[j, 3] * vm[kk, 2] + + cp[i, kk] * vm[j, 2] * vm[kk, 3] + ) + + lc[j] + * cp[i, kk] ** 2 + * vm[j, 2] + * ( + cp[i, j] * vm[i, 3] * vm[kk, 2] + + cp[j, kk] * vm[i, 2] * vm[kk, 3] + ) + + lc[kk] + * cp[i, j] ** 2 + * vm[kk, 2] + * ( + cp[i, kk] * vm[i, 3] * vm[j, 2] + + cp[j, kk] * vm[i, 2] * vm[j, 3] + ) + ) + ans += 12 * ( + qc[i] + * cp[j, kk] ** 2 + * vm[i, 3] + * ( + cp[i, j] * vm[j, 3] * vm[kk, 2] + + cp[i, kk] * vm[j, 2] * vm[kk, 3] + ) + + qc[j] + * cp[i, kk] ** 2 + * vm[j, 3] + * ( + cp[i, j] * vm[i, 3] * vm[kk, 2] + + cp[j, kk] * vm[i, 2] * vm[kk, 3] + ) + + qc[kk] + * cp[i, j] ** 2 + * vm[kk, 3] + * ( + cp[i, kk] * vm[i, 3] * vm[j, 2] + + cp[j, kk] * vm[i, 2] * vm[j, 3] + ) + ) + ans += ( + 24 + * cp[i, j] + * cp[i, kk] + * cp[j, kk] + * ( + qc[i] * vm[i, 4] * vm[j, 2] * vm[kk, 2] + + qc[j] * vm[i, 2] * vm[j, 4] * vm[kk, 2] + + qc[kk] * vm[i, 2] * vm[j, 2] * vm[kk, 4] + ) + ) + ans += ( + vm[i, 2] + * vm[j, 2] + * vm[kk, 2] + * ( + 12 + * ( + qc[i] * qc[j] * lc[kk] ** 2 + + qc[i] * qc[kk] * lc[j] ** 2 + + qc[j] * qc[kk] * lc[i] ** 2 + ) + + 6 + * ( + lc[i] ** 2 * cp[j, kk] ** 2 + + lc[j] ** 2 * cp[i, kk] ** 2 + + lc[kk] ** 2 * cp[i, j] ** 2 + ) + + 24 + * ( + cp[i, j] * cp[i, kk] * lc[j] * lc[kk] + + cp[i, j] * cp[j, kk] * lc[i] * lc[kk] + + cp[i, kk] * cp[j, kk] * lc[i] * lc[j] + ) + + 24 + * ( + lc[i] * lc[j] * qc[kk] * cp[i, j] + + lc[i] * lc[kk] * qc[j] * cp[i, kk] + + lc[j] * lc[kk] * qc[i] * cp[j, kk] + ) + ) + ) + ans += ( + vm[i, 3] + * vm[j, 2] + * vm[kk, 2] + * ( + 24 * lc[j] * cp[i, j] * qc[i] * qc[kk] + + 24 * lc[kk] * cp[i, kk] * qc[i] * qc[j] + + 12 * lc[i] * cp[j, kk] ** 2 * qc[i] + + 24 * lc[j] * cp[i, kk] * cp[j, kk] * qc[i] + + 24 * lc[kk] * cp[i, j] * cp[j, kk] * qc[i] + + 12 * lc[i] * cp[i, kk] ** 2 * qc[j] + + 12 * lc[i] * cp[i, j] ** 2 * qc[kk] + ) + ) + ans += ( + vm[i, 2] + * vm[j, 3] + * vm[kk, 2] + * ( + 24 * lc[i] * cp[i, j] * qc[j] * qc[kk] + + 24 * lc[kk] * cp[j, kk] * qc[i] * qc[j] + + 12 * lc[j] * cp[i, kk] ** 2 * qc[j] + + 24 * lc[i] * cp[i, kk] * cp[j, kk] * qc[j] + + 24 * lc[kk] * cp[i, j] * cp[i, kk] * qc[j] + + 12 * lc[j] * cp[j, kk] ** 2 * qc[i] + + 12 * lc[j] * cp[i, j] ** 2 * qc[kk] + ) + ) + ans += ( + vm[i, 2] + * vm[j, 2] + * vm[kk, 3] + * ( + 24 * lc[i] * cp[i, kk] * qc[j] * qc[kk] + + 24 * lc[j] * cp[j, kk] * qc[i] * qc[kk] + + 12 * lc[kk] * cp[i, j] ** 2 * qc[kk] + + 24 * lc[i] * cp[i, j] * cp[j, kk] * qc[kk] + + 24 * lc[j] * cp[i, j] * cp[i, kk] * qc[kk] + + 12 * lc[kk] * cp[j, kk] ** 2 * qc[i] + + 12 * lc[kk] * cp[i, kk] ** 2 * qc[j] + ) + ) + + if n >= 4: + for i in range(n - 3): + for j in range(i + 1, n - 2): + for kk in range(j + 1, n - 1): + for m in range(kk + 1, n): + ans += ( + vm[i, 2] + * vm[j, 2] + * vm[kk, 2] + * vm[m, 2] + * ( + 24 + * ( + qc[i] * qc[j] * qc[kk] * qc[m] + + cp[i, j] + * cp[i, kk] + * cp[j, m] + * cp[kk, m] + + cp[i, j] + * cp[i, m] + * cp[j, kk] + * cp[kk, m] + + cp[i, kk] + * cp[i, m] + * cp[j, kk] + * cp[j, m] + + qc[i] + * cp[j, kk] + * cp[j, m] + * cp[kk, m] + + qc[j] + * cp[i, kk] + * cp[i, m] + * cp[i, m] + + qc[kk] + * cp[i, j] + * cp[i, m] + * cp[j, m] + + qc[m] + * cp[i, j] + * cp[i, kk] + * cp[j, kk] + ) + + 12 + * ( + qc[i] * qc[j] * cp[kk, m] ** 2 + + qc[i] * qc[kk] * cp[j, m] ** 2 + + qc[i] * qc[m] * cp[j, kk] ** 2 + + qc[j] * qc[kk] * cp[i, m] ** 2 + + qc[j] * qc[m] * cp[i, kk] ** 2 + + qc[kk] * qc[m] * cp[i, j] ** 2 + ) + + 6 + * ( + cp[i, j] ** 2 * cp[kk, m] ** 2 + + cp[i, kk] ** 2 * cp[j, m] ** 2 + + cp[i, m] ** 2 * cp[j, kk] ** 2 + ) + ) + ) + ############################ - + else: - print('Can only calculate raw moments k = 0 to 4. Sorry.') + print("Can only calculate raw moments k = 0 to 4. Sorry.") ans = None - + return ans - + + ############################################################################### -def centralmoment(vi, k): + +def centralmoment(vi: NDArray, k: int) -> float: """ Converts raw distribution moments to central moments - + Parameters ---------- vi : array The first four raw distribution moments k : int The central moment (0 to 4) to calculate (i.e., k=2 is the variance) - + Returns ------- cm : scalar The central moment itself - + """ - if k==0: + if k == 0: ans = 1 - elif k==1: + elif k == 1: ans = 0 - elif k==2: - ans = vi[2] - vi[1]**2 - elif k==3: - ans = vi[3] - 3*vi[2]*vi[1] + 2*vi[1]**3 - elif k==4: - ans = vi[4] - 4*vi[3]*vi[1] + 6*vi[2]*vi[1]**2 - 3*vi[1]**4 + elif k == 2: + ans = vi[2] - vi[1] ** 2 + elif k == 3: + ans = vi[3] - 3 * vi[2] * vi[1] + 2 * vi[1] ** 3 + elif k == 4: + ans = ( + vi[4] - 4 * vi[3] * vi[1] + 6 * vi[2] * vi[1] ** 2 - 3 * vi[1] ** 4 + ) else: - print('Can only calculate central moments k = 0 to 4. Sorry.') + print("Can only calculate central moments k = 0 to 4. Sorry.") ans = None return ans + ############################################################################### -def variance_components(slc, sqc, scp, var_moments, vz): + +def variance_components( + slc: NDArray, sqc: NDArray, scp: NDArray, var_moments: NDArray, vz: NDArray +) -> tuple[NDArray, NDArray, NDArray]: """ Calculate the 1st and 2nd-order output variance components for each input variable. - + Parameters ---------- slc : array @@ -425,12 +757,12 @@ def variance_components(slc, sqc, scp, var_moments, vz): scp : 2d-array The standardized cross-product second derivative terms var_moments : 2d-array - The first 9 (starting at 0) standardized distribution moments (one - row for each input variable, corresponding to the derivative array + The first 9 (starting at 0) standardized distribution moments (one + row for each input variable, corresponding to the derivative array order). See the documentation for ``soerp_numeric`` for more details. vz : array The 1st-4th central output distribution moments - + Returns ------- var_comp_lc : array @@ -448,32 +780,45 @@ def variance_components(slc, sqc, scp, var_moments, vz): for i in range(n): slc_tmp = copy(slc) slc_tmp[i] = 0.0 - vy_tmp = [rawmoment(slc_tmp, sqc, scp, var_moments, k) for k in range(3)] + vy_tmp = [ + rawmoment(slc_tmp, sqc, scp, var_moments, k) for k in range(3) + ] var_comp_lc[i] = vz[2] - centralmoment(vy_tmp, 2) for i in range(n): sqc_tmp = copy(sqc) sqc_tmp[i] = 0.0 - vy_tmp = [rawmoment(slc, sqc_tmp, scp, var_moments, k) for k in range(3)] + vy_tmp = [ + rawmoment(slc, sqc_tmp, scp, var_moments, k) for k in range(3) + ] var_comp_qc[i] = vz[2] - centralmoment(vy_tmp, 2) - for i in range(n-1): - for j in range(i+1, n): + for i in range(n - 1): + for j in range(i + 1, n): scp_tmp = copy(scp) - scp_tmp[i,j] = 0.0 - scp_tmp[j,i] = 0.0 - vy_tmp = [rawmoment(slc, sqc, scp_tmp, var_moments, k) for k in range(3)] - var_comp_cp[i,j] = vz[2] - centralmoment(vy_tmp, 2) + scp_tmp[i, j] = 0.0 + scp_tmp[j, i] = 0.0 + vy_tmp = [ + rawmoment(slc, sqc, scp_tmp, var_moments, k) for k in range(3) + ] + var_comp_cp[i, j] = vz[2] - centralmoment(vy_tmp, 2) return var_comp_lc, var_comp_qc, var_comp_cp + ############################################################################### -def variance_contrib(var_comp_lc, var_comp_qc, var_comp_cp, vz): + +def variance_contrib( + var_comp_lc: NDArray, + var_comp_qc: NDArray, + var_comp_cp: NDArray, + vz: NDArray, +) -> tuple[NDArray, NDArray, NDArray]: """ Convert actual variance components to percent contributions (best if used in conjunction with ``variance_components`` function). - + Parameters ---------- var_comp_lc : array @@ -484,19 +829,19 @@ def variance_contrib(var_comp_lc, var_comp_qc, var_comp_cp, vz): The actual variance components from the cross-product 2nd-order terms vz : array The 1st-4th central output distribution moments - + Returns ------- var_contrib_lc : array - The contribution percentage of the variance components from the + The contribution percentage of the variance components from the 1st-order terms var_contrib_qc : array - The contribution percentage of the variance components from the pure + The contribution percentage of the variance components from the pure 2nd-order terms var_contrib_cp : 2d-array - The contribution percentage of the variance components from the + The contribution percentage of the variance components from the cross-product 2nd-order terms - + """ n = len(var_comp_lc) var_contrib_lc = np.empty_like(var_comp_lc) @@ -505,53 +850,64 @@ def variance_contrib(var_comp_lc, var_comp_qc, var_comp_cp, vz): for i in range(n): if vz[2]: - var_contrib_lc[i] = np.abs(var_comp_lc[i]/vz[2]) + var_contrib_lc[i] = np.abs(var_comp_lc[i] / vz[2]) else: var_contrib_lc[i] = 0.0 for i in range(n): if vz[2]: - var_contrib_qc[i] = np.abs(var_comp_qc[i]/vz[2]) + var_contrib_qc[i] = np.abs(var_comp_qc[i] / vz[2]) else: var_contrib_qc[i] = 0.0 for i in range(n - 1): for j in range(i + 1, n): if vz[2]: - var_contrib_cp[i, j] = np.abs(var_comp_cp[i, j]/vz[2]) + var_contrib_cp[i, j] = np.abs(var_comp_cp[i, j] / vz[2]) else: var_contrib_cp[i, j] = 0.0 - + return var_contrib_lc, var_contrib_qc, var_contrib_cp - + + ############################################################################### -def soerp_numeric(slc, sqc, scp, var_moments, func0, title=None, debug=False, - silent=False): + +def soerp_numeric( # noqa: PLR0912, PLR0913, PLR0917 + slc: NDArray, + sqc: NDArray, + scp: NDArray, + var_moments: NDArray, + func0: float, + title: str | None = None, + *, + debug: bool = False, + silent: bool = False, +) -> list: """ This performs the same moment calculations, but expects that all input derivatives and moments have been put in standardized form. It can also describe the variance contributions and print out any output distribution information, both raw and central moments. - + Parameters ---------- slc : array - 1st-order standardized derivatives (i.e., multiplied by the standard + 1st-order standardized derivatives (i.e., multiplied by the standard deviation of the related input) sqc : array - 2nd-order derivatives (i.e., multiplied by the standard + 2nd-order derivatives (i.e., multiplied by the standard deviation squared, or variance, of the related input) scp : 2d-array - 2nd-order cross-derivatives (i.e., multiplied by the two standard + 2nd-order cross-derivatives (i.e., multiplied by the two standard deviations of the related inputs) var_moments : 2-d array - Standardized moments where row[i] contains the first 9 moments of + Standardized moments where row[i] contains the first 9 moments of variable x[i]. FYI: the first 3 values should always be [1, 0, 1] func0 : scalar System mean (i.e. value of the system evaluated at the means of all the input variables) - + Optional -------- title : str @@ -560,13 +916,13 @@ def soerp_numeric(slc, sqc, scp, var_moments, func0, title=None, debug=False, If true, all intermediate calculation results get printed to the screen silent : bool, false by default If true, nothing gets printed to the screen (overrides debug). - + Returns ------- moments : list The first four standard moments (mean, variance, skewness and kurtosis coefficients) - + Example ------- Example taken from the original SOERP user guide by N. D. Cox: @@ -576,8 +932,14 @@ def soerp_numeric(slc, sqc, scp, var_moments, func0, title=None, debug=False, >>> cp = np.array([[0, -216.5], [-216.5, 0]]) >>> vm = np.array([norm_moments, norm_moments]) >>> f0 = 4152 - >>> soerp_numeric(lc, qc, cp, vm, f0, - ... title='EXAMPLE FROM ORIGINAL SOERP USER GUIDE') + >>> soerp_numeric( + ... lc, + ... qc, + ... cp, + ... vm, + ... f0, + ... title="EXAMPLE FROM ORIGINAL SOERP USER GUIDE", + ... ) ******************************************************************************** **************** SOERP: EXAMPLE FROM ORIGINAL SOERP USER GUIDE ***************** ******************************************************************************** @@ -600,31 +962,31 @@ def soerp_numeric(slc, sqc, scp, var_moments, func0, title=None, debug=False, FOURTH CENTRAL MOMENT (MU4DL)............. 3.8956371E+12 COEFFICIENT OF KURTOSIS (BETA2)........... 4.1121529E+00 ******************************************************************************** - """ + """ # noqa: E501 if not silent: - print('\n', '*'*80) + print("\n", "*" * 80) if title: - print('{:*^80}'.format(' SOERP: ' + title + ' ')) + print("{:*^80}".format(" SOERP: " + title + " ")) ############################ vy = np.empty(5) if debug and not silent: - print('*'*80) + print("*" * 80) for k in range(5): vy[k] = rawmoment(slc, sqc, scp, var_moments, k) if debug and not silent: - print('Raw Moment {}: {}'.format(k, vy[k])) + print(f"Raw Moment {k}: {vy[k]}") ############################ vz = np.empty(5) if debug and not silent: - print('*'*80) + print("*" * 80) for k in range(5): vz[k] = centralmoment(vy, k) if debug and not silent: - print('Central Moment {}: {}'.format(k, vz[k])) + print(f"Central Moment {k}: {vz[k]}") sysmean = float(vy[1] + func0) ############################ @@ -635,62 +997,91 @@ def soerp_numeric(slc, sqc, scp, var_moments, func0, title=None, debug=False, n = len(slc) if not silent: - print('*'*80) + print("*" * 80) for i in range(n): - print('Variance Contribution of lc[x{:d}]: {:7.5%}'.format(i, vlc[i])) + print(f"Variance Contribution of lc[x{i:d}]: {vlc[i]:7.5%}") for i in range(n): - print('Variance Contribution of qc[x{:d}]: {:7.5%}'.format(i, vqc[i])) + print(f"Variance Contribution of qc[x{i:d}]: {vqc[i]:7.5%}") for i in range(n - 1): for j in range(i + 1, n): - print('Variance Contribution of cp[x{:d}, x{:d}]: {:7.5%}'.format(i, j, vcp[i, j])) - - + print( + f"Variance Contribution of cp[x{i:d}, x{j:d}]: {vcp[i, j]:7.5%}" # noqa: E501 + ) + ############################ - stdev = vz[2]**(0.5) + stdev = vz[2] ** (0.5) if stdev: - rtbt1 = vz[3]/vz[2]**(1.5) - beta2 = vz[4]/vz[2]**2 + rtbt1 = vz[3] / vz[2] ** (1.5) + beta2 = vz[4] / vz[2] ** 2 else: rtbt1 = 0.0 beta2 = 0.0 beta1 = rtbt1**2 if not silent: - print('*'*80) - print('MEAN-INTERCEPT (EDEL1)....................','{: 8.7E}'.format(vy[1])) - print('MEAN......................................','{: 8.7E}'.format(sysmean)) - print('SECOND MOMENT (EDEL2).....................','{: 8.7E}'.format(vy[2])) - print('VARIANCE (VARDL)..........................','{: 8.7E}'.format(vz[2])) - print('STANDARD DEVIATION (RTVAR)................','{: 8.7E}'.format(stdev)) - print('THIRD MOMENT (EDEL3)......................','{: 8.7E}'.format(vy[3])) - print('THIRD CENTRAL MOMENT (MU3DL)..............','{: 8.7E}'.format(vz[3])) - print('COEFFICIENT OF SKEWNESS SQUARED (BETA1)...','{: 8.7E}'.format(beta1)) - print('COEFFICIENT OF SKEWNESS (RTBT1)...........','{: 8.7E}'.format(rtbt1)) - print('FOURTH MOMENT (EDEL4).....................','{: 8.7E}'.format(vy[4])) - print('FOURTH CENTRAL MOMENT (MU4DL).............','{: 8.7E}'.format(vz[4])) - print('COEFFICIENT OF KURTOSIS (BETA2)...........','{: 8.7E}'.format(beta2)) - print('*'*80) - + print("*" * 80) + print("MEAN-INTERCEPT (EDEL1)....................", f"{vy[1]: 8.7E}") + print("MEAN......................................", f"{sysmean: 8.7E}") + print("SECOND MOMENT (EDEL2).....................", f"{vy[2]: 8.7E}") + print("VARIANCE (VARDL)..........................", f"{vz[2]: 8.7E}") + print("STANDARD DEVIATION (RTVAR)................", f"{stdev: 8.7E}") + print("THIRD MOMENT (EDEL3)......................", f"{vy[3]: 8.7E}") + print("THIRD CENTRAL MOMENT (MU3DL)..............", f"{vz[3]: 8.7E}") + print("COEFFICIENT OF SKEWNESS SQUARED (BETA1)...", f"{beta1: 8.7E}") + print("COEFFICIENT OF SKEWNESS (RTBT1)...........", f"{rtbt1: 8.7E}") + print("FOURTH MOMENT (EDEL4).....................", f"{vy[4]: 8.7E}") + print("FOURTH CENTRAL MOMENT (MU4DL).............", f"{vz[4]: 8.7E}") + print("COEFFICIENT OF KURTOSIS (BETA2)...........", f"{beta2: 8.7E}") + print("*" * 80) + return [sysmean, vz[2], rtbt1, beta2] -if __name__=='__main__': - # standardized moments of a normal distribution: + +if __name__ == "__main__": + # standardized moments of a normal distribution: norm_moments = [1, 0, 1, 0, 3, 0, 15, 0, 105] - + lc = [-802.65, -430.5] qc = [205.54, 78.66] cp = np.array([[0, -216.5], [-216.5, 0]]) - + vm = np.array([norm_moments, norm_moments]) f0 = 4152 - - m = soerp_numeric(lc, qc, cp, vm, f0, - title='EXAMPLE FROM ORIGINAL SOERP USER GUIDE') - - print('Returned moments:') - print(' Mean.................... {}'.format(m[0])) - print(' Variance................ {}'.format(m[1])) - print(' Standardized Skewness... {}'.format(m[2])) - print(' Standardized Kurtosis... {}'.format(m[3])) + + m = soerp_numeric( + lc, qc, cp, vm, f0, title="EXAMPLE FROM ORIGINAL SOERP USER GUIDE" + ) + + print("Returned moments:") + print(f" Mean.................... {m[0]}") + print(f" Variance................ {m[1]}") + print(f" Standardized Skewness... {m[2]}") + print(f" Standardized Kurtosis... {m[3]}") + + +############################################################################### + + +def raw2central(v: list) -> list: + """Convert raw moments (1 to len(v)) to central moments + + Returns + ------- + central_moments : list + The central moments corresponding to the input raw moments. + """ + + def nci(n: int, i: int) -> float: + return math.factorial(n) / (math.factorial(i) * math.factorial(n - i)) + + v = [1, *v] + central_moments = [] + for k in range(len(v)): + val = 0.0 + for j in range(k + 1): + val += (-1) ** j * nci(k, j) * v[k - j] * v[1] ** j + central_moments.append(val) + + return central_moments[1:] diff --git a/soerp/statistics.py b/soerp/statistics.py new file mode 100644 index 0000000..2106482 --- /dev/null +++ b/soerp/statistics.py @@ -0,0 +1,89 @@ +from .uncertain_function import to_uncertain_func + + +def covariance_matrix(nums_with_uncert: list) -> list: + """ + Calculate the covariance matrix of uncertain variables, oriented by the + order of the inputs + + Parameters + ---------- + nums_with_uncert : array-like + A list of variables that have an associated uncertainty + + Returns + ------- + cov_matrix : 2d-array-like + A nested list containing covariance values + + Example + ------- + + >>> x = N(1, 0.1) + >>> y = N(10, 0.1) + >>> z = x + 2 * y + >>> covariance_matrix([x, y, z]) + [[ 0.01 0. 0.01] + [ 0. 0.01 0.02] + [ 0.01 0.02 0.05]] + + """ + ufuncs = list(map(to_uncertain_func, nums_with_uncert)) + cov_matrix = [] + for i1, expr1 in enumerate(ufuncs): + derivatives1 = expr1._lc # Optimization + vars1 = set(derivatives1) + coefs_expr1 = [] + for _, expr2 in enumerate(ufuncs[: i1 + 1]): + derivatives2 = expr2._lc # Optimization + coef = 0.0 + for v in vars1.intersection(derivatives2): + coef += derivatives1[v] * derivatives2[v] * v.var + coefs_expr1.append(coef) + cov_matrix.append(coefs_expr1) + + # Symmetrize the matrix: + for i, covariance_coefs in enumerate(cov_matrix): + covariance_coefs.extend( + cov_matrix[j][i] for j in range(i + 1, len(cov_matrix)) + ) + + return cov_matrix + + +def correlation_matrix(nums_with_uncert: list) -> list: + """ + Calculate the correlation matrix of uncertain variables, oriented by the + order of the inputs + + Parameters + ---------- + nums_with_uncert : array-like + A list of variables that have an associated uncertainty + + Returns + ------- + corr_matrix : 2d-array-like + A nested list containing covariance values + + Example + ------- + + >>> x = N(1, 0.1) + >>> y = N(10, 0.1) + >>> z = x + 2 * y + >>> correlation_matrix([x, y, z]) + [[ 1. 0. 0.4472136 ] + [ 0. 1. 0.89442719] + [ 0.4472136 0.89442719 1. ]] + + """ + ufuncs = list(map(to_uncertain_func, nums_with_uncert)) + cov_matrix = covariance_matrix(ufuncs) + corr_matrix = [] + for i1, expr1 in enumerate(ufuncs): + row_data = [] + for i2, expr2 in enumerate(ufuncs): + row_data.append(cov_matrix[i1][i2] / expr1.std / expr2.std) + corr_matrix.append(row_data) + return corr_matrix diff --git a/soerp/umath.py b/soerp/umath.py new file mode 100644 index 0000000..ef07771 --- /dev/null +++ b/soerp/umath.py @@ -0,0 +1,749 @@ +""" +Generalizes mathematical operators that work on numeric objects (from the math +module) compatible with objects with uncertainty distributions. + +All functions implement exact second-order derivatives via the chain rule so +that second-order error propagation remains accurate. +""" + +import math + +from scipy.special import digamma, polygamma + +from .uncertain_function import UncertainFunction, _combine_op, _unary_op + + +__author__ = "Abraham Lee" + +__all__ = [] + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +e = math.e +pi = math.pi + +__all__ += ["e", "pi"] + +# --------------------------------------------------------------------------- +# Internal helper: build an UncertainFunction from a unary operation applied +# to a value that *may* be an UncertainFunction. +# --------------------------------------------------------------------------- + + +def _uf_unary( + x: UncertainFunction | float, hx: float, dh: float, d2h: float +) -> UncertainFunction | float: + """Return _unary_op(x, hx, dh, d2h) when x is UncertainFunction, + otherwise return hx (a plain scalar). + + Returns + ------- + result : UncertainFunction | float + The result of the unary operation with uncertainty propagated. + """ + if isinstance(x, UncertainFunction): + return _unary_op(x, hx, dh, d2h) + return hx + + +def _uf_binary( # noqa: PLR0913, PLR0917 + x: UncertainFunction | float, + y: UncertainFunction | float, + hx: float, + dh_dx: float, + dh_dy: float, + d2h_dx2: float, + d2h_dy2: float, + d2h_dxy: float, +) -> UncertainFunction | float: + """Return _combine_op(...) when at least one arg is UncertainFunction, + otherwise return hx. + + Returns + ------- + result : UncertainFunction | float + The result of the binary operation with uncertainty propagated. + """ + x_uf = isinstance(x, UncertainFunction) + y_uf = isinstance(y, UncertainFunction) + if not x_uf and not y_uf: + return hx + if not x_uf: + x = UncertainFunction(float(x)) + if not y_uf: + y = UncertainFunction(float(y)) + return _combine_op(x, y, hx, dh_dx, dh_dy, d2h_dx2, d2h_dy2, d2h_dxy) + + +# --------------------------------------------------------------------------- +# Trigonometric +# --------------------------------------------------------------------------- + + +def sin(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.sin(fx), math.cos(fx), -math.sin(fx)) + + +__all__.append("sin") + + +def cos(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.cos(fx), -math.sin(fx), -math.cos(fx)) + + +__all__.append("cos") + + +def tan(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + c = math.cos(fx) + t = math.tan(fx) + d1 = 1.0 / (c * c) # $\sec^2(x)$ + d2 = 2.0 * t / (c * c) # $2 \tan(x) \sec^2(x)$ + return _uf_unary(x, t, d1, d2) + + +__all__.append("tan") + + +def sec(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + s = 1.0 / math.cos(fx) # sec(x) + t = math.tan(fx) + d1 = s * t # sec·tan + d2 = s * (1.0 + 2.0 * t * t) # $\sec(1 + 2 \tan^2)$ + return _uf_unary(x, s, d1, d2) + + +__all__.append("sec") + + +def csc(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + cs = 1.0 / math.sin(fx) # csc(x) + ct = math.cos(fx) / math.sin(fx) # cot(x) + d1 = -cs * ct + d2 = cs * (1.0 + 2.0 * ct * ct) # $\csc(1 + 2 \cot^2)$ + return _uf_unary(x, cs, d1, d2) + + +__all__.append("csc") + + +def cot(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + c = math.cos(fx) + s = math.sin(fx) + ct = c / s + cs = s / c + d1 = -(cs * cs) # $-\csc^2$ + d2 = 2.0 * ct * cs * cs # $2 \cot \csc^2$ + return _uf_unary(x, ct, d1, d2) + + +__all__.append("cot") + +# --------------------------------------------------------------------------- +# Inverse trigonometric +# --------------------------------------------------------------------------- + + +def asin(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(1.0 - fx * fx) + d1 = 1.0 / r + d2 = fx / (r * r * r) + return _uf_unary(x, math.asin(fx), d1, d2) + + +__all__.append("asin") + + +def acos(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(1.0 - fx * fx) + d1 = -1.0 / r + d2 = -fx / (r * r * r) + return _uf_unary(x, math.acos(fx), d1, d2) + + +__all__.append("acos") + + +def atan(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + q = 1.0 + fx * fx + d1 = 1.0 / q + d2 = -2.0 * fx / (q * q) + return _uf_unary(x, math.atan(fx), d1, d2) + + +__all__.append("atan") + + +def atan2( + y: UncertainFunction | float, x: UncertainFunction | float +) -> UncertainFunction | float: + yv = y.x if isinstance(y, UncertainFunction) else float(y) + xv = x.x if isinstance(x, UncertainFunction) else float(x) + r2 = xv * xv + yv * yv + hx = math.atan2(yv, xv) + # $\partial/\partial y = x/r^2$, $\partial/\partial x = -y/r^2$ + dh_dy = xv / r2 + dh_dx = -yv / r2 + # $\partial^2/\partial y^2 = -2xy/r^4$, $\partial^2/\partial x^2 = 2xy/r^4$, $\partial^2/(\partial x \partial y) = (y^2-x^2)/r^4$ # noqa: E501 + d2h_dy2 = -2.0 * xv * yv / (r2 * r2) + d2h_dx2 = 2.0 * xv * yv / (r2 * r2) + d2h_dxy = (yv * yv - xv * xv) / (r2 * r2) + return _uf_binary(y, x, hx, dh_dy, dh_dx, d2h_dy2, d2h_dx2, d2h_dxy) + + +__all__.append("atan2") + + +def acot(x: UncertainFunction | float) -> UncertainFunction | float: + r"""Inverse cotangent: acot(x) = atan(1/x) ($= \pi/2 - \operatorname{atan}(x)$) + + Returns + ------- + result : UncertainFunction | float + acot(x) with uncertainty propagated. + """ # noqa: E501 + fx = x.x if isinstance(x, UncertainFunction) else x + q = 1.0 + fx * fx + d1 = -1.0 / q + d2 = 2.0 * fx / (q * q) + return _uf_unary(x, math.atan(1.0 / fx) if fx != 0 else math.pi / 2, d1, d2) + + +__all__.append("acot") + + +def asec(x: UncertainFunction | float) -> UncertainFunction | float: + """Inverse secant: asec(x) = acos(1/x) + + Returns + ------- + result : UncertainFunction | float + asec(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + r = abs_(fx) * math.sqrt(fx * fx - 1.0) + d1 = 1.0 / r + d2 = -(2.0 * fx * fx - 1.0) / (fx * fx * (fx * fx - 1.0) ** 1.5) + return _uf_unary(x, math.acos(1.0 / fx), d1, d2) + + +__all__.append("asec") + + +def acsc(x: UncertainFunction | float) -> UncertainFunction | float: + """Inverse cosecant: acsc(x) = asin(1/x) + + Returns + ------- + result : UncertainFunction | float + acsc(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + r = abs_(fx) * math.sqrt(fx * fx - 1.0) + d1 = -1.0 / r + d2 = (2.0 * fx * fx - 1.0) / (fx * fx * (fx * fx - 1.0) ** 1.5) + return _uf_unary(x, math.asin(1.0 / fx), d1, d2) + + +__all__.append("acsc") + +# --------------------------------------------------------------------------- +# Hyperbolic +# --------------------------------------------------------------------------- + + +def sinh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.sinh(fx), math.cosh(fx), math.sinh(fx)) + + +__all__.append("sinh") + + +def cosh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.cosh(fx), math.sinh(fx), math.cosh(fx)) + + +__all__.append("cosh") + + +def tanh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + ch = math.cosh(fx) + th = math.tanh(fx) + d1 = 1.0 / (ch * ch) # $\sech^2$ + d2 = -2.0 * th / (ch * ch) # $-2 \tanh \sech^2$ + return _uf_unary(x, th, d1, d2) + + +__all__.append("tanh") + + +def sech(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + ch = math.cosh(fx) + th = math.tanh(fx) + s = 1.0 / ch # sech + d1 = -s * th + d2 = s * (2.0 * th * th - 1.0) + return _uf_unary(x, s, d1, d2) + + +__all__.append("sech") + + +def csch(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + sh = math.sinh(fx) + ct = math.cosh(fx) / sh # coth + cs = 1.0 / sh # csch + d1 = -cs * ct + d2 = cs * (2.0 * ct * ct - 1.0) # $= \csch(1 + 2 \csch^2)$ + return _uf_unary(x, cs, d1, d2) + + +__all__.append("csch") + + +def coth(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + sh = math.sinh(fx) + ct = math.cosh(fx) / sh + cs = 1.0 / sh # csch + d1 = -(cs * cs) # $-\csch^2$ + d2 = 2.0 * ct * cs * cs # $2 \coth \csch^2$ + return _uf_unary(x, ct, d1, d2) + + +__all__.append("coth") + +# --------------------------------------------------------------------------- +# Inverse hyperbolic +# --------------------------------------------------------------------------- + + +def asinh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(fx * fx + 1.0) + d1 = 1.0 / r + d2 = -fx / (r * r * r) + return _uf_unary(x, math.asinh(fx), d1, d2) + + +__all__.append("asinh") + + +def acosh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(fx * fx - 1.0) + d1 = 1.0 / r + d2 = -fx / (r * r * r) + return _uf_unary(x, math.acosh(fx), d1, d2) + + +__all__.append("acosh") + + +def atanh(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + q = 1.0 - fx * fx + d1 = 1.0 / q + d2 = 2.0 * fx / (q * q) + return _uf_unary(x, math.atanh(fx), d1, d2) + + +__all__.append("atanh") + + +def acoth(x: UncertainFunction | float) -> UncertainFunction | float: + """Inverse hyperbolic cotangent: acoth(x) = atanh(1/x), |x| > 1 + + Returns + ------- + result : UncertainFunction | float + acoth(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + q = 1.0 - fx * fx + d1 = 1.0 / q + d2 = 2.0 * fx / (q * q) + return _uf_unary(x, math.atanh(1.0 / fx), d1, d2) + + +__all__.append("acoth") + + +def asech(x: UncertainFunction | float) -> UncertainFunction | float: + """Inverse hyperbolic secant: asech(x) = acosh(1/x), 0 < x <= 1 + + Returns + ------- + result : UncertainFunction | float + asech(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(1.0 - fx * fx) + d1 = -1.0 / (fx * r) + d2 = (1.0 - 2.0 * fx * fx) / (fx * fx * r * r * r) + return _uf_unary(x, math.acosh(1.0 / fx), d1, d2) + + +__all__.append("asech") + + +def acsch(x: UncertainFunction | float) -> UncertainFunction | float: + """Inverse hyperbolic cosecant: acsch(x) = asinh(1/x) + + Returns + ------- + result : UncertainFunction | float + acsch(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + r = math.sqrt(1.0 + fx * fx) + d1 = -1.0 / (abs_(fx) * r) + d2 = (1.0 + 2.0 * fx * fx) / (fx * fx * r * r * r) + return _uf_unary(x, math.asinh(1.0 / fx), d1, d2) + + +__all__.append("acsch") + +# --------------------------------------------------------------------------- +# Exponential and logarithmic +# --------------------------------------------------------------------------- + + +def exp(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + ex = math.exp(fx) + return _uf_unary(x, ex, ex, ex) + + +__all__.append("exp") + + +def expm1(x: UncertainFunction | float) -> UncertainFunction | float: + """exp(x) - 1, accurate for small x. + + Returns + ------- + result : UncertainFunction | float + expm1(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + ex = math.exp(fx) # derivative same as exp + return _uf_unary(x, math.expm1(fx), ex, ex) + + +__all__.append("expm1") + + +def ln(x: UncertainFunction | float) -> UncertainFunction | float: + """Natural logarithm (alias for log base e). + + Returns + ------- + result : UncertainFunction | float + ln(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + d1 = 1.0 / fx + d2 = -1.0 / (fx * fx) + return _uf_unary(x, math.log(fx), d1, d2) + + +__all__.append("ln") + + +def log( + x: UncertainFunction | float, base: float = math.e +) -> UncertainFunction | float: + """Logarithm to given base (default: natural log). + + Returns + ------- + result : UncertainFunction | float + log(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + lb = math.log(base) + d1 = 1.0 / (fx * lb) + d2 = -1.0 / (fx * fx * lb) + return _uf_unary(x, math.log(fx, base), d1, d2) + + +__all__.append("log") + + +def log10(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + lb = math.log(10.0) + d1 = 1.0 / (fx * lb) + d2 = -1.0 / (fx * fx * lb) + return _uf_unary(x, math.log10(fx), d1, d2) + + +__all__.append("log10") + + +def log1p(x: UncertainFunction | float) -> UncertainFunction | float: + """log(1 + x), accurate for small x. + + Returns + ------- + result : UncertainFunction | float + log1p(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + q = 1.0 + fx + d1 = 1.0 / q + d2 = -1.0 / (q * q) + return _uf_unary(x, math.log1p(fx), d1, d2) + + +__all__.append("log1p") + +# --------------------------------------------------------------------------- +# Power and root +# --------------------------------------------------------------------------- + + +def sqrt(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + s = math.sqrt(fx) + d1 = 0.5 / s + d2 = -0.25 / (fx * s) # = -1/(4 x^{3/2}) + return _uf_unary(x, s, d1, d2) + + +__all__.append("sqrt") + + +def pow_(x: UncertainFunction | float, n: float) -> UncertainFunction | float: + """x raised to the power n (n may be any real number). + + Returns + ------- + result : UncertainFunction | float + x**n with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + hx = fx**n + if abs(fx) < 1e-10: + d1 = 0.0 if n > 1 else (1.0 if n == 1 else float("inf")) + d2 = 0.0 if n > 2 else (1.0 if n == 2 else float("inf")) + else: + d1 = n * fx ** (n - 1) + d2 = n * (n - 1) * fx ** (n - 2) + return _uf_unary(x, hx, d1, d2) + + +__all__.append("pow_") + +# --------------------------------------------------------------------------- +# Absolute value and rounding (piecewise constant → zero 2nd derivative) +# --------------------------------------------------------------------------- + + +def abs_(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + s = 1.0 if fx >= 0 else -1.0 + return _uf_unary(x, math.fabs(fx), s, 0.0) + + +__all__.append("abs_") + + +def fabs(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + s = 1.0 if fx >= 0 else -1.0 + return _uf_unary(x, math.fabs(fx), s, 0.0) + + +__all__.append("fabs") + + +def ceil(x: UncertainFunction | float) -> UncertainFunction | float: + """Ceiling: piecewise constant, treated as identity for uncertainty. + + Returns + ------- + result : UncertainFunction | float + ceil(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, float(math.ceil(fx)), 0.0, 0.0) + + +__all__.append("ceil") + + +def floor(x: UncertainFunction | float) -> UncertainFunction | float: + """Floor: piecewise constant, treated as identity for uncertainty. + + Returns + ------- + result : UncertainFunction | float + floor(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, float(math.floor(fx)), 0.0, 0.0) + + +__all__.append("floor") + + +def trunc(x: UncertainFunction | float) -> UncertainFunction | float: + """Truncate toward zero: treated as constant for uncertainty. + + Returns + ------- + result : UncertainFunction | float + trunc(x) with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, float(math.trunc(fx)), 0.0, 0.0) + + +__all__.append("trunc") + + +def factorial(x: UncertainFunction | float) -> float: + """Integer factorial: returns a constant (no uncertainty propagation). + + Returns + ------- + result : float + The factorial of the integer value of x. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + return float(math.factorial(int(fx))) + + +__all__.append("factorial") + +# --------------------------------------------------------------------------- +# Angle conversion (linear → constant derivative) +# --------------------------------------------------------------------------- + + +def degrees(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.degrees(fx), 180.0 / math.pi, 0.0) + + +__all__.append("degrees") + + +def radians(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + return _uf_unary(x, math.radians(fx), math.pi / 180.0, 0.0) + + +__all__.append("radians") + +# --------------------------------------------------------------------------- +# Special functions (error, gamma) +# --------------------------------------------------------------------------- + +_TWO_OVER_SQRT_PI = 2.0 / math.sqrt(math.pi) + + +def erf(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + e2 = math.exp(-fx * fx) + d1 = _TWO_OVER_SQRT_PI * e2 + d2 = -2.0 * fx * d1 + return _uf_unary(x, math.erf(fx), d1, d2) + + +__all__.append("erf") + + +def erfc(x: UncertainFunction | float) -> UncertainFunction | float: + fx = x.x if isinstance(x, UncertainFunction) else x + e2 = math.exp(-fx * fx) + d1 = -_TWO_OVER_SQRT_PI * e2 + d2 = -2.0 * fx * d1 + return _uf_unary(x, math.erfc(fx), d1, d2) + + +__all__.append("erfc") + + +def gamma(x: UncertainFunction | float) -> UncertainFunction | float: + r"""Gamma function $\Gamma(x)$. Derivatives use the digamma ($\psi$) function. + + Returns + ------- + result : UncertainFunction | float + $\Gamma(x)$ with uncertainty propagated. + """ # noqa: E501 + fx = x.x if isinstance(x, UncertainFunction) else x + gx = math.gamma(fx) + psi = float(digamma(fx)) + psi1 = float(polygamma(1, fx)) + d1 = gx * psi + d2 = gx * (psi * psi + psi1) + return _uf_unary(x, gx, d1, d2) + + +__all__.append("gamma") + + +def lgamma(x: UncertainFunction | float) -> UncertainFunction | float: + r"""Natural log of the Gamma function $\ln \Gamma(x)$. + + Returns + ------- + result : UncertainFunction | float + $\ln \Gamma(x)$ with uncertainty propagated. + """ + fx = x.x if isinstance(x, UncertainFunction) else x + d1 = float(digamma(fx)) + d2 = float(polygamma(1, fx)) + return _uf_unary(x, math.lgamma(fx), d1, d2) + + +__all__.append("lgamma") + +# --------------------------------------------------------------------------- +# Binary helpers +# --------------------------------------------------------------------------- + + +def hypot( + x: UncertainFunction | float, y: UncertainFunction | float +) -> UncertainFunction | float: + r"""Euclidean distance $\sqrt{x^2 + y^2}$. + + Returns + ------- + result : UncertainFunction | float + hypot(x, y) with uncertainty propagated. + """ + xv = x.x if isinstance(x, UncertainFunction) else float(x) + yv = y.x if isinstance(y, UncertainFunction) else float(y) + r = math.hypot(xv, yv) + r3 = r * r * r + dh_dx = xv / r + dh_dy = yv / r + d2h_dx2 = yv * yv / r3 + d2h_dy2 = xv * xv / r3 + d2h_dxy = -xv * yv / r3 + return _uf_binary(x, y, r, dh_dx, dh_dy, d2h_dx2, d2h_dy2, d2h_dxy) + + +__all__.append("hypot") diff --git a/soerp/umath/__init__.py b/soerp/umath/__init__.py deleted file mode 100644 index c411276..0000000 --- a/soerp/umath/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -================================================================================ -soerp: Second Order ERror Propogation -================================================================================ - -Author: Abraham Lee -Copyright: 2013 -""" - -from .umath import * \ No newline at end of file diff --git a/soerp/umath/umath.py b/soerp/umath/umath.py deleted file mode 100644 index 77fb145..0000000 --- a/soerp/umath/umath.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Generalizes mathematical operators that work on numeric objects (from the math -module) compatible with objects with uncertainty distributions -""" -from soerp import _make_UF_compatible_object -from ad.admath import admath -#import sys - -__author__ = 'Abraham Lee' - -__all__ = [] - -e = admath.e -pi = admath.pi - -__all__.append('e') -__all__.append('pi') - -def abs(x): - return _make_UF_compatible_object(admath.abs(x)) -__all__.append('abs') - -def acos(x): - return _make_UF_compatible_object(admath.acos(x)) -__all__.append('acos') - -def acosh(x): - return _make_UF_compatible_object(admath.acosh(x)) -__all__.append('acosh') - -def acot(x): - return _make_UF_compatible_object(admath.acot(x)) -__all__.append('acot') - -def acoth(x): - return _make_UF_compatible_object(admath.acoth(x)) -__all__.append('acoth') - -def acsc(x): - return _make_UF_compatible_object(admath.acsc(x)) -__all__.append('acsc') - -def acsch(x): - return _make_UF_compatible_object(admath.acsch(x)) -__all__.append('acsch') - -def asec(x): - return _make_UF_compatible_object(admath.asec(x)) -__all__.append('asec') - -def asech(x): - return _make_UF_compatible_object(admath.asech(x)) -__all__.append('asech') - -def asin(x): - return _make_UF_compatible_object(admath.asin(x)) -__all__.append('asin') - -def asinh(x): - return _make_UF_compatible_object(admath.asinh(x)) -__all__.append('asinh') - -def atan(x): - return _make_UF_compatible_object(admath.atan(x)) -__all__.append('atan') - -def atan2(y, x): - return _make_UF_compatible_object(admath.atan2(y, x)) -__all__.append('atan2') - -def atanh(x): - return _make_UF_compatible_object(admath.atanh(x)) -__all__.append('atanh') - -def ceil(x): - return _make_UF_compatible_object(admath.ceil(x)) -__all__.append('ceil') - -def cos(x): - return _make_UF_compatible_object(admath.cos(x)) -__all__.append('cos') - -def cosh(x): - return _make_UF_compatible_object(admath.cosh(x)) -__all__.append('cosh') - -def cot(x): - return _make_UF_compatible_object(admath.cot(x)) -__all__.append('cot') - -def coth(x): - return _make_UF_compatible_object(admath.coth(x)) -__all__.append('coth') - -def csc(x): - return _make_UF_compatible_object(admath.csc(x)) -__all__.append('csc') - -def csch(x): - return _make_UF_compatible_object(admath.csch(x)) -__all__.append('csch') - -def degrees(x): - return _make_UF_compatible_object(admath.degrees(x)) -__all__.append('degrees') - -def erf(x): - return _make_UF_compatible_object(admath.erf(x)) -__all__.append('erf') - -def erfc(x): - return _make_UF_compatible_object(admath.erfc(x)) -__all__.append('erfc') - -def exp(x): - return _make_UF_compatible_object(admath.exp(x)) -__all__.append('exp') - -def expm1(x): - return _make_UF_compatible_object(admath.expm1(x)) -__all__.append('expm1') - -def fabs(x): - return _make_UF_compatible_object(admath.fabs(x)) -__all__.append('fabs') - -def factorial(x): - return _make_UF_compatible_object(admath.factorial(x)) -__all__.append('factorial') - -def floor(x): - return _make_UF_compatible_object(admath.floor(x)) -__all__.append('floor') - -def gamma(x): - return _make_UF_compatible_object(admath.gamma(x)) -__all__.append('gamma') - -def lgamma(x): - return _make_UF_compatible_object(admath.lgamma(x)) -__all__.append('lgamma') - -def hypot(x,y): - return _make_UF_compatible_object(admath.hypot(x,y)) -__all__.append('hypot') - -def ln(x): - return _make_UF_compatible_object(admath.ln(x)) -__all__.append('ln') - -def log(x, base): - return _make_UF_compatible_object(admath.log(x, base)) -__all__.append('log') - -def log10(x): - return _make_UF_compatible_object(admath.log10(x)) -__all__.append('log10') - -def log1p(x): - return _make_UF_compatible_object(admath.log1p(x)) -__all__.append('log1p') - -def pow(x): - return _make_UF_compatible_object(admath.pow(x)) -__all__.append('pow') - -def radians(x): - return _make_UF_compatible_object(admath.radians(x)) -__all__.append('radians') - -def sec(x): - return _make_UF_compatible_object(admath.sec(x)) -__all__.append('sec') - -def sech(x): - return _make_UF_compatible_object(admath.sech(x)) -__all__.append('sech') - -def sin(x): - return _make_UF_compatible_object(admath.sin(x)) -__all__.append('sin') - -def sinh(x): - return _make_UF_compatible_object(admath.sinh(x)) -__all__.append('sinh') - -def sqrt(x): - return _make_UF_compatible_object(admath.sqrt(x)) -__all__.append('sqrt') - -def tan(x): - return _make_UF_compatible_object(admath.tan(x)) -__all__.append('tan') - -def tanh(x): - return _make_UF_compatible_object(admath.tanh(x)) -__all__.append('tanh') - -def trunc(x): - return _make_UF_compatible_object(admath.trunc(x)) -__all__.append('trunc') - - - diff --git a/soerp/uncertain_function.py b/soerp/uncertain_function.py new file mode 100644 index 0000000..ea521ac --- /dev/null +++ b/soerp/uncertain_function.py @@ -0,0 +1,736 @@ +import math +from collections.abc import Callable + +import numpy as np + +from .method_of_moments import ( + soerp_numeric, + variance_components, + variance_contrib, +) + + +CONSTANT_TYPES = (float, int, complex, np.number) + + +def to_uncertain_func( + x: "UncertainFunction | float | int | complex", +) -> "UncertainFunction | None": + """ + Transforms x into a constant automatically differentiated UncertainFunction + (UF), unless it already is (in which case x is returned unchanged). + + Raises an exception unless 'x' belongs to some specific classes of + objects that are known not to depend on UncertainFunction objects + (which then cannot be considered as constants). + + Returns + ------- + result : UncertainFunction or None + The UncertainFunction wrapping x, or None if x is not a known type. + """ + + if isinstance(x, UncertainFunction): + return x + + # ! In Python 2.6+, numbers.Number could be used instead, here: + if isinstance(x, CONSTANT_TYPES): + # No variable => no derivative to define: + return UncertainFunction(x, {}, {}, {}) + + +############################################################################### +# Second-order forward-mode automatic differentiation helpers. +# +# Each UncertainFunction stores: +# x - function value +# _lc - {var: $\partial f/\partial \text{var}$} first-order partials # noqa: E501 +# _qc - {var: $\partial^2 f/\partial \text{var}^2$} pure second-order partials # noqa: E501 +# _cp - {(var_i, var_j): $\partial^2 f/(\partial \text{var}_i \partial \text{var}_j)$} cross second-order partials (i < j) # noqa: E501 +# +# The chain rules implemented here are exact for polynomial functions and give +# the second-order Taylor approximation for non-polynomial ones, which is +# exactly what SOERP requires. +############################################################################### + + +def _combine_op( # noqa: PLR0913, PLR0914, PLR0917 + f: "UncertainFunction", + g: "UncertainFunction", + hx: float, + dh_df: float, + dh_dg: float, + d2h_df2: float, + d2h_dg2: float, + d2h_dfg: float, +) -> "UncertainFunction": + r""" + Propagate derivatives through a binary operation h = h(f, g). + + Parameters + ---------- + f : UncertainFunction + First UncertainFunction input. + g : UncertainFunction + Second UncertainFunction input. + hx : float + Scalar value of h. + dh_df : float + $\partial h/\partial f$ (scalar, evaluated at f.x, g.x) + dh_dg : float + $\partial h/\partial g$ + d2h_df2 : float + $\partial^2 h/\partial f^2$ + d2h_dg2 : float + $\partial^2 h/\partial g^2$ + d2h_dfg : float + $\partial^2 h/(\partial f \partial g)$ + + Returns + ------- + result : UncertainFunction + New UncertainFunction with propagated derivatives. + """ + all_vars = set(f._lc) | set(g._lc) + lc, qc, cp = {}, {}, {} + + for v in all_vars: + fi = f._lc.get(v, 0.0) + gi = g._lc.get(v, 0.0) + lc[v] = dh_df * fi + dh_dg * gi + + for v in all_vars: + fi = f._lc.get(v, 0.0) + gi = g._lc.get(v, 0.0) + fi2 = f._qc.get(v, 0.0) + gi2 = g._qc.get(v, 0.0) + qc[v] = ( + d2h_df2 * fi * fi + + dh_df * fi2 + + 2.0 * d2h_dfg * fi * gi + + dh_dg * gi2 + + d2h_dg2 * gi * gi + ) + + vlist = list(all_vars) + n = len(vlist) + for i in range(n): + vi = vlist[i] + for j in range(i + 1, n): + vj = vlist[j] + fi = f._lc.get(vi, 0.0) + fj = f._lc.get(vj, 0.0) + gi = g._lc.get(vi, 0.0) + gj = g._lc.get(vj, 0.0) + fij = f._cp.get((vi, vj), f._cp.get((vj, vi), 0.0)) + gij = g._cp.get((vi, vj), g._cp.get((vj, vi), 0.0)) + cp[vi, vj] = ( + d2h_df2 * fi * fj + + dh_df * fij + + d2h_dfg * (fi * gj + fj * gi) + + dh_dg * gij + + d2h_dg2 * gi * gj + ) + + return UncertainFunction(hx, lc, qc, cp) + + +def _unary_op( + f: "UncertainFunction", hx: float, dh_df: float, d2h_df2: float +) -> "UncertainFunction": + """ + Propagate derivatives through a unary operation h = h(f). + + Parameters + ---------- + f : UncertainFunction + UncertainFunction input. + hx : float + Scalar value of h. + dh_df : float + dh/df (evaluated at f.x) + d2h_df2 : float + $d^2h/df^2$ + + Returns + ------- + result : UncertainFunction + New UncertainFunction with propagated derivatives. + """ + lc = {v: dh_df * df for v, df in f._lc.items()} + qc = { + v: d2h_df2 * df * df + dh_df * f._qc.get(v, 0.0) + for v, df in f._lc.items() + } + cp = {} + vlist = list(f._lc.keys()) + n = len(vlist) + for i in range(n): + vi = vlist[i] + for j in range(i + 1, n): + vj = vlist[j] + fi = f._lc[vi] + fj = f._lc[vj] + fij = f._cp.get((vi, vj), f._cp.get((vj, vi), 0.0)) + cp[vi, vj] = d2h_df2 * fi * fj + dh_df * fij + return UncertainFunction(hx, lc, qc, cp) + + +############################################################################### + + +class UncertainFunction: # noqa: PLR0904 + """ + UncertainFunction objects represent the uncertainty of a result of + calculations with uncertain variables. Nearly all basic mathematical + operations are supported. + + This class is mostly intended for internal use. + """ + + def __init__( + self, + x: float, + lc: dict | None = None, + qc: dict | None = None, + cp: dict | None = None, + ) -> None: + r""" + Parameters + ---------- + x : scalar - function value (evaluated at variable means) + lc : dict - {var: $\partial f/\partial \text{var}$} first-order partials + qc : dict - {var: $\partial^2 f/\partial \text{var}^2$} pure second-order partials + cp : dict - {(var_i, var_j): $\partial^2 f/(\partial \text{var}_i \partial \text{var}_j)$} cross partials (i < j ordering) + """ # noqa: E501 + self.x = float(x) + self._lc = lc if lc is not None else {} + self._qc = qc if qc is not None else {} + self._cp = cp if cp is not None else {} + + def __hash__(self) -> int: + return id(self) + + # ------------------------------------------------------------------ + # Derivative accessors (mirror the interface previously from `ad`) + # ------------------------------------------------------------------ + + def d(self, var: "UncertainFunction | None" = None) -> "dict | float": + r"""Return first-derivative dict, or the scalar $\partial f/\partial \text{var}$ for a given var. + + Returns + ------- + derivative : dict or float + First-derivative dict if var is None, else scalar $\partial f/\partial \text{var}$. + """ # noqa: E501 + if var is None: + return self._lc + return self._lc.get(var, 0.0) + + def d2(self, var: "UncertainFunction | None" = None) -> "dict | float": + r"""Return pure second-derivative dict, or scalar $\partial^2 f/\partial \text{var}^2$ for var. + + Returns + ------- + derivative : dict or float + Pure second-derivative dict if var is None, else $\partial^2 f/\partial \text{var}^2$. + """ # noqa: E501 + if var is None: + return self._qc + return self._qc.get(var, 0.0) + + def d2c( + self, + var1: "UncertainFunction | None" = None, + var2: "UncertainFunction | None" = None, + ) -> "dict | float": + r"""Return cross-derivative dict, or the scalar $\partial^2 f/(\partial \text{var}_1 \partial \text{var}_2)$. + + Returns + ------- + derivative : dict or float + Cross-derivative dict if var1 is None, else $\partial^2 f/(\partial \text{var}_1 \partial \text{var}_2)$. + """ # noqa: E501 + if var1 is None: + return self._cp + return self._cp.get((var1, var2), self._cp.get((var2, var1), 0.0)) + + def gradient(self, uvars: list) -> list: + """ + Return the gradient (first derivatives) with + respect to a list of variables. + + Parameters + ---------- + uvars : list + List of variables (UncertainFunction instances) to differentiate + with respect to. + + Returns + ------- + grad : list + List of first derivatives [d(self)/dvar for var in uvars]. + """ + return [self.d(var) for var in uvars] + + def hessian(self, uvars: list) -> list: + """ + Return the Hessian matrix (second derivatives) with + respect to a list of variables. + + Parameters + ---------- + uvars : list + List of variables (UncertainFunction instances) to differentiate + with respect to. + + Returns + ------- + hess : list of lists + Hessian matrix [[d2(self)/dvar_i dvar_j for j] for i]. + """ + n = len(uvars) + return [ + [ + self.d2(uvars[i]) if i == j else self.d2c(uvars[i], uvars[j]) + for j in range(n) + ] + for i in range(n) + ] + + # ------------------------------------------------------------------ + # Statistical moment properties (via method-of-moments) + # ------------------------------------------------------------------ + + _dist = None + _moments = None + + @property + def mean(self) -> float: + """Mean value as a result of an uncertainty calculation""" + return self.moments(0) + + @property + def var(self) -> float: + """Variance value as a result of an uncertainty calculation""" + return self.moments(1) + + @property + def std(self) -> float: + r""" + Standard deviation value as a result of an uncertainty calculation, + defined as:: + + ________ + std = \/variance + + """ + return self.var**0.5 + + @property + def skew(self) -> float: + r""" + Skewness coefficient value as a result of an uncertainty calculation, + defined as:: + + _____ m3 + \/beta1 = ------ + std**3 + + where m3 is the third central moment and std is the standard deviation + """ + return self.moments(2) + + @property + def kurt(self) -> float: + """ + Kurtosis coefficient value as a result of an uncertainty calculation, + defined as:: + + m4 + beta2 = ------ + std**4 + + where m4 is the fourth central moment and std is the standard deviation + """ + return self.moments(3) + + def moments(self, idx: int | None = None) -> "list | float": + """ + The first four standard moments of a distribution: mean, variance, and + standardized skewness and kurtosis coefficients. + + Returns + ------- + moments : list or float + All four moments as a list, or a single moment if idx is given. + + Raises + ------ + ValueError + If idx is not in the range [0, 3]. + """ + slc, sqc, scp, var_moments, f0 = self._get_inputs_for_soerp() + m = soerp_numeric(slc, sqc, scp, var_moments, f0, silent=True) + if idx is not None: + if not (0 <= idx <= 3): + raise ValueError( + "idx must be 0, 1, 2, or 3 since only the first " + "four moments can be calculated" + ) + return m[idx] + else: + return m + + def _to_general_representation( + self, str_func: Callable[[float], str] + ) -> str: + m = self.moments() + mn, vr, sk, kt = m[:4] + return ( + f"uv({str_func(mn)}, {str_func(vr)}, " + f"{str_func(sk)}, {str_func(kt)})" + if any([vr, sk, kt]) + else str_func(mn) + ) + + def __str__(self) -> str: + return self._to_general_representation(str) + + def __repr__(self) -> str: + return str(self) + + def describe(self) -> None: + """ + Cleanly show what the distribution moments are: + - Mean, Variance, Skewness and Kurtosis Coefficients + """ + mn, vr, sk, kt = [self.moments(i) for i in [0, 1, 2, 3]] + s = "SOERP Uncertain Value:\n" + s += f" > Mean................... {mn: }\n" + s += f" > Variance............... {vr: }\n" + s += f" > Skewness Coefficient... {sk: }\n" + s += f" > Kurtosis Coefficient... {kt: }\n" + print(s) + + def _get_inputs_for_soerp(self) -> tuple: + """ + Prepare variable moments and derivatives for method-of-moments + calculations by standardizing the derivatives (moving the distribution + to the origin and normalising with the standard deviation). + + Returns + ------- + inputs : tuple + Tuple of (slc, sqc, scp, var_moments, f0). + """ + variables = self.d().keys() + nvar = len(variables) + + # standardize the input derivatives + # - slc: linear terms + # - sqc: pure quadratic terms + # - scp: cross quadratic terms + slc = np.array([self.d(v) * v.std for v in variables]) + sqc = np.array([0.5 * self.d2(v) * v.var for v in variables]) + scp = np.zeros((nvar, nvar)) + for i, v1 in enumerate(variables): + for j, v2 in enumerate(variables): + if hash(v1) != hash(v2): + scp[i, j] = self.d2c(v1, v2) * v1.std * v2.std + else: + scp[i, j] = 0.0 + + var_moments = np.array([[1, 0, 1, *v._moments[2:]] for v in variables]) + + f0 = self.x # from evaluation at input means + + return (slc, sqc, scp, var_moments, f0) + + def error_components( # noqa: PLR0912, PLR0914, PLR0915 + self, *, pprint: bool = False, as_eq_terms: bool = False + ) -> "dict | tuple | None": + """ + The parts of the second order approximation of the variance function, + returned in three pieces if ``as_eq_terms`` = True, first-order + components, pure-quadratic components, and cross-product components, + otherwise the error components from the linear terms are added to the + corresponding error components from the quadratic terms. Any + cross-product term components are divided equally between the two + factors of the cross-product. + + Optional + -------- + pprint : bool, default is False, + Pretty-print the error components, showing both the component and + the percent contribution of the component + as_eq_terms : bool, default is False, + True to return the error components in the form of the equation + terms (pure linear, pure quadratic, and cross-product), where both + orders are available for the cross-product terms (i.e., (x, y) and + (y, x) will be returned in the cross-product terms), otherwise in + terms of the contributing UncertainVariables. + + Returns + ------- + err_comp : dict + A dictionary that maps the error components to the contributing + UncertainVariables. If ``as_eq_terms=True``, then a tuple of three + dictionaries is returned containing the 1) linear, 2) pure + quadratic, and 3) cross-product term contributions). + + Example + -------- + If we had a function of two variables ended up with the linear terms + (``as_eq_terms`` = False here), :: + + >>> lc = {x:0.5, y:0.25} + + the quadratic terms:: + + >>> qc = {x:0.2, y:0.1} + + and the cross-product term:: + + >>> cp = {(x, y}:0.14} + + then the variables would be given the error components like this:: + + >>> lc[x] + qc[x] + 0.5*cp[(x, y)] # first variable, x + 0.77 + >>> lc[y] + qc[y] + 0.5*cp[(x, y)] # second variable, y + 0.42 + + """ + variables = self.d().keys() + slc, sqc, scp, var_moments, _ = self._get_inputs_for_soerp() + vz = self.moments() + + # convert standardized moments back to central moments + vz[2] *= vz[1] ** 1.5 + vz[3] *= vz[1] ** 2 + vz = [1, *vz] # the leading 1 is required by method-of-moments + vlc, vqc, vcp = variance_components(slc, sqc, scp, var_moments, vz) + + vc_lc = {} + vc_qc = {} + vc_cp = {} + + for i, v1 in enumerate(variables): + vc_lc[v1] = vlc[i] + vc_qc[v1] = vqc[i] + for j, v2 in enumerate(variables): + if i < j: + vc_cp[v1, v2] = vcp[i, j] + vc_cp[v2, v1] = vcp[i, j] + + if not as_eq_terms: + error_wrt_var = {v: 0.0 for v in variables} + for i, v1 in enumerate(variables): + if v1 in vc_lc: + error_wrt_var[v1] += vc_lc[v1] + error_wrt_var[v1] += vc_qc[v1] + for j, v2 in enumerate(variables): + if i < j and (v1, v2) in vc_cp: + error_wrt_var[v1] += 0.5 * vc_cp[v1, v2] + error_wrt_var[v2] += 0.5 * vc_cp[v1, v2] + if pprint: + print("COMPOSITE VARIABLE ERROR COMPONENTS") + for v in variables: + pct = np.abs(error_wrt_var[v] / vz[2]) + print(f"{v} = {error_wrt_var[v]} or {pct:%}") + print(" ") + else: + return error_wrt_var + elif pprint: + vcont_lc, vcont_qc, vcont_cp = variance_contrib(vlc, vqc, vcp, vz) + print("*" * 65) + print("LINEAR ERROR COMPONENTS:") + for i, v1 in enumerate(variables): + if v1 in vc_lc: + print(f"{v1} = {vc_lc[v1]} or {vcont_lc[i]:%}") + else: + print(f"{v1} = {0.0} or {0.0:%}") + + print("*" * 65) + print("QUADRATIC ERROR COMPONENTS:") + for i, v1 in enumerate(variables): + if v1 in vc_qc: + print(f"{v1} = {vc_qc[v1]} or {vcont_qc[i]:%}") + else: + print(f"{v1} = {0.0} or {0.0:%}") + + print("*" * 65) + print("CROSS-PRODUCT ERROR COMPONENTS:") + for i, v1 in enumerate(variables): + for j, v2 in enumerate(variables): + if i < j: + if (v1, v2) in vc_cp: + print( + f"({v1}, {v2}) = {vc_cp[v1, v2]}" + f" or {vcont_cp[i, j]:%}" + ) + elif (v2, v1) in vc_cp: + print( + f"({v2}, {v1}) = {vc_cp[v2, v1]}" + f" or {vcont_cp[j, i]:%}" + ) + else: + print(f"({v1}, {v2}) = {0.0} or {0.0:%}") + print(" ") + else: + return (vc_lc, vc_qc, vc_cp) + + # ------------------------------------------------------------------ + # Arithmetic operators + # ------------------------------------------------------------------ + + def __add__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return _combine_op(self, val, self.x + val.x, 1.0, 1.0, 0.0, 0.0, 0.0) + + def __radd__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return val.__add__(self) + + def __mul__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + # h = f*g → $dh/df = g$, $dh/dg = f$, $d^2h/(df dg) = 1$, rest 0 + return _combine_op( + self, val, self.x * val.x, val.x, self.x, 0.0, 0.0, 1.0 + ) + + def __rmul__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return val.__mul__(self) + + def __sub__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return _combine_op(self, val, self.x - val.x, 1.0, -1.0, 0.0, 0.0, 0.0) + + def __rsub__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return val.__sub__(self) + + def __truediv__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + fx, gx = self.x, val.x + hx = fx / gx + dh_df = 1.0 / gx + dh_dg = -fx / (gx * gx) + d2h_dg2 = 2.0 * fx / (gx**3) + d2h_dfg = -1.0 / (gx * gx) + return _combine_op(self, val, hx, dh_df, dh_dg, 0.0, d2h_dg2, d2h_dfg) + + def __rtruediv__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return val.__truediv__(self) + + def __pow__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + n = float(val) + fx = self.x + hx = fx**n + if abs(fx) < 1e-10: + dh_df = 0.0 if n > 1 else (1.0 if n == 1 else float("inf")) + d2h_df2 = 0.0 if n > 2 else (1.0 if n == 2 else float("inf")) + else: + dh_df = n * fx ** (n - 1) + d2h_df2 = n * (n - 1) * fx ** (n - 2) + return _unary_op(self, hx, dh_df, d2h_df2) + else: + # Both uncertain: h = f^g + fx, gx = self.x, val.x + hx = fx**gx + log_fx = math.log(fx) if fx > 0 else 0.0 + dh_df = gx * fx ** (gx - 1) if fx != 0 else 0.0 + dh_dg = hx * log_fx + d2h_df2 = gx * (gx - 1) * fx ** (gx - 2) if fx != 0 else 0.0 + d2h_dg2 = hx * log_fx**2 + d2h_dfg = (fx ** (gx - 1) * (1.0 + gx * log_fx)) if fx != 0 else 0.0 + return _combine_op( + self, val, hx, dh_df, dh_dg, d2h_df2, d2h_dg2, d2h_dfg + ) + + def __rpow__( + self, val: "UncertainFunction | float | int | complex" + ) -> "UncertainFunction": + if not isinstance(val, UncertainFunction): + val = UncertainFunction(float(val)) + return val.__pow__(self) + + def __neg__(self) -> "UncertainFunction": + return _unary_op(self, -self.x, -1.0, 0.0) + + def __pos__(self) -> "UncertainFunction": + return UncertainFunction( + self.x, dict(self._lc), dict(self._qc), dict(self._cp) + ) + + def __abs__(self) -> "UncertainFunction": + s = 1.0 if self.x >= 0 else -1.0 + return _unary_op(self, abs(self.x), s, 0.0) + + def __eq__(self, val: object) -> bool: + diff = self - val + return not (diff.mean or diff.var or diff.skew or diff.kurt) + + def __ne__(self, val: object) -> bool: + return not self.__eq__(val) + + def __lt__(self, val: "UncertainFunction | float | int | complex") -> bool: + val = to_uncertain_func(val) + return float(self.mean - val.mean) < 0 + + def __le__(self, val: "UncertainFunction | float | int | complex") -> bool: + return (self == val) or self < val + + def __gt__(self, val: "UncertainFunction | float | int | complex") -> bool: + val = to_uncertain_func(val) + return float(self.mean - val.mean) > 0 + + def __ge__(self, val: "UncertainFunction | float | int | complex") -> bool: + return (self == val) or self > val + + def __bool__(self) -> bool: + return self != 0 + + +def make_uf_compatible_object(tmp: "UncertainFunction") -> "UncertainFunction": + """ + Backward-compatible shim: all arithmetic on UncertainFunction objects + now returns UncertainFunction directly, so this is effectively a no-op. + Kept so that external code (e.g. older umath wrappers) continues to work. + + Returns + ------- + tmp : UncertainFunction + The input unchanged. + """ + return tmp diff --git a/soerp/uncertain_variable.py b/soerp/uncertain_variable.py new file mode 100644 index 0000000..cae8b0b --- /dev/null +++ b/soerp/uncertain_variable.py @@ -0,0 +1,424 @@ +import numpy as np +import scipy.stats as ss + +from .method_of_moments import raw2central +from .uncertain_function import UncertainFunction + + +try: + import matplotlib.pyplot as plt +except ImportError: + matplotlib_installed = False +else: + matplotlib_installed = True + + +class UncertainVariable(UncertainFunction): + """ + UncertainVariable objects track the effects of uncertainty, characterized + in terms of the first four standard moments of statistical distributions + (mean, variance, skewness and kurtosis coefficients). Most texts + only deal with first-order models, but this class uses a full second + order model, which requires a knowledge of the first eight central moments + of a distribution. + + Parameters + ---------- + moments : array-like, optional + The first eight moments (standardized) of the uncertain variable's + underlying statistical distribution (the first two values should be the + mean and variance) + + rv : scipy.stats.rv_continous, optional + If supplied, the ``moments`` kwarg is ignored and the first eight + standardized moments are calculated internally + + tag : str, optional + A string identifier when information about this variable is printed to + the screen + + Notes + ----- + + For a full report on the methods behind this class, see: + + N. D. Cox, "Tolerance Analysis by Computer," Journal of Quality + Technology, Vol. 11, No. 2, 1979. + + Here are the first eight moments of some standard distributions: + + - Normal Distribution: [0, 1, 0, 3, 0, 15, 0, 105] + - Uniform Distribution: [0, 1, 0, 1.8, 0, 3.857, 0, 9] + - Exponential Distribution: [0, 1, 2, 9, 44, 265, 1854, 14833] + + A distribution's raw moment (moment about the origin) is defined as:: + + oo + / + | + k | k + E(x ) = | x *f(x) dx + | + / + -oo + + where E(...) is the expectation operator, k is the order of the moment, and + f(x) is the probability density function (pdf) of x. + + To convert these to central moments (moment about the mean), we can simply + use the helper function:: + + >>> moments = raw2central(raw_moments) + + or we can use the mathematical definition to calculate the kth moment as:: + + oo + / + | + k | k + E((x-mu) ) = | (x-mu) *f(x) dx + | + / + -oo + + This then needs to be standardized by normalizing each of the moments + (starting with the third moment) using the standard deviation:: + + >>> sd = moment[1]**0.5 + >>> moment[k] = [moment[k]/sd**(k + 1) for k in range(2, 9)] + + The ``scipy.stats`` module contains many distributions from which we can + easily generate these moments for any distribution. Currently, only + ``rv_continuous`` distributions are supported. It is important to follow + the initialization syntax for creating any kind of rv_continuous object: + + - *Location* and *Scale* values must use the kwargs ``loc`` and + ``scale`` + - *Shape* values are passed in as arguments before the location and + scale + + The mathematical operations that can be performed on Uncertain... objects + will work for any moments or distribution supplied, but may not be + misleading if the supplied moments or distribution is not accurately + defined. Here are some guidelines for creating UncertainVariable objects + using some of the most common statistical distributions: + + +---------------------------+-----------+-----------------+-----+--------+ + | Distribution | stats cls | args | loc | scale | + | | | (shape params) | | | + +===========================+===========+=================+=====+========+ + | Normal(mu, sigma) | norm | | mu | sigma | + +---------------------------+-----------+-----------------+-----+--------+ + | Uniform(a, b) | uniform | | a | b-a | + +---------------------------+-----------+-----------------+-----+--------+ + | Exponential(lamda) | expon | | | 1/lam | + +---------------------------+-----------+-----------------+-----+--------+ + | Gamma(k, theta) | gamma | k | | theta | + +---------------------------+-----------+-----------------+-----+--------+ + | Beta(alpha, beta, [a, b]) | beta | alpha, beta | a | b-a | + +---------------------------+-----------+-----------------+-----+--------+ + | Log-Normal(mu, sigma) | lognorm | sigma | mu | | + +---------------------------+-----------+-----------------+-----+--------+ + | Chi-Square(k) | chi2 | k | | | + +---------------------------+-----------+-----------------+-----+--------+ + | F(d1, d2) | f | d1, d2 | | | + +---------------------------+-----------+-----------------+-----+--------+ + | Triangular(a, b, c) | triang | c | a | b-a | + +---------------------------+-----------+-----------------+-----+--------+ + | Student-T(v) | t | v | | | + +---------------------------+-----------+-----------------+-----+--------+ + | Weibull(lamda, k) | exponweib | lamda, k | | | + +---------------------------+-----------+-----------------+-----+--------+ + + Thus, each distribution above would have the same call signature:: + + >>> import scipy.stats as ss + >>> ss.your_dist_here(args,loc=loc,scale=scale) + + Convenient constructors have been created to make assigning these + distributions easier. They follow the parameter notation found in the + respective Wikipedia articles: + + +---------------------------+--------------------------------------------+ + | MCERP Distibution | Wikipedia page | + +===========================+============================================+ + | N(mu, sigma) | wikipedia.org/wiki/Normal_distribution | + +---------------------------+--------------------------------------------+ + | U(a, b) | wikipedia.org/wiki/Uniform_distribution_ | + | | (continuous) | + +---------------------------+--------------------------------------------+ + | Exp(lamda, [mu]) | wikipedia.org/wiki/Exponential_distribution| + +---------------------------+--------------------------------------------+ + | Gamma(k, theta) | wikipedia.org/wiki/Gamma_distribution | + +---------------------------+--------------------------------------------+ + | Beta(alpha, beta, [a, b]) | wikipedia.org/wiki/Beta_distribution | + +---------------------------+--------------------------------------------+ + | LogN(mu, sigma) | wikipedia.org/wiki/Log-normal_distribution | + +---------------------------+--------------------------------------------+ + | X2(df) | wikipedia.org/wiki/Chi-squared_distribution| + +---------------------------+--------------------------------------------+ + | F(dfn, dfd) | wikipedia.org/wiki/F-distribution | + +---------------------------+--------------------------------------------+ + | Tri(a, b, c) | wikipedia.org/wiki/Triangular_distribution | + +---------------------------+--------------------------------------------+ + | T(df) | wikipedia.org/wiki/Student's_t-distribution| + +---------------------------+--------------------------------------------+ + | Weib(lamda, k) | wikipedia.org/wiki/Weibull_distribution | + +---------------------------+--------------------------------------------+ + + + Thus, the following are equivalent:: + + >>> x = uv([10, 1, 0, 3, 0, 15, 0, 105]) + >>> x = uv(rv=ss.norm(loc=10, scale=1)) + >>> x = N(10, 1) + + Examples + -------- + Using the first eight distribution moments:: + + >>> x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) # normally distributed + >>> x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) # normally distributed + >>> x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) # exp. distributed + >>> Z = (x1*x2**2)/(15*(1.5 + x3)) + >>> Z + uv(1176.45, 99699.6822919, 0.708013052954, 6.16324345122) + + The result shows the mean, variance, and standardized skewness and kurtosis + of the output variable Z. + + Same example, but now using ``scipy.stats`` objects:: + + >>> import scipy.stats as ss + >>> x1 = uv(rv=ss.norm(loc=24, scale=1)) # normally distributed + >>> x2 = uv(rv=ss.norm(loc=37, scale=4)) # normally distributed + >>> x3 = uv(rv=ss.expon(scale=0.5)) # exponentially distributed + + Or using the convenient distribution constructors:: + + >>> x1 = N(24, 1) + >>> x2 = N(37, 4) + >>> x3 = Exp(2) + + The results may be slightly different from using the moments manually since + moment calculations can suffer from numerical errors during the integration + of the expectation equations above, but they will be close enough. + + Basic math operations may be applied to distributions, where all + statistical calculations are performed using method of moments. Built-in + trig-, logarithm-, etc. functions should be used when possible since they + support both scalar values and uncertain objects. + + At any time, the 8 standardized moments of variables (or the 4 that result + from calculations) can be retrieved using:: + + >>> x1.moments() + [24.0, 1.0, 0.0, 3.0, 0.0, 15.0, 0.0, 105.0] + + Or any moment can be accessed directly by specifying its index:: + + >>> Z.moments(1) # variance + 99699.6822919 + + Important + --------- + + One final thing to note is that some answers suffer from the use of a + second-order approximation to the method of moment equations. For example, + the equation f(x) = x*sin(x) has this issue:: + + >>> x = N(0, 1) # standard normal distribution + >>> x*sin(x) + uv(1.0, 2.0, 2.82842712475, 15.0) + + This is the precise answer for f(x) = x**2, which just so happens to be the + second-order Taylor series approximation of x*sin(x). The correct answer + for [mean,variance,skewness,kurtosis] here can be calculated by:: + + >>> mu = 0.0 + >>> sigma = 1.0 + >>> n = ss.norm(loc=mu, scale=sigma) + >>> rm = [n.dist.expect(lambda x: (x*math.sin(x))**k, loc=mu, + ... scale=sigma) for k in (1, 2, 3, 4)] + >>> cm = raw2central(rm) + >>> mean = rm[0] + >>> var = cm[1] + >>> std = var**0.5 + >>> skew = cm[2]/std**3 + >>> kurt = cm[3]/std**4 + >>> [mean, var, skew, kurt] + [0.6065306597, 0.3351234837, 0.6539519888, 2.5584134397] + + Thus, care should be taken to make sure that the equations used are + effectively quadratic within the respective input variable distribution + ranges or you will see approximation errors like the example above. + + """ + + def __init__( + self, + moments: list | None = None, + rv: "ss.rv_continuous | None" = None, + tag: str | None = None, + ) -> None: + if not moments and not rv: + raise ValueError( + "Either the moments must be put in manually or a " + '"rv_continuous" object from the "scipy.stats" ' + "module must be supplied" + ) + + if rv is not None: + loc = rv.kwds.get("loc", 0.0) + scale = rv.kwds.get("scale", 1.0) + shape = rv.args + + mn = rv.mean() + sd = rv.std() + + if shape: + if rv.dist.numargs < 1: + raise ValueError( + "The distribution provided doesn't support" + " a 'shape' parameter" + ) + + def expect(k: int) -> float: + return rv.dist.expect( + lambda x: x**k, args=shape, loc=loc, scale=scale + ) + + raw_moments = [expect(k) for k in range(1, 9)] + moments = raw2central(list(raw_moments)) + for k in range(2, 8): + moments[k] /= sd ** (k + 1) + + else: + if rv.dist.numargs != 0: + raise ValueError( + "The distribution provided requires a third" + " 'shape' parameter" + ) + + def expect(k: int) -> float: + return rv.dist.expect(lambda x: x**k) + + raw_moments = [expect(k) for k in range(1, 9)] + moments = raw2central(list(raw_moments)) + + moments[0] = mn # mean + moments[1] = sd**2 # variance + + self._dist = rv + + else: + self._dist = None + + # Initialise as a leaf node of the AD graph. + # The derivative of this variable with respect to itself is 1; + # all higher derivatives are zero. + UncertainFunction.__init__(self, moments[0], lc={}, qc={}, cp={}) + self._lc[self] = 1.0 # $\partial x/\partial x$ = 1 + self._qc[self] = 0.0 # $\partial^2 x/\partial x^2$ = 0 + + self._tag = tag + self._moments = moments + + def __hash__(self) -> int: + return id(self) + + def __repr__(self) -> str: + if self._tag is not None: + return self._tag + return UncertainFunction.__repr__(self) + + @property + def mean(self) -> float: + return self.moments(0) + + @property + def var(self) -> float: + return self.moments(1) + + @property + def std(self) -> float: + return self.var**0.5 + + @property + def skew(self) -> float: + return self.moments(2) + + @property + def kurt(self) -> float: + return self.moments(3) + + def moments(self, idx: int | None = None) -> "list | float": + if idx is not None and idx < len(self._moments): + return self._moments[idx] + else: + return self._moments + + def set_mean(self, mn: float) -> None: + """Modify the first moment via the mean""" + self._moments[0] = mn + + def set_std(self, sd: float) -> None: + """Modify the second moment via the standard deviation""" + self._moments[1] = sd**2 + + def set_var(self, vr: float) -> None: + """Modify the second moment via the variance""" + self._moments[1] = vr + + def set_skew(self, sk: float) -> None: + """Modify the third moment via the standardized skewness coefficient""" + self._moments[2] = sk + + def set_kurt(self, kt: float) -> None: + """Modify the fourth moment via the standardized kurtosis coefficient""" + self._moments[3] = kt + + def set_moments(self, m: list) -> None: + """Modify the first eight moments of the UncertainVariable's + distribution. + + Raises + ------ + ValueError + If m does not contain exactly eight values. + """ + if len(m) != 8: + raise ValueError("Input moments must include eight values") + self._moments = m + + if matplotlib_installed: + + def plot( + self, vals: "np.ndarray | list | None" = None, **kwargs: float + ) -> None: + """Plot the distribution of the input variable. + + NOTE: This requires defining the input using a distribution from + the ``scipy.stats`` module. + + """ + if self._dist is not None: + if vals is None: + low = self._dist.ppf(0.0001) + high = self._dist.ppf(0.9999) + else: + low = min(vals) + high = max(vals) + vals = np.linspace(low, high, 500) + plt.plot(vals, self._dist.pdf(vals), **kwargs) + plt.xlim(low - (high - low) * 0.1, high + (high - low) * 0.1) + plt.show() + else: + raise NotImplementedError( + "Cannot determine a distribution's " + "pdf only by its moments (yet). Please use a scipy " + "distribution if you want to plot." + ) + + +uv = UncertainVariable # a nicer form for the user diff --git a/soerp_examples.py b/soerp_examples.py index dd72a8c..04ec828 100644 --- a/soerp_examples.py +++ b/soerp_examples.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Created on Mon Jun 24 17:13:42 2013 @@ -6,74 +5,69 @@ """ import math -import os,sys -pwd = r'/'.join(os.path.dirname(os.path.abspath(__file__)).split('/')[:-1]) -if pwd not in sys.path: - sys.path.append(pwd) +from soerp import Chi2, Exp, Gamma, N, umath, uv -from soerp import * -import soerp.umath as umath -print 'UNCERTAIN DISTRIBUTION TEST FUNCTIONS USING GIVEN MOMENTS' -print '*'*80 -print 'Example of a three part assembly' -x1 = uv([24,1,0,3,0,15,0,105]) # normally distributed -x2 = uv([37,16,0,3,0,15,0,105]) # normally distributed -x3 = uv([0.5,0.25,2,9,44,265,1854,14833]) # exponentially distributed +print("UNCERTAIN DISTRIBUTION TEST FUNCTIONS USING GIVEN MOMENTS") +print("*" * 80) +print("Example of a three part assembly") +x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) # normally distributed +x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) # normally distributed +x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) # exponentially distributed -Z = (x1*x2**2)/(15*(1.5+x3)) -print 'Results should be about:' -print ' > Mean................... 1176.45' -print ' > Variance............... 99699.682' -print ' > Skewness Coefficient... 0.70801305' -print ' > Kurtosis Coefficient... 6.1632855' -print Z +Z = (x1 * x2**2) / (15 * (1.5 + x3)) +print("Results should be about:") +print(" > Mean................... 1176.45") +print(" > Variance............... 99699.682") +print(" > Skewness Coefficient... 0.70801305") +print(" > Kurtosis Coefficient... 6.1632855") +print(Z) -print '*'*80 -print 'Example of volumetric gas flow through orifice meter' -H = uv([64,0.25,0,3,0,15,0,105]) # normally distributed -M = uv([16,0.01,0,3,0,15,0,105]) # normally distributed -P = uv([361, 4,0,3,0,15,0,105]) # normally distributed -t = uv([165,0.25,0,3,0,15,0,105]) # normally distributed +print("*" * 80) +print("Example of volumetric gas flow through orifice meter") +H = uv([64, 0.25, 0, 3, 0, 15, 0, 105]) # normally distributed +M = uv([16, 0.01, 0, 3, 0, 15, 0, 105]) # normally distributed +P = uv([361, 4, 0, 3, 0, 15, 0, 105]) # normally distributed +t = uv([165, 0.25, 0, 3, 0, 15, 0, 105]) # normally distributed C = 38.4 -Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) -print 'Results should be about:' -print ' > Mean................... 1330.9997' -print ' > Variance............... 58.210763' -print ' > Skewness Coefficient... 0.010942207' -print ' > Kurtosis Coefficient... 3.0003269' -print Q +Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) +print("Results should be about:") +print(" > Mean................... 1330.9997") +print(" > Variance............... 58.210763") +print(" > Skewness Coefficient... 0.010942207") +print(" > Kurtosis Coefficient... 3.0003269") +print(Q) -print '*'*80 -print 'Example of manufacturing tolerance stackup' -x = uv([1.5,0.25,2/3.,11/3.,0,0,0,0]) # gamma distributed -y = uv([1.5,0.25,2/3.,11/3.,0,0,0,0]) # gamma distributed -z = uv([1.5,0.25,2/3.,11/3.,0,0,0,0]) # gamma distributed -w = x+y+z -print 'Results should be about:' -print ' > Mean................... 4.5' -print ' > Variance............... 0.75' -print ' > Skewness Coefficient... 0.385' -print ' > Kurtosis Coefficient... 3.22' -print w +print("*" * 80) +print("Example of manufacturing tolerance stackup") +x = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) # gamma distributed +y = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) # gamma distributed +z = uv([1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0]) # gamma distributed +w = x + y + z +print("Results should be about:") +print(" > Mean................... 4.5") +print(" > Variance............... 0.75") +print(" > Skewness Coefficient... 0.385") +print(" > Kurtosis Coefficient... 3.22") +print(w) -print '*'*80 -print 'Example of scheduling facilities (six stations)' -s1 = uv([10,1,0,3,0,0,0,0]) # normal distributed -s2 = uv([20,2,0,3,0,0,0,0]) # normal distributed -s3 = uv([1.5,0.25,0.67,3.67,0,0,0,0]) # gamma distributed -s4 = uv([10,10,0.63,3.6,0,0,0,0]) # gamma distributed -s5 = uv([0.2,0.04,2,9,0,0,0,0]) # exponental distributed -s6 = uv([10,20,0.89,4.2,0,0,0,0]) # chi-square distributed +print("*" * 80) +print("Example of scheduling facilities (six stations)") +s1 = uv([10, 1, 0, 3, 0, 0, 0, 0]) # normal distributed +s2 = uv([20, 2, 0, 3, 0, 0, 0, 0]) # normal distributed +s3 = uv([1.5, 0.25, 0.67, 3.67, 0, 0, 0, 0]) # gamma distributed +s4 = uv([10, 10, 0.63, 3.6, 0, 0, 0, 0]) # gamma distributed +s5 = uv([0.2, 0.04, 2, 9, 0, 0, 0, 0]) # exponental distributed +s6 = uv([10, 20, 0.89, 4.2, 0, 0, 0, 0]) # chi-square distributed T = s1 + s2 + s3 + s4 + s5 + s6 -print 'Results should be about:' -print ' > Mean................... 51.7' -print ' > Variance............... 33.3' -print ' > Skewness Coefficient... 0.52' -print ' > Kurtosis Coefficient... 3.49' -print T - +print("Results should be about:") +print(" > Mean................... 51.7") +print(" > Variance............... 33.3") +print(" > Skewness Coefficient... 0.52") +print(" > Kurtosis Coefficient... 3.49") +print(T) + ############################################################################### try: @@ -81,195 +75,194 @@ except ImportError: pass else: + print("*" * 80) + print("SAME TEST FUNCTIONS USING DERIVED MOMENTS FROM SCIPY DISTRIBUTIONS") - print '*'*80 - print 'SAME TEST FUNCTIONS USING DERIVED MOMENTS FROM SCIPY DISTRIBUTIONS' - - print '*'*80 - print 'Example of a three part assembly' - x1 = uv(rv=ss.norm(loc=24, scale=1)) # normally distributed - x2 = uv(rv=ss.norm(loc=37, scale=4)) # normally distributed - x3 = uv(rv=ss.expon(scale=1/2.)) # exponentially distributed - Z = (x1*x2**2)/(15*(1.5 + x3)) - print 'Results should be about:' - print ' > Mean................... 1176.45' - print ' > Variance............... 99699.682' - print ' > Skewness Coefficient... 0.70801305' - print ' > Kurtosis Coefficient... 6.1632855' - print Z + print("*" * 80) + print("Example of a three part assembly") + x1 = uv(rv=ss.norm(loc=24, scale=1)) # normally distributed + x2 = uv(rv=ss.norm(loc=37, scale=4)) # normally distributed + x3 = uv(rv=ss.expon(scale=1 / 2.0)) # exponentially distributed + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + print("Results should be about:") + print(" > Mean................... 1176.45") + print(" > Variance............... 99699.682") + print(" > Skewness Coefficient... 0.70801305") + print(" > Kurtosis Coefficient... 6.1632855") + print(Z) - print '*'*80 - print 'Example of volumetric gas flow through orifice meter' + print("*" * 80) + print("Example of volumetric gas flow through orifice meter") H = uv(rv=ss.norm(loc=64, scale=0.5)) M = uv(rv=ss.norm(loc=16, scale=0.1)) P = uv(rv=ss.norm(loc=361, scale=2)) t = uv(rv=ss.norm(loc=165, scale=0.5)) C = 38.4 - Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) - print 'Results should be about:' - print ' > Mean................... 1330.9997' - print ' > Variance............... 58.210763' - print ' > Skewness Coefficient... 0.010942207' - print ' > Kurtosis Coefficient... 3.0003269' - print Q + Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) + print("Results should be about:") + print(" > Mean................... 1330.9997") + print(" > Variance............... 58.210763") + print(" > Skewness Coefficient... 0.010942207") + print(" > Kurtosis Coefficient... 3.0003269") + print(Q) - print '*'*80 - print 'Example of manufacturing tolerance stackup' + print("*" * 80) + print("Example of manufacturing tolerance stackup") # for a gamma distribution we need the following conversions: # scale = var/mean # shape = mean**2/var mn = 1.5 vr = 0.25 - scale = vr/mn - shape = mn**2/vr - x = uv(rv=ss.gamma(shape, scale=scale)) - y = uv(rv=ss.gamma(shape, scale=scale)) + scale = vr / mn + shape = mn**2 / vr + x = uv(rv=ss.gamma(shape, scale=scale)) + y = uv(rv=ss.gamma(shape, scale=scale)) z = uv(rv=ss.gamma(shape, scale=scale)) w = x + y + z - print 'Results should be about:' - print ' > Mean................... 4.5' - print ' > Variance............... 0.75' - print ' > Skewness Coefficient... 0.385' - print ' > Kurtosis Coefficient... 3.22' - print w + print("Results should be about:") + print(" > Mean................... 4.5") + print(" > Variance............... 0.75") + print(" > Skewness Coefficient... 0.385") + print(" > Kurtosis Coefficient... 3.22") + print(w) - print '*'*80 - print 'Example of scheduling facilities (six stations)' + print("*" * 80) + print("Example of scheduling facilities (six stations)") s1 = uv(rv=ss.norm(loc=10, scale=1)) s2 = uv(rv=ss.norm(loc=20, scale=2**0.5)) mn1 = 1.5 vr1 = 0.25 - scale1 = vr1/mn1 - shape1 = mn1**2/vr1 - s3 = uv(rv=ss.gamma(shape1,scale=scale1)) + scale1 = vr1 / mn1 + shape1 = mn1**2 / vr1 + s3 = uv(rv=ss.gamma(shape1, scale=scale1)) mn2 = 10 vr2 = 10 - scale2 = vr2/mn2 - shape2 = mn2**2/vr2 + scale2 = vr2 / mn2 + shape2 = mn2**2 / vr2 s4 = uv(rv=ss.gamma(shape2, scale=scale2)) s5 = uv(rv=ss.expon(scale=0.2)) s6 = uv(rv=ss.chi2(10)) T = s1 + s2 + s3 + s4 + s5 + s6 - print 'Results should be about:' - print ' > Mean................... 51.7' - print ' > Variance............... 33.3' - print ' > Skewness Coefficient... 0.52' - print ' > Kurtosis Coefficient... 3.49' - print T + print("Results should be about:") + print(" > Mean................... 51.7") + print(" > Variance............... 33.3") + print(" > Skewness Coefficient... 0.52") + print(" > Kurtosis Coefficient... 3.49") + print(T) - print '*'*80 - print 'Example of two-bar truss' - H = uv(rv=ss.norm(loc=30, scale=5/3.), tag='H') - B = uv(rv=ss.norm(loc=60, scale=0.5/3.), tag='B') - d = uv(rv=ss.norm(loc=3, scale=0.1/3), tag='d') - t = uv(rv=ss.norm(loc=0.15, scale=0.01/3), tag='t') - E = uv(rv=ss.norm(loc=30000, scale=1500/3.), tag='E') - rho = uv(rv=ss.norm(loc=0.3, scale=0.01/3.), tag='rho') - P = uv(rv=ss.norm(loc=66, scale=3/3.), tag='P') + print("*" * 80) + print("Example of two-bar truss") + H = uv(rv=ss.norm(loc=30, scale=5 / 3.0), tag="H") + B = uv(rv=ss.norm(loc=60, scale=0.5 / 3.0), tag="B") + d = uv(rv=ss.norm(loc=3, scale=0.1 / 3), tag="d") + t = uv(rv=ss.norm(loc=0.15, scale=0.01 / 3), tag="t") + E = uv(rv=ss.norm(loc=30000, scale=1500 / 3.0), tag="E") + rho = uv(rv=ss.norm(loc=0.3, scale=0.01 / 3.0), tag="rho") + P = uv(rv=ss.norm(loc=66, scale=3 / 3.0), tag="P") pi = math.pi - wght = 2*pi*rho*d*t*umath.sqrt((B/2)**2 + H**2) - strs = (P*umath.sqrt((B/2)**2 + H**2))/(2*pi*d*t*H) - buck = (pi**2*E*(d**2 + t**2))/(8*((B/2)**2 + H**2)) - defl = (P*((B/2)**2 + H**2)**(1.5))/(2*pi*d*t*H**2*E) - print 'wght:', wght - print 'strs:', strs - print 'buck:', buck - print 'defl:', defl + wght = 2 * pi * rho * d * t * umath.sqrt((B / 2) ** 2 + H**2) + strs = (P * umath.sqrt((B / 2) ** 2 + H**2)) / (2 * pi * d * t * H) + buck = (pi**2 * E * (d**2 + t**2)) / (8 * ((B / 2) ** 2 + H**2)) + defl = (P * ((B / 2) ** 2 + H**2) ** (1.5)) / (2 * pi * d * t * H**2 * E) + print("wght:", wght) + print("strs:", strs) + print("buck:", buck) + print("defl:", defl) wght.error_components(pprint=True) - -############################################################################### - - print '*'*80 - print 'SAME TEST FUNCTIONS USING EASY DISTRIBUTION CONSTRUCTORS' - - print '*'*80 - print 'Example of a three part assembly' - x1 = N(24, 1) # normally distributed - x2 = N(37, 4) # normally distributed - x3 = Exp(2) # exponentially distributed - Z = (x1*x2**2)/(15*(1.5 + x3)) - print 'Results should be about:' - print ' > Mean................... 1176.45' - print ' > Variance............... 99699.682' - print ' > Skewness Coefficient... 0.70801305' - print ' > Kurtosis Coefficient... 6.1632855' - print Z - print '*'*80 - print 'Example of volumetric gas flow through orifice meter' + ############################################################################### + + print("*" * 80) + print("SAME TEST FUNCTIONS USING EASY DISTRIBUTION CONSTRUCTORS") + + print("*" * 80) + print("Example of a three part assembly") + x1 = N(24, 1) # normally distributed + x2 = N(37, 4) # normally distributed + x3 = Exp(2) # exponentially distributed + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + print("Results should be about:") + print(" > Mean................... 1176.45") + print(" > Variance............... 99699.682") + print(" > Skewness Coefficient... 0.70801305") + print(" > Kurtosis Coefficient... 6.1632855") + print(Z) + + print("*" * 80) + print("Example of volumetric gas flow through orifice meter") H = N(64, 0.5) M = N(16, 0.1) P = N(361, 2) t = N(165, 0.5) C = 38.4 - Q = C*umath.sqrt((520*H*P)/(M*(t + 460))) - print 'Results should be about:' - print ' > Mean................... 1330.9997' - print ' > Variance............... 58.210763' - print ' > Skewness Coefficient... 0.010942207' - print ' > Kurtosis Coefficient... 3.0003269' - print Q + Q = C * umath.sqrt((520 * H * P) / (M * (t + 460))) + print("Results should be about:") + print(" > Mean................... 1330.9997") + print(" > Variance............... 58.210763") + print(" > Skewness Coefficient... 0.010942207") + print(" > Kurtosis Coefficient... 3.0003269") + print(Q) - print '*'*80 - print 'Example of manufacturing tolerance stackup' + print("*" * 80) + print("Example of manufacturing tolerance stackup") # for a gamma distribution we need the following conversions: # scale = var/mean # shape = mean**2/var mn = 1.5 vr = 0.25 - scale = vr/mn - shape = mn**2/vr + scale = vr / mn + shape = mn**2 / vr x = Gamma(shape, scale) y = Gamma(shape, scale) z = Gamma(shape, scale) w = x + y + z - print 'Results should be about:' - print ' > Mean................... 4.5' - print ' > Variance............... 0.75' - print ' > Skewness Coefficient... 0.385' - print ' > Kurtosis Coefficient... 3.22' - print w + print("Results should be about:") + print(" > Mean................... 4.5") + print(" > Variance............... 0.75") + print(" > Skewness Coefficient... 0.385") + print(" > Kurtosis Coefficient... 3.22") + print(w) - print '*'*80 - print 'Example of scheduling facilities (six stations)' + print("*" * 80) + print("Example of scheduling facilities (six stations)") s1 = N(10, 1) s2 = N(20, 2**0.5) mn1 = 1.5 vr1 = 0.25 - scale1 = vr1/mn1 - shape1 = mn1**2/vr1 + scale1 = vr1 / mn1 + shape1 = mn1**2 / vr1 s3 = Gamma(shape1, scale1) mn2 = 10 vr2 = 10 - scale2 = vr2/mn2 - shape2 = mn2**2/vr2 + scale2 = vr2 / mn2 + shape2 = mn2**2 / vr2 s4 = Gamma(shape2, scale2) s5 = Exp(5) s6 = Chi2(10) T = s1 + s2 + s3 + s4 + s5 + s6 - print 'Results should be about:' - print ' > Mean................... 51.7' - print ' > Variance............... 33.3' - print ' > Skewness Coefficient... 0.52' - print ' > Kurtosis Coefficient... 3.49' - print T + print("Results should be about:") + print(" > Mean................... 51.7") + print(" > Variance............... 33.3") + print(" > Skewness Coefficient... 0.52") + print(" > Kurtosis Coefficient... 3.49") + print(T) - print '*'*80 - print 'Example of two-bar truss' - H = N(30, 5/3., tag='H') - B = N(60, 0.5/3., tag='B') - d = N(3, 0.1/3, tag='d') - t = N(0.15, 0.01/3, tag='t') - E = N(30000, 1500/3., tag='E') - rho = N(0.3, 0.01/3., tag='rho') - P = N(66, 3/3., tag='P') + print("*" * 80) + print("Example of two-bar truss") + H = N(30, 5 / 3.0, tag="H") + B = N(60, 0.5 / 3.0, tag="B") + d = N(3, 0.1 / 3, tag="d") + t = N(0.15, 0.01 / 3, tag="t") + E = N(30000, 1500 / 3.0, tag="E") + rho = N(0.3, 0.01 / 3.0, tag="rho") + P = N(66, 3 / 3.0, tag="P") pi = math.pi - wght = 2*pi*rho*d*t*umath.sqrt((B/2)**2 + H**2) - strs = (P*umath.sqrt((B/2)**2 + H**2))/(2*pi*d*t*H) - buck = (pi**2*E*(d**2 + t**2))/(8*((B/2)**2 + H**2)) - defl = (P*((B/2)**2 + H**2)**(1.5))/(2*pi*d*t*H**2*E) - print 'wght:', wght - print 'strs:', strs - print 'buck:', buck - print 'defl:', defl - wght.error_components(pprint=True) + wght = 2 * pi * rho * d * t * umath.sqrt((B / 2) ** 2 + H**2) + strs = (P * umath.sqrt((B / 2) ** 2 + H**2)) / (2 * pi * d * t * H) + buck = (pi**2 * E * (d**2 + t**2)) / (8 * ((B / 2) ** 2 + H**2)) + defl = (P * ((B / 2) ** 2 + H**2) ** (1.5)) / (2 * pi * d * t * H**2 * E) + print("wght:", wght) + print("strs:", strs) + print("buck:", buck) + print("defl:", defl) + wght.error_components(pprint=True) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_ad.py b/tests/test_ad.py new file mode 100644 index 0000000..5db8fb0 --- /dev/null +++ b/tests/test_ad.py @@ -0,0 +1,111 @@ +""" +Tests for basic AD (automatic differentiation) correctness. +Verifies derivative tracking for simple hand-checkable cases. +""" + +import pytest + +from soerp import N, uv + + +class TestADCorrectness: + def test_linear_mean_and_var(self): + # z = a*x + b*y → var(z) = $a^2 \cdot \text{var}(x) + b^2 \cdot \text{var}(y)$ for independent x, y # noqa: E501 + x = uv([2.0, 1.0, 0, 3, 0, 15, 0, 105]) + y = uv([3.0, 4.0, 0, 3, 0, 15, 0, 105]) + z = 2 * x + 3 * y + assert z.mean == pytest.approx(2 * 2.0 + 3 * 3.0) + assert z.var == pytest.approx(4 * 1.0 + 9 * 4.0) + + def test_product_mean(self): + # z = x*y → E[z] ≈ E[x]*E[y] (first-order) + x = uv([3.0, 0.01, 0, 3, 0, 15, 0, 105]) + y = uv([4.0, 0.01, 0, 3, 0, 15, 0, 105]) + z = x * y + assert z.mean == pytest.approx(12.0, rel=1e-3) + + def test_constant_propagation(self): + x = uv([5.0, 1.0, 0, 3, 0, 15, 0, 105]) + z = x + 0 # adding zero constant should not change var + assert z.var == pytest.approx(x.var, rel=1e-10) + + def test_subtraction_cancels(self): + x = uv([2.0, 1.0, 0, 3, 0, 15, 0, 105]) + z = x - x # same variable → variance = 0 + assert z.var == pytest.approx(0.0, abs=1e-10) + assert z.mean == pytest.approx(0.0, abs=1e-10) + + def test_power_two(self): + # z = x^2 → mean ≈ E[x]^2 + var(x), var ≈ 4·E[x]^2·var(x) for normal + mn, vr = 3.0, 0.01 + x = uv([mn, vr, 0, 3, 0, 15, 0, 105]) + z = x**2 + # second-order mean correction: E[x^2] = E[x]^2 + var(x) + assert z.mean == pytest.approx(mn**2 + vr, rel=1e-3) + + def test_first_derivative_stored(self): + x = N(2.0, 1.0) + y = 3 * x + # $\partial y/\partial x$ should be 3 + assert y.d(x) == pytest.approx(3.0) + + def test_second_derivative_stored(self): + x = N(2.0, 1.0) + y = x**2 + # $d^2y/dx^2$ = 2 + assert y.d2(x) == pytest.approx(2.0) + + def test_cross_derivative_stored(self): + x = N(2.0, 0.5) + y = N(3.0, 0.5) + z = x * y + # $\partial^2 (xy)/(\partial x \partial y)$ = 1 + assert z.d2c(x, y) == pytest.approx(1.0) + + def test_uv_variable_moments_passthrough(self): + moments = [5.0, 2.0, 0.5, 3.5, 0, 0, 0, 0] + x = uv(moments) + assert x.mean == pytest.approx(5.0) + assert x.var == pytest.approx(2.0) + assert x.skew == pytest.approx(0.5) + assert x.kurt == pytest.approx(3.5) + + def test_std_property(self): + x = uv([0.0, 4.0, 0, 3, 0, 15, 0, 105]) + assert x.std == pytest.approx(2.0) + + def test_gradient_and_hessian(self): + # Z = (x1 * x2**2) / (15 * (1.5 + x3)) + x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) + x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) + x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + + # Test first and second derivatives + # dZ/dx1 = 45.63333333333333 + assert Z.d(x1) == pytest.approx(45.63333333333333, rel=1e-10) + # d^2Z/dx2^2 = 1.6 + assert Z.d2(x2) == pytest.approx(1.6, rel=1e-10) + # d^2Z/dx1dx3 = -22.816666666666666 + assert Z.d2c(x1, x3) == pytest.approx(-22.816666666666666, rel=1e-10) + + # Test gradient + grad = Z.gradient([x1, x2, x3]) + expected_grad = [45.63333333333333, 59.199999999999996, -547.6] + assert all( + pytest.approx(g, rel=1e-10) == e + for g, e in zip(grad, expected_grad, strict=True) + ) + + # Test hessian + hess = Z.hessian([x1, x2, x3]) + expected_hess = [ + [0.0, 2.466666666666667, -22.816666666666666], + [2.466666666666667, 1.6, -29.6], + [-22.816666666666666, -29.6, 547.6], + ] + for row, expected_row in zip(hess, expected_hess, strict=True): + assert all( + pytest.approx(v, rel=1e-10) == e + for v, e in zip(row, expected_row, strict=True) + ) diff --git a/tests/test_assembly.py b/tests/test_assembly.py new file mode 100644 index 0000000..63054f0 --- /dev/null +++ b/tests/test_assembly.py @@ -0,0 +1,66 @@ +r""" +Tests for Three-Part Assembly example. + +$Z = \frac{x_1 \cdot x_2^2}{15 \cdot (1.5 + x_3)}$ + +where: +$x_1 \sim \mathcal{N}(24, 1)$ +$x_2 \sim \mathcal{N}(37, 4)$ +$x_3 \sim \text{Exp}(\lambda=2)$ +""" + +import pytest +import scipy.stats as ss + +from soerp import Exp, N, uv + + +TIGHT = dict(rel=1e-4) +LOOSE = dict(rel=2e-3) + + +def _moments(uf): + return uf.mean, uf.var, uf.skew, uf.kurt + + +class TestThreePartAssembly: + MEAN = 1176.45 + VAR = 99699.682 + SKEW = 0.70801305 + KURT = 6.1632855 + + def test_moments_input(self): + x1 = uv([24, 1, 0, 3, 0, 15, 0, 105]) + x2 = uv([37, 16, 0, 3, 0, 15, 0, 105]) + x3 = uv([0.5, 0.25, 2, 9, 44, 265, 1854, 14833]) + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + mn, vr, sk, kt = _moments(Z) + assert mn == pytest.approx(self.MEAN, **TIGHT) + assert vr == pytest.approx(self.VAR, **TIGHT) + assert sk == pytest.approx(self.SKEW, **TIGHT) + # kurtosis is a 4th-order quantity; ~0.4 % tolerance + assert kt == pytest.approx(self.KURT, rel=5e-3) + + def test_scipy_rv(self): + x1 = uv(rv=ss.norm(loc=24, scale=1)) + x2 = uv(rv=ss.norm(loc=37, scale=4)) + x3 = uv(rv=ss.expon(scale=0.5)) + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + mn, vr, sk, kt = _moments(Z) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, **LOOSE) + # kurtosis is a 4th-order quantity; ~0.4 % tolerance + assert kt == pytest.approx(self.KURT, rel=5e-3) + + def test_constructors(self): + x1 = N(24, 1) + x2 = N(37, 4) + x3 = Exp(2) + Z = (x1 * x2**2) / (15 * (1.5 + x3)) + mn, vr, sk, kt = _moments(Z) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, **LOOSE) + # kurtosis is a 4th-order quantity; ~0.4 % tolerance + assert kt == pytest.approx(self.KURT, rel=5e-3) diff --git a/tests/test_covariance.py b/tests/test_covariance.py new file mode 100644 index 0000000..80df44c --- /dev/null +++ b/tests/test_covariance.py @@ -0,0 +1,44 @@ +""" +Tests for covariance and correlation matrix utilities. +""" + +import pytest + +from soerp import N, correlation_matrix, covariance_matrix + + +class TestCovarianceMatrix: + def test_covariance_matrix(self): + x = N(1, 0.1) + y = N(10, 0.1) + z = x + 2 * y + cov = covariance_matrix([x, y, z]) + assert cov[0][0] == pytest.approx(0.01, rel=1e-6) + assert cov[1][1] == pytest.approx(0.01, rel=1e-6) + assert cov[2][2] == pytest.approx(0.05, rel=1e-3) + assert cov[0][1] == pytest.approx(0.0, abs=1e-10) + assert cov[0][2] == pytest.approx(0.01, rel=1e-6) + assert cov[1][2] == pytest.approx(0.02, rel=1e-6) + # symmetry + assert cov[1][0] == cov[0][1] + assert cov[2][0] == cov[0][2] + assert cov[2][1] == cov[1][2] + + def test_correlation_matrix_diagonal_is_one(self): + x = N(1, 0.1) + y = N(10, 0.5) + corr = correlation_matrix([x, y]) + assert corr[0][0] == pytest.approx(1.0, abs=1e-10) + assert corr[1][1] == pytest.approx(1.0, abs=1e-10) + + def test_correlation_matrix_independent(self): + x = N(0, 1) + y = N(0, 1) + corr = correlation_matrix([x, y]) + assert corr[0][1] == pytest.approx(0.0, abs=1e-10) + + def test_correlation_matrix_perfect_correlation(self): + x = N(5, 2) + z = x + 0 # identical to x (same variable) + corr = correlation_matrix([x, z]) + assert corr[0][1] == pytest.approx(1.0, rel=1e-6) diff --git a/tests/test_orifice.py b/tests/test_orifice.py new file mode 100644 index 0000000..574e6bf --- /dev/null +++ b/tests/test_orifice.py @@ -0,0 +1,62 @@ +""" +Tests for Orifice Flow Meter example. + Q = C * sqrt(520*H*P / (M*(t+460))) + H ~ N(64, 0.5), M ~ N(16, 0.1), P ~ N(361, 2), t ~ N(165, 0.5) +""" + +import pytest +import scipy.stats as ss + +from soerp import N, umath, uv + + +TIGHT = dict(rel=1e-4) +LOOSE = dict(rel=2e-3) + + +def _moments(uf): + return uf.mean, uf.var, uf.skew, uf.kurt + + +class TestOrificeFlowMeter: + C = 38.4 + MEAN = 1330.9997 + VAR = 58.210763 + SKEW = 0.010942207 + KURT = 3.0003269 + + def test_moments_input(self): + H = uv([64, 0.25, 0, 3, 0, 15, 0, 105]) + M = uv([16, 0.01, 0, 3, 0, 15, 0, 105]) + P = uv([361, 4, 0, 3, 0, 15, 0, 105]) + t = uv([165, 0.25, 0, 3, 0, 15, 0, 105]) + Q = self.C * umath.sqrt((520 * H * P) / (M * (t + 460))) + mn, vr, sk, kt = _moments(Q) + assert mn == pytest.approx(self.MEAN, **TIGHT) + assert vr == pytest.approx(self.VAR, **TIGHT) + assert sk == pytest.approx(self.SKEW, **TIGHT) + assert kt == pytest.approx(self.KURT, **TIGHT) + + def test_scipy_rv(self): + H = uv(rv=ss.norm(loc=64, scale=0.5)) + M = uv(rv=ss.norm(loc=16, scale=0.1)) + P = uv(rv=ss.norm(loc=361, scale=2)) + t = uv(rv=ss.norm(loc=165, scale=0.5)) + Q = self.C * umath.sqrt((520 * H * P) / (M * (t + 460))) + mn, vr, sk, kt = _moments(Q) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, **LOOSE) + assert kt == pytest.approx(self.KURT, **LOOSE) + + def test_constructors(self): + H = N(64, 0.5) + M = N(16, 0.1) + P = N(361, 2) + t = N(165, 0.5) + Q = self.C * umath.sqrt((520 * H * P) / (M * (t + 460))) + mn, vr, sk, kt = _moments(Q) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, **LOOSE) + assert kt == pytest.approx(self.KURT, **LOOSE) diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py new file mode 100644 index 0000000..69eb16b --- /dev/null +++ b/tests/test_scheduling.py @@ -0,0 +1,66 @@ +""" +Tests for Scheduling Facilities example (six stations). + T = s1 + s2 + s3 + s4 + s5 + s6 +""" + +import pytest +import scipy.stats as ss + +from soerp import Chi2, Exp, Gamma, N, uv + + +TIGHT = dict(rel=1e-4) +LOOSE = dict(rel=2e-3) + + +def _moments(uf): + return uf.mean, uf.var, uf.skew, uf.kurt + + +class TestSchedulingFacilities: + MEAN = 51.7 + VAR = 33.3 + SKEW = 0.52 + KURT = 3.49 + + def test_moments_input(self): + s1 = uv([10, 1, 0, 3, 0, 0, 0, 0]) + s2 = uv([20, 2, 0, 3, 0, 0, 0, 0]) + s3 = uv([1.5, 0.25, 0.67, 3.67, 0, 0, 0, 0]) + s4 = uv([10, 10, 0.63, 3.6, 0, 0, 0, 0]) + s5 = uv([0.2, 0.04, 2, 9, 0, 0, 0, 0]) + s6 = uv([10, 20, 0.89, 4.2, 0, 0, 0, 0]) + T = s1 + s2 + s3 + s4 + s5 + s6 + mn, vr, sk, kt = _moments(T) + assert mn == pytest.approx(self.MEAN, **TIGHT) + assert vr == pytest.approx(self.VAR, rel=1e-3) + assert sk == pytest.approx(self.SKEW, rel=5e-3) + assert kt == pytest.approx(self.KURT, rel=5e-3) + + def test_scipy_rv(self): + s1 = uv(rv=ss.norm(loc=10, scale=1)) + s2 = uv(rv=ss.norm(loc=20, scale=2**0.5)) + s3 = uv(rv=ss.gamma(9, scale=1 / 6.0)) + s4 = uv(rv=ss.gamma(10, scale=1)) + s5 = uv(rv=ss.expon(scale=0.2)) + s6 = uv(rv=ss.chi2(10)) + T = s1 + s2 + s3 + s4 + s5 + s6 + mn, vr, sk, kt = _moments(T) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, rel=5e-3) + assert sk == pytest.approx(self.SKEW, rel=1e-2) + assert kt == pytest.approx(self.KURT, rel=1e-2) + + def test_constructors(self): + s1 = N(10, 1) + s2 = N(20, 2**0.5) + s3 = Gamma(9, 1 / 6.0) + s4 = Gamma(10, 1) + s5 = Exp(5) + s6 = Chi2(10) + T = s1 + s2 + s3 + s4 + s5 + s6 + mn, vr, sk, kt = _moments(T) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, rel=5e-3) + assert sk == pytest.approx(self.SKEW, rel=1e-2) + assert kt == pytest.approx(self.KURT, rel=1e-2) diff --git a/tests/test_stackup.py b/tests/test_stackup.py new file mode 100644 index 0000000..2e05ccc --- /dev/null +++ b/tests/test_stackup.py @@ -0,0 +1,63 @@ +""" +Tests for Manufacturing Tolerance Stackup example. + w = x + y + z, each ~ Gamma(shape=9, scale=1/6) +""" + +import pytest +import scipy.stats as ss + +from soerp import Gamma, uv + + +LOOSE = dict(rel=2e-3) + + +def _moments(uf): + return uf.mean, uf.var, uf.skew, uf.kurt + + +class TestToleranceStackup: + MEAN = 4.5 + VAR = 0.75 + SKEW = 0.385 + KURT = 3.22 + + def _gamma_moments(self): + return [1.5, 0.25, 2 / 3.0, 11 / 3.0, 0, 0, 0, 0] + + def test_moments_input(self): + m = self._gamma_moments() + x, y, z = uv(m), uv(m), uv(m) + w = x + y + z + mn, vr, sk, kt = _moments(w) + assert mn == pytest.approx(self.MEAN, rel=1e-4) + assert vr == pytest.approx(self.VAR, rel=1e-4) + assert sk == pytest.approx(self.SKEW, rel=1e-3) + assert kt == pytest.approx(self.KURT, rel=1e-3) + + def test_scipy_rv(self): + mn_val, vr_val = 1.5, 0.25 + shape = mn_val**2 / vr_val + scale = vr_val / mn_val + rv = ss.gamma(shape, scale=scale) + x, y, z = uv(rv=rv), uv(rv=rv), uv(rv=rv) + w = x + y + z + mn, vr, sk, kt = _moments(w) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, rel=5e-3) + assert kt == pytest.approx(self.KURT, rel=5e-3) + + def test_constructors(self): + mn_val, vr_val = 1.5, 0.25 + shape = mn_val**2 / vr_val + scale = vr_val / mn_val + x = Gamma(shape, scale) + y = Gamma(shape, scale) + z = Gamma(shape, scale) + w = x + y + z + mn, vr, sk, kt = _moments(w) + assert mn == pytest.approx(self.MEAN, **LOOSE) + assert vr == pytest.approx(self.VAR, **LOOSE) + assert sk == pytest.approx(self.SKEW, rel=5e-3) + assert kt == pytest.approx(self.KURT, rel=5e-3) diff --git a/tests/test_truss.py b/tests/test_truss.py new file mode 100644 index 0000000..efe9f05 --- /dev/null +++ b/tests/test_truss.py @@ -0,0 +1,71 @@ +""" +Tests for Two-Bar Truss example. +Tests that means match deterministic values and variances are positive. +""" + +import math + +import pytest + +from soerp import N, umath + + +class TestTwoBarTruss: + pi = math.pi + + def _build(self, cls): + H = cls(30, 5 / 3.0, tag="H") + B = cls(60, 0.5 / 3.0, tag="B") + d = cls(3, 0.1 / 3, tag="d") + t = cls(0.15, 0.01 / 3, tag="t") + E = cls(30000, 1500 / 3.0, tag="E") + rho = cls(0.3, 0.01 / 3.0, tag="rho") + P = cls(66, 1.0, tag="P") + return H, B, d, t, E, rho, P + + def _compute(self, H, B, d, t, E, rho, P): # noqa: PLR0913, PLR0917 + pi = self.pi + wght = 2 * pi * rho * d * t * umath.sqrt((B / 2) ** 2 + H**2) + strs = (P * umath.sqrt((B / 2) ** 2 + H**2)) / (2 * pi * d * t * H) + buck = (pi**2 * E * (d**2 + t**2)) / (8 * ((B / 2) ** 2 + H**2)) + defl = (P * ((B / 2) ** 2 + H**2) ** 1.5) / (2 * pi * d * t * H**2 * E) + return wght, strs, buck, defl + + def test_means_match_deterministic(self): # noqa: PLR0914 + H, B, d, t, E, rho, P = self._build(N) + wght, strs, buck, defl = self._compute(H, B, d, t, E, rho, P) + + Hv, Bv, dv, tv, Ev, rv, Pv = 30, 60, 3, 0.15, 30000, 0.3, 66 + r2 = (Bv / 2) ** 2 + Hv**2 + wght_det = 2 * self.pi * rv * dv * tv * math.sqrt(r2) + strs_det = (Pv * math.sqrt(r2)) / (2 * self.pi * dv * tv * Hv) + buck_det = (self.pi**2 * Ev * (dv**2 + tv**2)) / (8 * r2) + defl_det = (Pv * r2**1.5) / (2 * self.pi * dv * tv * Hv**2 * Ev) + + # wght is a product formula - small 2nd-order correction to the mean + assert wght.mean == pytest.approx(wght_det, rel=1e-3) + # strs/defl include 1/H terms so the 2nd-order mean correction is + # larger (~0.3 %); use a 1 % tolerance for those + assert strs.mean == pytest.approx(strs_det, rel=1e-2) + assert buck.mean == pytest.approx(buck_det, rel=1e-2) + assert defl.mean == pytest.approx(defl_det, rel=1e-2) + + def test_variances_positive(self): + H, B, d, t, E, rho, P = self._build(N) + wght, strs, buck, defl = self._compute(H, B, d, t, E, rho, P) + assert wght.var > 0 + assert strs.var > 0 + assert buck.var > 0 + assert defl.var > 0 + + def test_error_components_sum_to_variance(self): + H, B, d, t, E, rho, P = self._build(N) + wght, *_ = self._compute(H, B, d, t, E, rho, P) + ec = wght.error_components() + assert sum(ec.values()) == pytest.approx(wght.var, rel=1e-6) + + def test_tags_preserved(self): + H, B, _d, _t, _E, rho, _P = self._build(N) + assert repr(H) == "H" + assert repr(B) == "B" + assert repr(rho) == "rho" diff --git a/tests/test_umath.py b/tests/test_umath.py new file mode 100644 index 0000000..ea789da --- /dev/null +++ b/tests/test_umath.py @@ -0,0 +1,94 @@ +""" +Tests for soerp.umath functions — scalar pass-through and UF propagation. +""" + +import math + +import pytest + +from soerp import N, umath + + +class TestUmath: + """Verify that umath functions return the correct value at the mean + and that uncertainty is propagated (variance > 0).""" + + def test_sqrt_scalar(self): + assert umath.sqrt(4.0) == pytest.approx(2.0) + + def test_sqrt_uncertain(self): + x = N(4.0, 0.1) + r = umath.sqrt(x) + assert r.mean == pytest.approx(2.0, rel=1e-3) + assert r.var > 0 + + def test_sin_scalar(self): + assert umath.sin(0.0) == pytest.approx(0.0, abs=1e-15) + + def test_sin_uncertain(self): + x = N(math.pi / 6, 0.01) # $\sin(\pi/6) = 0.5$ + r = umath.sin(x) + assert r.mean == pytest.approx(0.5, rel=1e-3) + assert r.var > 0 + + def test_cos_scalar(self): + assert umath.cos(0.0) == pytest.approx(1.0) + + def test_exp_scalar(self): + assert umath.exp(1.0) == pytest.approx(math.e) + + def test_exp_uncertain(self): + # E[exp(X)] for X~N(0, sigma) includes the second-order correction: + # E[exp(X)] ≈ $\exp(\mu) \cdot (1 + \sigma^2/2) = \exp(0) \cdot (1 + 0.01/2) = 1.005$ # noqa: E501 + x = N(0.0, 0.1) + r = umath.exp(x) + assert r.mean == pytest.approx(1.005, rel=1e-3) + assert r.var > 0 + + def test_log_scalar(self): + assert umath.log(math.e) == pytest.approx(1.0) + + def test_log_uncertain(self): + x = N(1.0, 0.01) + r = umath.log(x) + assert r.mean == pytest.approx(0.0, abs=1e-4) + assert r.var > 0 + + def test_ln_equals_log_base_e(self): + x = N(2.0, 0.1) + assert umath.ln(x).mean == pytest.approx(umath.log(x).mean, rel=1e-10) + + def test_atan2_scalar(self): + assert umath.atan2(1.0, 1.0) == pytest.approx(math.pi / 4) + + def test_hypot_scalar(self): + assert umath.hypot(3.0, 4.0) == pytest.approx(5.0) + + def test_hypot_uncertain(self): + x = N(3.0, 0.1) + y = N(4.0, 0.1) + r = umath.hypot(x, y) + assert r.mean == pytest.approx(5.0, rel=1e-3) + assert r.var > 0 + + def test_erf_scalar(self): + assert umath.erf(0.0) == pytest.approx(0.0, abs=1e-15) + + def test_erfc_scalar(self): + assert umath.erfc(0.0) == pytest.approx(1.0) + + def test_degrees_scalar(self): + assert umath.degrees(math.pi) == pytest.approx(180.0) + + def test_radians_scalar(self): + assert umath.radians(180.0) == pytest.approx(math.pi) + + def test_floor_uncertain(self): + x = N(3.7, 0.01) + r = umath.floor(x) + assert r.mean == pytest.approx(3.0) + + def test_ceil_uncertain(self): + x = N(3.2, 0.01) + r = umath.ceil(x) + assert r.mean == pytest.approx(4.0) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..2f53011 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1351 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "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 = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "coverage" +version = "7.13.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/33/e8c48488c29a73fd089f9d71f9653c1be7478f2ad6b5bc870db11a55d23d/coverage-7.13.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0723d2c96324561b9aa76fb982406e11d93cdb388a7a7da2b16e04719cf7ca5", size = 219255, upload-time = "2026-03-17T10:29:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/da/bd/b0ebe9f677d7f4b74a3e115eec7ddd4bcf892074963a00d91e8b164a6386/coverage-7.13.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52f444e86475992506b32d4e5ca55c24fc88d73bcbda0e9745095b28ef4dc0cf", size = 219772, upload-time = "2026-03-17T10:29:52.867Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/5cb9502f4e01972f54eedd48218bb203fe81e294be606a2bc93970208013/coverage-7.13.5-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:704de6328e3d612a8f6c07000a878ff38181ec3263d5a11da1db294fa6a9bdf8", size = 246532, upload-time = "2026-03-17T10:29:54.688Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d8/3217636d86c7e7b12e126e4f30ef1581047da73140614523af7495ed5f2d/coverage-7.13.5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a1a6d79a14e1ec1832cabc833898636ad5f3754a678ef8bb4908515208bf84f4", size = 248333, upload-time = "2026-03-17T10:29:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/2b/30/2002ac6729ba2d4357438e2ed3c447ad8562866c8c63fc16f6dfc33afe56/coverage-7.13.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79060214983769c7ba3f0cee10b54c97609dca4d478fa1aa32b914480fd5738d", size = 250211, upload-time = "2026-03-17T10:29:57.938Z" }, + { url = "https://files.pythonhosted.org/packages/6c/85/552496626d6b9359eb0e2f86f920037c9cbfba09b24d914c6e1528155f7d/coverage-7.13.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:356e76b46783a98c2a2fe81ec79df4883a1e62895ea952968fb253c114e7f930", size = 252125, upload-time = "2026-03-17T10:29:59.388Z" }, + { url = "https://files.pythonhosted.org/packages/44/21/40256eabdcbccdb6acf6b381b3016a154399a75fe39d406f790ae84d1f3c/coverage-7.13.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cef0cdec915d11254a7f549c1170afecce708d30610c6abdded1f74e581666d", size = 247219, upload-time = "2026-03-17T10:30:01.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/96e2a6c3f21a0ea77d7830b254a1542d0328acc8d7bdf6a284ba7e529f77/coverage-7.13.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dc022073d063b25a402454e5712ef9e007113e3a676b96c5f29b2bda29352f40", size = 248248, upload-time = "2026-03-17T10:30:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/da/ba/8477f549e554827da390ec659f3c38e4b6d95470f4daafc2d8ff94eaa9c2/coverage-7.13.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9b74db26dfea4f4e50d48a4602207cd1e78be33182bc9cbf22da94f332f99878", size = 246254, upload-time = "2026-03-17T10:30:04.832Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/bc22aef0e6aa179d5b1b001e8b3654785e9adf27ef24c93dc4228ebd5d68/coverage-7.13.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ad146744ca4fd09b50c482650e3c1b1f4dfa1d4792e0a04a369c7f23336f0400", size = 250067, upload-time = "2026-03-17T10:30:06.535Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/c6a023a160806a5137dca53468fd97530d6acad24a22003b1578a9c2e429/coverage-7.13.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:c555b48be1853fe3997c11c4bd521cdd9a9612352de01fa4508f16ec341e6fe0", size = 246521, upload-time = "2026-03-17T10:30:08.486Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3f/3532c85a55aa2f899fa17c186f831cfa1aa434d88ff792a709636f64130e/coverage-7.13.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7034b5c56a58ae5e85f23949d52c14aca2cfc6848a31764995b7de88f13a1ea0", size = 247126, upload-time = "2026-03-17T10:30:09.966Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2e/b9d56af4a24ef45dfbcda88e06870cb7d57b2b0bfa3a888d79b4c8debd76/coverage-7.13.5-cp310-cp310-win32.whl", hash = "sha256:eb7fdf1ef130660e7415e0253a01a7d5a88c9c4d158bcf75cbbd922fd65a5b58", size = 221860, upload-time = "2026-03-17T10:30:11.393Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cc/d938417e7a4d7f0433ad4edee8bb2acdc60dc7ac5af19e2a07a048ecbee3/coverage-7.13.5-cp310-cp310-win_amd64.whl", hash = "sha256:3e1bb5f6c78feeb1be3475789b14a0f0a5b47d505bfc7267126ccbd50289999e", size = 222788, upload-time = "2026-03-17T10:30:12.886Z" }, + { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" }, + { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" }, + { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" }, + { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" }, + { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" }, + { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" }, + { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" }, + { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" }, + { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" }, + { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" }, + { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" }, + { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" }, + { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" }, + { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + +[[package]] +name = "fonttools" +version = "4.62.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/ff/532ed43808b469c807e8cb6b21358da3fe6fd51486b3a8c93db0bb5d957f/fonttools-4.62.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad5cca75776cd453b1b035b530e943334957ae152a36a88a320e779d61fc980c", size = 2873740, upload-time = "2026-03-13T13:52:11.822Z" }, + { url = "https://files.pythonhosted.org/packages/85/e4/2318d2b430562da7227010fb2bb029d2fa54d7b46443ae8942bab224e2a0/fonttools-4.62.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b3ae47e8636156a9accff64c02c0924cbebad62854c4a6dbdc110cd5b4b341a", size = 2417649, upload-time = "2026-03-13T13:52:14.605Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/40f15523b5188598018e7956899fed94eb7debec89e2dd70cb4a8df90492/fonttools-4.62.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b9e288b4da2f64fd6180644221749de651703e8d0c16bd4b719533a3a7d6e3", size = 4935213, upload-time = "2026-03-13T13:52:17.399Z" }, + { url = "https://files.pythonhosted.org/packages/42/09/7dbe3d7023f57d9b580cfa832109d521988112fd59dddfda3fddda8218f9/fonttools-4.62.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bca7a1c1faf235ffe25d4f2e555246b4750220b38de8261d94ebc5ce8a23c23", size = 4892374, upload-time = "2026-03-13T13:52:20.175Z" }, + { url = "https://files.pythonhosted.org/packages/d1/2d/84509a2e32cb925371560ef5431365d8da2183c11d98e5b4b8b4e42426a5/fonttools-4.62.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b4e0fcf265ad26e487c56cb12a42dffe7162de708762db951e1b3f755319507d", size = 4911856, upload-time = "2026-03-13T13:52:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/a5/80/df28131379eed93d9e6e6fccd3bf6e3d077bebbfe98cc83f21bbcd83ed02/fonttools-4.62.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d850f66830a27b0d498ee05adb13a3781637b1826982cd7e2b3789ef0cc71ae", size = 5031712, upload-time = "2026-03-13T13:52:25.14Z" }, + { url = "https://files.pythonhosted.org/packages/3d/03/3c8f09aad64230cd6d921ae7a19f9603c36f70930b00459f112706f6769a/fonttools-4.62.1-cp310-cp310-win32.whl", hash = "sha256:486f32c8047ccd05652aba17e4a8819a3a9d78570eb8a0e3b4503142947880ed", size = 1507878, upload-time = "2026-03-13T13:52:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ec/f53f626f8f3e89f4cadd8fc08f3452c8fd182c951ad5caa35efac22b29ab/fonttools-4.62.1-cp310-cp310-win_amd64.whl", hash = "sha256:5a648bde915fba9da05ae98856987ca91ba832949a9e2888b48c47ef8b96c5a9", size = 1556766, upload-time = "2026-03-13T13:52:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, +] + +[[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 = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/be/a30bd917018ad220c400169fba298f2bb7003c8ccbc0c3e24ae2aacad1e8/matplotlib-3.10.8-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:00270d217d6b20d14b584c521f810d60c5c78406dc289859776550df837dcda7", size = 8239828, upload-time = "2025-12-10T22:55:02.313Z" }, + { url = "https://files.pythonhosted.org/packages/58/27/ca01e043c4841078e82cf6e80a6993dfecd315c3d79f5f3153afbb8e1ec6/matplotlib-3.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37b3c1cc42aa184b3f738cfa18c1c1d72fd496d85467a6cf7b807936d39aa656", size = 8128050, upload-time = "2025-12-10T22:55:04.997Z" }, + { url = "https://files.pythonhosted.org/packages/cb/aa/7ab67f2b729ae6a91bcf9dcac0affb95fb8c56f7fd2b2af894ae0b0cf6fa/matplotlib-3.10.8-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee40c27c795bda6a5292e9cff9890189d32f7e3a0bf04e0e3c9430c4a00c37df", size = 8700452, upload-time = "2025-12-10T22:55:07.47Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/2d5817b0acee3c49b7e7ccfbf5b273f284957cc8e270adf36375db353190/matplotlib-3.10.8-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a48f2b74020919552ea25d222d5cc6af9ca3f4eb43a93e14d068457f545c2a17", size = 9534928, upload-time = "2025-12-10T22:55:10.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5b/8e66653e9f7c39cb2e5cab25fce4810daffa2bff02cbf5f3077cea9e942c/matplotlib-3.10.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f254d118d14a7f99d616271d6c3c27922c092dac11112670b157798b89bf4933", size = 9586377, upload-time = "2025-12-10T22:55:12.362Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/fd0bbadf837f81edb0d208ba8f8cb552874c3b16e27cb91a31977d90875d/matplotlib-3.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:f9b587c9c7274c1613a30afabf65a272114cd6cdbe67b3406f818c79d7ab2e2a", size = 8128127, upload-time = "2025-12-10T22:55:14.436Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" }, + { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" }, + { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" }, + { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" }, + { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" }, + { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" }, + { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" }, + { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/f5/43/31d59500bb950b0d188e149a2e552040528c13d6e3d6e84d0cccac593dcd/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f97aeb209c3d2511443f8797e3e5a569aebb040d4f8bc79aa3ee78a8fb9e3dd8", size = 8237252, upload-time = "2025-12-10T22:56:39.529Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2c/615c09984f3c5f907f51c886538ad785cf72e0e11a3225de2c0f9442aecc/matplotlib-3.10.8-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fb061f596dad3a0f52b60dc6a5dec4a0c300dec41e058a7efe09256188d170b7", size = 8124693, upload-time = "2025-12-10T22:56:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/e1/2757277a1c56041e1fc104b51a0f7b9a4afc8eb737865d63cababe30bc61/matplotlib-3.10.8-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:12d90df9183093fcd479f4172ac26b322b1248b15729cb57f42f71f24c7e37a3", size = 8702205, upload-time = "2025-12-10T22:56:43.415Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/c6/4218570d8c8ecc9704b5157a3348e486e84ef4be0ed3e38218ab473c83d2/numpy-2.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f983334aea213c99992053ede6168500e5f086ce74fbc4acc3f2b00f5762e9db", size = 16976799, upload-time = "2026-03-29T13:18:15.438Z" }, + { url = "https://files.pythonhosted.org/packages/dd/92/b4d922c4a5f5dab9ed44e6153908a5c665b71acf183a83b93b690996e39b/numpy-2.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:72944b19f2324114e9dc86a159787333b77874143efcf89a5167ef83cfee8af0", size = 14971552, upload-time = "2026-03-29T13:18:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/8a/dc/df98c095978fa6ee7b9a9387d1d58cbb3d232d0e69ad169a4ce784bde4fd/numpy-2.4.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:86b6f55f5a352b48d7fbfd2dbc3d5b780b2d79f4d3c121f33eb6efb22e9a2015", size = 5476566, upload-time = "2026-03-29T13:18:21.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/34/b3fdcec6e725409223dd27356bdf5a3c2cc2282e428218ecc9cb7acc9763/numpy-2.4.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:ba1f4fc670ed79f876f70082eff4f9583c15fb9a4b89d6188412de4d18ae2f40", size = 6806482, upload-time = "2026-03-29T13:18:23.634Z" }, + { url = "https://files.pythonhosted.org/packages/68/62/63417c13aa35d57bee1337c67446761dc25ea6543130cf868eace6e8157b/numpy-2.4.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a87ec22c87be071b6bdbd27920b129b94f2fc964358ce38f3822635a3e2e03d", size = 15973376, upload-time = "2026-03-29T13:18:26.677Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c5/9fcb7e0e69cef59cf10c746b84f7d58b08bc66a6b7d459783c5a4f6101a6/numpy-2.4.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df3775294accfdd75f32c74ae39fcba920c9a378a2fc18a12b6820aa8c1fb502", size = 16925137, upload-time = "2026-03-29T13:18:30.14Z" }, + { url = "https://files.pythonhosted.org/packages/7e/43/80020edacb3f84b9efdd1591120a4296462c23fd8db0dde1666f6ef66f13/numpy-2.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0d4e437e295f18ec29bc79daf55e8a47a9113df44d66f702f02a293d93a2d6dd", size = 17329414, upload-time = "2026-03-29T13:18:33.733Z" }, + { url = "https://files.pythonhosted.org/packages/fd/06/af0658593b18a5f73532d377188b964f239eb0894e664a6c12f484472f97/numpy-2.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa3236c78803afbcb255045fbef97a9e25a1f6c9888357d205ddc42f4d6eba5", size = 18658397, upload-time = "2026-03-29T13:18:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/13a09ed65f5d0ce5c7dd0669250374c6e379910f97af2c08c57b0608eee4/numpy-2.4.4-cp311-cp311-win32.whl", hash = "sha256:30caa73029a225b2d40d9fae193e008e24b2026b7ee1a867b7ee8d96ca1a448e", size = 6239499, upload-time = "2026-03-29T13:18:40.372Z" }, + { url = "https://files.pythonhosted.org/packages/bd/63/05d193dbb4b5eec1eca73822d80da98b511f8328ad4ae3ca4caf0f4db91d/numpy-2.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6bbe4eb67390b0a0265a2c25458f6b90a409d5d069f1041e6aff1e27e3d9a79e", size = 12614257, upload-time = "2026-03-29T13:18:42.95Z" }, + { url = "https://files.pythonhosted.org/packages/87/c5/8168052f080c26fa984c413305012be54741c9d0d74abd7fbeeccae3889f/numpy-2.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:fcfe2045fd2e8f3cb0ce9d4ba6dba6333b8fa05bb8a4939c908cd43322d14c7e", size = 10486775, upload-time = "2026-03-29T13:18:45.835Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933, upload-time = "2026-03-29T13:19:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532, upload-time = "2026-03-29T13:19:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661, upload-time = "2026-03-29T13:19:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539, upload-time = "2026-03-29T13:19:30.97Z" }, + { url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806, upload-time = "2026-03-29T13:19:33.887Z" }, + { url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682, upload-time = "2026-03-29T13:19:37.336Z" }, + { url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810, upload-time = "2026-03-29T13:19:40.963Z" }, + { url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394, upload-time = "2026-03-29T13:19:44.859Z" }, + { url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556, upload-time = "2026-03-29T13:19:47.661Z" }, + { url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311, upload-time = "2026-03-29T13:19:50.67Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060, upload-time = "2026-03-29T13:19:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302, upload-time = "2026-03-29T13:19:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407, upload-time = "2026-03-29T13:20:00.601Z" }, + { url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631, upload-time = "2026-03-29T13:20:02.855Z" }, + { url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691, upload-time = "2026-03-29T13:20:06.004Z" }, + { url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241, upload-time = "2026-03-29T13:20:09.417Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767, upload-time = "2026-03-29T13:20:13.126Z" }, + { url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169, upload-time = "2026-03-29T13:20:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477, upload-time = "2026-03-29T13:20:20.195Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487, upload-time = "2026-03-29T13:20:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002, upload-time = "2026-03-29T13:20:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353, upload-time = "2026-03-29T13:20:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914, upload-time = "2026-03-29T13:20:33.547Z" }, + { url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005, upload-time = "2026-03-29T13:20:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974, upload-time = "2026-03-29T13:20:39.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591, upload-time = "2026-03-29T13:20:42.146Z" }, + { url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700, upload-time = "2026-03-29T13:20:46.204Z" }, + { url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781, upload-time = "2026-03-29T13:20:50.242Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959, upload-time = "2026-03-29T13:20:54.019Z" }, + { url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768, upload-time = "2026-03-29T13:20:56.912Z" }, + { url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181, upload-time = "2026-03-29T13:20:59.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035, upload-time = "2026-03-29T13:21:02.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958, upload-time = "2026-03-29T13:21:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020, upload-time = "2026-03-29T13:21:08.635Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758, upload-time = "2026-03-29T13:21:10.949Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948, upload-time = "2026-03-29T13:21:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325, upload-time = "2026-03-29T13:21:17.561Z" }, + { url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883, upload-time = "2026-03-29T13:21:21.106Z" }, + { url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474, upload-time = "2026-03-29T13:21:24.828Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500, upload-time = "2026-03-29T13:21:28.205Z" }, + { url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755, upload-time = "2026-03-29T13:21:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643, upload-time = "2026-03-29T13:21:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/6b/33/8fae8f964a4f63ed528264ddf25d2b683d0b663e3cba26961eb838a7c1bd/numpy-2.4.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:58c8b5929fcb8287cbd6f0a3fae19c6e03a5c48402ae792962ac465224a629a4", size = 16854491, upload-time = "2026-03-29T13:21:38.03Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d0/1aabee441380b981cf8cdda3ae7a46aa827d1b5a8cce84d14598bc94d6d9/numpy-2.4.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eea7ac5d2dce4189771cedb559c738a71512768210dc4e4753b107a2048b3d0e", size = 14895830, upload-time = "2026-03-29T13:21:41.509Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b8/aafb0d1065416894fccf4df6b49ef22b8db045187949545bced89c034b8e/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:51fc224f7ca4d92656d5a5eb315f12eb5fe2c97a66249aa7b5f562528a3be38c", size = 5400927, upload-time = "2026-03-29T13:21:44.747Z" }, + { url = "https://files.pythonhosted.org/packages/d6/77/063baa20b08b431038c7f9ff5435540c7b7265c78cf56012a483019ca72d/numpy-2.4.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:28a650663f7314afc3e6ec620f44f333c386aad9f6fc472030865dc0ebb26ee3", size = 6715557, upload-time = "2026-03-29T13:21:47.406Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a8/379542d45a14f149444c5c4c4e7714707239ce9cc1de8c2803958889da14/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19710a9ca9992d7174e9c52f643d4272dcd1558c5f7af7f6f8190f633bd651a7", size = 15804253, upload-time = "2026-03-29T13:21:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/f0a45426d6d21e7ea3310a15cf90c43a14d9232c31a837702dba437f3373/numpy-2.4.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b2aec6af35c113b05695ebb5749a787acd63cafc83086a05771d1e1cd1e555f", size = 16753552, upload-time = "2026-03-29T13:21:54.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/74/f4c001f4714c3ad9ce037e18cf2b9c64871a84951eaa0baf683a9ca9301c/numpy-2.4.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f2cf083b324a467e1ab358c105f6cad5ea950f50524668a80c486ff1db24e119", size = 12509075, upload-time = "2026-03-29T13:21:57.644Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pillow" +version = "12.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" }, + { url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" }, + { url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, +] + +[[package]] +name = "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 = "pymdown-extensions" +version = "10.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[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 = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, + { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +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 = "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 = "soerp" +source = { editable = "." } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] + +[package.optional-dependencies] +all = [ + { name = "matplotlib" }, +] +plot = [ + { name = "matplotlib" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, + { name = "ruff" }, + { name = "zensical" }, +] +docs = [ + { name = "zensical" }, +] +lint = [ + { name = "ruff" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-cov" }, + { name = "pytest-xdist" }, +] + +[package.metadata] +requires-dist = [ + { name = "matplotlib", marker = "extra == 'all'" }, + { name = "matplotlib", marker = "extra == 'plot'" }, + { name = "numpy" }, + { name = "scipy" }, +] +provides-extras = ["all", "plot"] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-xdist", specifier = ">=3.6.1" }, + { name = "ruff", specifier = "==0.15.*" }, + { name = "zensical", specifier = ">=0.0.23" }, +] +docs = [{ name = "zensical", specifier = ">=0.0.23" }] +lint = [{ name = "ruff", specifier = "==0.15.*" }] +test = [ + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-xdist", specifier = ">=3.6.1" }, +] + +[[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 = "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 = "zensical" +version = "0.0.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/c2/dea4b86dc1ca2a7b55414017f12cfb12b5cfdf3a1ed7c77a04c271eb523b/zensical-0.0.33.tar.gz", hash = "sha256:05209cb4f80185c533e0d37c25d084ddc2050e3d5a4dd1b1812961c2ee0c3380", size = 3892278, upload-time = "2026-04-14T11:08:19.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/5f/45d5200405420a9d8ac91cf9e7826622ea12f3198e8e6ac4ffb481eb53bf/zensical-0.0.33-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f658e3c241cfbb560bd8811116a9486cff7e04d7d5aed73569dd533c74187450", size = 12416748, upload-time = "2026-04-14T11:07:43.246Z" }, + { url = "https://files.pythonhosted.org/packages/33/1e/aadaf31d6e4d20419ecedaf0b1c804e359ec23dcdb44c8d2bf6d8407080c/zensical-0.0.33-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f9813ac3256c28e2e2f1ba5c9fab1b4bca62bbe0e0f8e85ac22d33b068b1b08a", size = 12293372, upload-time = "2026-04-14T11:07:46.569Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/838be8451ea8b2aecec39fbec3971060fc705e17f5741249740d9b6a6824/zensical-0.0.33-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bad7ac71028769c5d1f3f84f448dbb7352db28d77095d1b40a8d1b0aa34ec30", size = 12659832, upload-time = "2026-04-14T11:07:50.754Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5c/dd957d7c83efc13a70a6058d4190a3afcf29942aefb391120bca5466347d/zensical-0.0.33-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:06bb039daf044547c9400a52f9493b3cd486ba9baef3324fdcffd2e26e61105f", size = 12603847, upload-time = "2026-04-14T11:07:53.698Z" }, + { url = "https://files.pythonhosted.org/packages/b7/99/dd6ccc392ece1f34fb20ea339a01717badbbeb2fba1d4f3019a5028d0bcc/zensical-0.0.33-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:260238062b3139ece0edab93f4dbe7a12923453091f5aa580dfd73e799388076", size = 12956236, upload-time = "2026-04-14T11:07:56.728Z" }, + { url = "https://files.pythonhosted.org/packages/f4/76/e0a1b884eadf6afa7e2d56c90c268eec36836ac27e96ef250c0129e55417/zensical-0.0.33-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dff0f4afda7b8586bc4ab2a5684bce5b282232dd4e0cad3be4c73fedd264425", size = 12701944, upload-time = "2026-04-14T11:07:59.928Z" }, + { url = "https://files.pythonhosted.org/packages/38/38/e1ff13461e406864fa2b23fc828822659a7dbac5c79398f724d17f088540/zensical-0.0.33-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:207b4d81b208d75b97dc7bd318804550b886a3e852ef67429ef0e6b9442839d1", size = 12835444, upload-time = "2026-04-14T11:08:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/41/04/7d24d52d6903fc5c511633afe8b5716fef19da09685327665cc127f61648/zensical-0.0.33-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:06d2f57f7bc8cc8fd904386020ea1365eebc411e8698a871e9525c885abca574", size = 12878419, upload-time = "2026-04-14T11:08:06.054Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ec/87fc9e360c694ab006363c7834639eccafd0d26a487cd63dd609bd68f36a/zensical-0.0.33-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c2851b82d83aa0b2ae4f8e99731cfeedeecebfa04e6b3fc4d375deca629fa240", size = 13022474, upload-time = "2026-04-14T11:08:09.007Z" }, + { url = "https://files.pythonhosted.org/packages/10/b3/0bf174ab6ceedb31d9af462073b5339c894b2084a27d42cb9f0906050d76/zensical-0.0.33-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:90daaf512b0429d7b9147ad5e6085b455d24803eff18b508aed738ca65444683", size = 12975233, upload-time = "2026-04-14T11:08:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a9/27/7cc3c2d284698647f60f3b823e0101e619c87edf158d47ee11bf4bfb6228/zensical-0.0.33-cp310-abi3-win32.whl", hash = "sha256:2701820597fe19361a12371129927c58c19633dcaa5f6986d610dce58cecd8c4", size = 12012664, upload-time = "2026-04-14T11:08:14.977Z" }, + { url = "https://files.pythonhosted.org/packages/25/0b/6be5c2fdaf9f1600577e7ba5e235d86b72a26f6af389efb146f978f76ac3/zensical-0.0.33-cp310-abi3-win_amd64.whl", hash = "sha256:a5a0911b4247708a55951b74c459f4d5faec5daaf287d23a2e1f0d96be1e647f", size = 12206255, upload-time = "2026-04-14T11:08:17.375Z" }, +]