diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1f752ba..a8dc72d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,11 +1,9 @@ version: 2 + updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: weekly - open-pull-requests-limit: 1 - assignees: - - JoshKarpel - labels: - - dependencies + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "thursday" + open-pull-requests-limit: 1 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..1002f7c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ + + +Tasks +----- + +- [ ] Updated changelog. +- [ ] Updated documentation. diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 0000000..20eeab0 --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,23 @@ +name: publish-docs + +on: + push: + branches: + - main + +jobs: + publish-docs: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3.3.0 + - name: Set up Python 3.x + uses: actions/setup-python@v4.5.0 + with: + python-version: "3.x" + - name: Install Poetry + uses: snok/install-poetry@v1.3.3 + - name: Install Package + run: poetry install + - name: Build and deploy docs + run: poetry run mkdocs gh-deploy --clean --strict --verbose --force diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml new file mode 100644 index 0000000..b47e437 --- /dev/null +++ b/.github/workflows/publish-package.yml @@ -0,0 +1,23 @@ +name: publish-package + +on: + release: + types: [published] + +jobs: + pypi: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v3.3.0 + - name: Set up Python 3.x + uses: actions/setup-python@v4.5.0 + with: + python-version: "3.x" + - name: Install Poetry + uses: snok/install-poetry@v1.3.3 + - name: Build the package + run: poetry build -vvv + - name: Publish to PyPI + run: poetry publish --username __token__ --password ${{ secrets.pypi_token }} + working-directory: ${{ github.workspace }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 90da07d..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: publish - -on: - release: - types: [published] - -jobs: - build-and-publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python 3.x - uses: actions/setup-python@v2 - with: - python-version: "3.x" - - name: Install build dependencies - run: pip install wheel - - name: Build packages - run: python setup.py sdist bdist_wheel - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@v1.3.1 - with: - user: __token__ - password: ${{ secrets.pypi_token }} diff --git a/.github/workflows/quality-check.yml b/.github/workflows/quality-check.yml new file mode 100644 index 0000000..2d9b367 --- /dev/null +++ b/.github/workflows/quality-check.yml @@ -0,0 +1,50 @@ +name: quality-check + +on: + push: + branches: + - main + pull_request: + +jobs: + test-code: + strategy: + fail-fast: false + matrix: + platform: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.8", "3.9", "3.10", "3.11"] + defaults: + run: + shell: bash + runs-on: ${{ matrix.platform }} + timeout-minutes: 15 + env: + PLATFORM: ${{ matrix.platform }} + PYTHON_VERSION: ${{ matrix.python-version }} + PIP_DISABLE_PIP_VERSION_CHECK: 1 + steps: + - name: Check out repository + uses: actions/checkout@v3.3.0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4.5.0 + with: + python-version: ${{ matrix.python-version }} + - name: Install Poetry + uses: snok/install-poetry@v1.3.3 + - name: Install Package + run: poetry install + - name: Run pre-commit checks + run: poetry run pre-commit run --all-files --show-diff-on-failure --color=always + - name: Make sure we can build the package + run: poetry build -vvv + - name: Test types + run: poetry run mypy + - name: Test code + run: poetry run pytest -v --cov --cov-report=xml --durations=20 + - name: Test docs + run: poetry run mkdocs build --clean --strict --verbose + - name: Upload coverage + uses: codecov/codecov-action@v3.1.1 + with: + env_vars: PLATFORM,PYTHON_VERSION + fail_ci_if_error: false diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index d2f962c..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: tests - -on: - push: - branches: - - master - pull_request: - -jobs: - test: - strategy: - fail-fast: false - matrix: - platform: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] - - runs-on: ${{ matrix.platform }} - - env: - PLATFORM: ${{ matrix.platform }} - PYTHON_VERSION: ${{ matrix.python-version }} - - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install --upgrade pip - pip install .[tests] - - name: Test with pytest - run: pytest --cov --cov-report=xml tests/ - - uses: codecov/codecov-action@v1 - with: - env_vars: PLATFORM,PYTHON_VERSION - fail_ci_if_error: true - verbose: true diff --git a/.gitignore b/.gitignore index cdc4fd8..d4a2261 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,7 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +.mypy_cache/ # Translations *.mo @@ -62,9 +63,6 @@ instance/ # Scrapy stuff: .scrapy -# Sphinx documentation -#docs/_build/ - # PyBuilder target/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e3e1e8a..4d8bdae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-ast @@ -11,41 +11,40 @@ repos: - id: check-docstring-first - id: check-merge-conflict - id: check-toml - - id: check-yaml + - id: check-json - id: debug-statements - id: end-of-file-fixer - id: forbid-new-submodules - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 + rev: v1.10.0 hooks: - id: python-check-mock-methods - id: python-no-eval - id: python-no-log-warn - id: python-use-type-annotations - - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.3.1 + - id: python-check-blanket-type-ignore + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.22.0 + hooks: + - id: check-github-workflows + - id: check-github-actions + - id: check-dependabot + - repo: https://github.com/hadialqattan/pycln + rev: v2.1.3 + hooks: + - id: pycln + args: [--config=pyproject.toml] + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 hooks: - - id: remove-crlf + - id: isort - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.1.0 hooks: - id: black - - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.13.0 hooks: - id: blacken-docs - additional_dependencies: [ black==20.8b1 ] - - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.2.0 - hooks: - - id: setup-cfg-fmt - - repo: https://github.com/asottile/seed-isort-config - rev: v2.2.0 - hooks: - - id: seed-isort-config - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 974d07a..19e8a1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,13 +3,13 @@ ## Reporting Issues Report issues via the GitHub issue tracker. -Be as specific as possible about the problem you're encountering, and include all relevant details (a [minimal working example](https://en.wikipedia.org/wiki/Minimal_Working_Example), the full traceback of the error or description of how the output and the desired output differ, Python version, IDESolver version, etc.) +Be as specific as possible about the problem you're encountering, and include all relevant details +(a [minimal working example](https://en.wikipedia.org/wiki/Minimal_Working_Example), +the full traceback of the error or description of how the output and the desired output differ, +Python version, IDESolver version, etc.) ## Contributing Code We are generally open to GitHub pull requests. However, IDESolver is built to solve a very specific problem. We won't accept pull requests that expand the project beyond the core idea: solving IDEs numerically via iterative relaxation algorithms. - -* Follow [pep8](https://www.python.org/dev/peps/pep-0008/) when possible. -* Extensions to functionality should generally be presented as optional additions/replacements to existing options. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a675629 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +idesolver +--------- + +[![DOI](https://joss.theoj.org/papers/10.21105/joss.00542/status.svg)](https://doi.org/10.21105/joss.00542) + +[![PyPI](https://img.shields.io/pypi/v/idesolver)](https://pypi.org/project/idesolver) +[![PyPI - License](https://img.shields.io/pypi/l/idesolver)](https://pypi.org/project/idesolver) +[![Docs](https://img.shields.io/badge/docs-exist-brightgreen)](https://www.idesolver.how) + +[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/JoshKarpel/idesolver/main.svg)](https://results.pre-commit.ci/latest/github/JoshKarpel/idesolver/main) +[![codecov](https://codecov.io/gh/JoshKarpel/idesolver/branch/main/graph/badge.svg?token=2sjP4V0AfY)](https://codecov.io/gh/JoshKarpel/idesolver) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) + +[![GitHub issues](https://img.shields.io/github/issues/JoshKarpel/idesolver)](https://github.com/JoshKarpel/idesolver/issues) +[![GitHub pull requests](https://img.shields.io/github/issues-pr/JoshKarpel/idesolver)](https://github.com/JoshKarpel/idesolver/pulls) + +A general purpose numeric integro-differential equation (IDE) solver, based on an iterative scheme devised by [Gelmi and Jorquera](https://doi.org/10.1016/j.cpc.2013.09.008). +IDEs appear in many contexts, particularly when trying to describe a system whose current behavior depends on its own history. +IDESolver provides a simple interface for solving these kinds of equations in Python. + +Stable releases are available on PyPI: `pip install idesolver`. + +Full documentation can be found [here](https://idesolver.readthedocs.io/en/latest/). +If you use `idesolver` in your research, please consider [citing the associated paper](https://joss.theoj.org/papers/10.21105/joss.00542>). + +Problems with IDESolver should be reported via [GitHub issues](https://github.com/JoshKarpel/idesolver/issues). +We are open to improvements: see the [Code of Conduct](https://github.com/JoshKarpel/idesolver/blob/master/CODE_OF_CONDUCT.md) +and the [Contribution Guidelines](https://github.com/JoshKarpel/idesolver/blob/master/CONTRIBUTING.md) for details. diff --git a/README.rst b/README.rst deleted file mode 100644 index e6d7a81..0000000 --- a/README.rst +++ /dev/null @@ -1,33 +0,0 @@ -idesolver ---------- - -.. image:: http://joss.theoj.org/papers/9d3ba306da6abb37f7cf357cd9aad695/status.svg - :target: http://joss.theoj.org/papers/9d3ba306da6abb37f7cf357cd9aad695 - -.. image:: https://readthedocs.org/projects/idesolver/badge/?version=latest - :target: https://idesolver.readthedocs.io/en/latest/?badge=latest - -.. image:: https://img.shields.io/pypi/v/idesolver - :alt: PyPI - -.. image:: https://codecov.io/gh/JoshKarpel/idesolver/branch/master/graph/badge.svg - :target: https://codecov.io/gh/JoshKarpel/idesolver - -.. image:: https://results.pre-commit.ci/badge/github/JoshKarpel/idesolver/master.svg - :target: https://results.pre-commit.ci/latest/github/JoshKarpel/idesolver/master - :alt: pre-commit.ci status - -A general purpose numeric integro-differential equation (IDE) solver, based on an iterative scheme devised by `Gelmi and Jorquera `_. -IDEs appear in many contexts, particularly when trying to describe a system whose current behavior depends on its own history. -IDESolver provides a simple interface for solving these kinds of equations in Python. - -Stable releases are available on PyPI: ``pip install idesolver``. -IDESolver requires Python 3.6+, `numpy `_, and `scipy `_. -We recommend installing into a `virtual environment `_. - -Full documentation can be found `here `_. -If you use ``idesolver`` in your research, please consider `citing the associated paper `_. - -Details about running the test suite are at the end of the `manual `_. -Problems with IDESolver should be reported via `GitHub issues `_. -We are open to improvements: see the `Code of Conduct `_ and the `Contribution Guidelines `_ for details. diff --git a/dev/Gelmi2014.py b/dev/Gelmi2014.py index 8e3e98e..a51fd46 100644 --- a/dev/Gelmi2014.py +++ b/dev/Gelmi2014.py @@ -1,14 +1,16 @@ import os +from typing import Tuple import matplotlib.pyplot as plt -import numpy as np +from numpy import abs, cos, exp, float_, linspace, log, pi, sin, sqrt +from numpy.typing import NDArray from idesolver import IDESolver OUT_DIR = __file__.strip(".py") -def make_comparison_plot(name, solver, exact): +def make_comparison_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure(dpi=600) ax = fig.add_subplot(111) @@ -31,11 +33,11 @@ def make_comparison_plot(name, solver, exact): ) -def make_error_plot(name, solver, exact): +def make_error_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure(dpi=600) ax = fig.add_subplot(111) - error = np.abs(solver.y - exact) + error = abs(solver.y - exact) ax.plot(solver.x, error) ax.set_yscale("log") @@ -50,46 +52,43 @@ def make_error_plot(name, solver, exact): ) -def example_1(): +def example_1() -> Tuple[IDESolver, NDArray[float_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, + c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - log(1 + x), + d=lambda x: 1 / (log(2)) ** 2, k=lambda x, s: x / (1 + s), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, ) solver.solve() - exact = np.log(1 + solver.x) + exact = log(1 + solver.x) return solver, exact -def example_2(): +def example_2() -> Tuple[IDESolver, NDArray[float_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=1, - c=lambda x, y: y - - np.cos(2 * np.pi * x) - - (2 * np.pi * np.sin(2 * np.pi * x)) - - (0.5 * np.sin(4 * np.pi * x)), + c=lambda x, y: y - cos(2 * pi * x) - (2 * pi * sin(2 * pi * x)) - (0.5 * sin(4 * pi * x)), d=lambda x: 1, - k=lambda x, s: np.sin(2 * np.pi * ((2 * x) + s)), + k=lambda x, s: sin(2 * pi * ((2 * x) + s)), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, ) solver.solve() - exact = np.cos(2 * np.pi * solver.x) + exact = cos(2 * pi * solver.x) return solver, exact -def example_3(): +def example_3() -> Tuple[IDESolver, NDArray[float_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=1, c=lambda x, y: 1 - (29 / 60) * x, d=lambda x: 1, @@ -104,20 +103,19 @@ def example_3(): return solver, exact -def example_4(): +def example_4() -> Tuple[IDESolver, NDArray[float_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=1, - c=lambda x, y: (x * (1 + np.sqrt(x)) * np.exp(-np.sqrt(x))) - - (((x**2) + x + 1) * np.exp(-x)), + c=lambda x, y: (x * (1 + sqrt(x)) * exp(-sqrt(x))) - (((x**2) + x + 1) * exp(-x)), d=lambda x: 1, k=lambda x, s: x * s, lower_bound=lambda x: x, - upper_bound=lambda x: np.sqrt(x), + upper_bound=lambda x: sqrt(x), f=lambda y: y, ) solver.solve() - exact = np.exp(-solver.x) + exact = exp(-solver.x) return solver, exact @@ -139,5 +137,5 @@ def example_4(): f"Example {name} took {solver.iteration} iterations to get to global error {solver.global_error}. Error compared to analytic solution is {solver._global_error(solver.y, exact)}" ) - make_comparison_plot(name, solver, exact) - make_error_plot(name, solver, exact) + make_comparison_plot(str(name), solver, exact) + make_error_plot(str(name), solver, exact) diff --git a/dev/complex_valued.py b/dev/complex_valued.py index 5ad781e..6f1267c 100644 --- a/dev/complex_valued.py +++ b/dev/complex_valued.py @@ -1,14 +1,16 @@ import os +from typing import Tuple import matplotlib.pyplot as plt -import numpy as np +from numpy import abs, complex_, exp, imag, linspace, real, sinh, sqrt +from numpy.typing import NDArray from idesolver import IDESolver OUT_DIR = os.path.join(os.getcwd(), "out", __file__.strip(".py")) -def make_comparison_plot(name, solver, exact): +def make_comparison_plot(name: str, solver: IDESolver, exact: NDArray[complex_]) -> None: fig = plt.figure() ax = fig.add_subplot(111) @@ -17,8 +19,8 @@ def make_comparison_plot(name, solver, exact): colors = ["C0", "C1"] for y, label, color in zip(lines, labels, colors): - ax.plot(solver.x, np.real(y), label=r"R " + label, color=color, linestyle="-") - ax.plot(solver.x, np.imag(y), label=r"I " + label, color=color, linestyle="--") + ax.plot(solver.x, real(y), label=r"R " + label, color=color, linestyle="-") + ax.plot(solver.x, imag(y), label=r"I " + label, color=color, linestyle="--") ax.legend(loc="best") ax.grid(True) @@ -26,11 +28,11 @@ def make_comparison_plot(name, solver, exact): plt.savefig(os.path.join(OUT_DIR, f"ex{name}_comparison")) -def make_error_plot(name, solver, exact): +def make_error_plot(name: str, solver: IDESolver, exact: NDArray[complex_]) -> None: fig = plt.figure() ax = fig.add_subplot(111) - error = np.abs(solver.y - exact) + error = abs(solver.y - exact) ax.plot(solver.x, error) ax.set_yscale("log") @@ -39,9 +41,9 @@ def make_error_plot(name, solver, exact): plt.savefig(os.path.join(OUT_DIR, f"ex{name}_error")) -def example_1(): +def example_1() -> Tuple[IDESolver, NDArray[complex_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=0j, c=lambda x, y: (5 * y) + 1, d=lambda x: -3j, @@ -51,12 +53,7 @@ def example_1(): f=lambda y: y, ) solver.solve() - exact = ( - 2 - * np.exp(5 * solver.x / 2) - * np.sinh(0.5 * np.sqrt(25 - 12j) * solver.x) - / np.sqrt(25 - 12j) - ) + exact = 2 * exp(5 * solver.x / 2) * sinh(0.5 * sqrt(25 - 12j) * solver.x) / sqrt(25 - 12j) # for s, e in zip(solver.y, exact): # print(s, e) diff --git a/dev/convergence.py b/dev/convergence.py index 9275ddd..951fc5f 100644 --- a/dev/convergence.py +++ b/dev/convergence.py @@ -1,28 +1,30 @@ import os +from typing import Tuple import matplotlib.pyplot as plt -import numpy as np +from numpy import abs, cos, float_, imag, linspace, pi, real, sin +from numpy.typing import NDArray from idesolver import IDESolver OUT_DIR = os.path.join(os.getcwd(), "out") -def make_comparison_plot(name, solver, exact): +def make_comparison_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure() ax = fig.add_subplot(111) for iteration, y in solver.y_intermediate.items(): ax.plot( solver.x, - np.real(y), + real(y), linestyle="-", color="black", alpha=0.5 + iteration / solver.iteration, ) ax.plot( solver.x, - np.imag(y), + imag(y), linestyle="--", color="black", alpha=0.5 + iteration / solver.iteration, @@ -31,8 +33,8 @@ def make_comparison_plot(name, solver, exact): ax.plot(solver.x, solver._initial_y(), linestyle="-", color="C1") ax.plot(solver.x, solver._initial_y(), linestyle="--", color="C1") - ax.plot(solver.x, np.real(exact), linestyle="-", color="C0") - ax.plot(solver.x, np.imag(exact), linestyle="--", color="C0") + ax.plot(solver.x, real(exact), linestyle="-", color="C0") + ax.plot(solver.x, imag(exact), linestyle="--", color="C0") ax.legend(loc="best") ax.grid(True) @@ -40,11 +42,11 @@ def make_comparison_plot(name, solver, exact): plt.savefig(os.path.join(OUT_DIR, f"ex_{name}_comparison")) -def make_error_plot(name, solver, exact): +def make_error_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure() ax = fig.add_subplot(111) - error = np.abs(solver.y - exact) + error = abs(solver.y - exact) ax.plot(solver.x, error) ax.set_yscale("log") @@ -53,22 +55,19 @@ def make_error_plot(name, solver, exact): plt.savefig(os.path.join(OUT_DIR, f"ex_{name}_error")) -def example_1(): +def example_1() -> Tuple[IDESolver, NDArray[float_]]: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=1, - c=lambda x, y: y - - np.cos(2 * np.pi * x) - - (2 * np.pi * np.sin(2 * np.pi * x)) - - (0.5 * np.sin(4 * np.pi * x)), + c=lambda x, y: y - cos(2 * pi * x) - (2 * pi * sin(2 * pi * x)) - (0.5 * sin(4 * pi * x)), d=lambda x: 1, - k=lambda x, s: np.sin(2 * np.pi * ((2 * x) + s)), + k=lambda x, s: sin(2 * pi * ((2 * x) + s)), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, ) solver.solve() - exact = np.cos(2 * np.pi * solver.x) + exact = cos(2 * pi * solver.x) return solver, exact diff --git a/dev/parallel.py b/dev/parallel.py index ea09061..e0c30f2 100644 --- a/dev/parallel.py +++ b/dev/parallel.py @@ -1,6 +1,7 @@ import multiprocessing -import numpy as np +from numpy import float_, linspace, log +from numpy.typing import NDArray from idesolver import IDESolver @@ -11,27 +12,27 @@ def run(solver): return solver -def c(x, y): - return y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x) +def c(x: NDArray[float_], y: NDArray[float_]) -> NDArray[float_]: + return y - (0.5 * x) + (1 / (1 + x)) - log(1 + x) -def d(x): - return 1 / (np.log(2)) ** 2 +def d(x: NDArray[float_]) -> NDArray[float_]: + return 1 / (log(2)) ** 2 -def k(x, s): +def k(x: NDArray[float_], s: float) -> NDArray[float_]: return x / (1 + s) -def lower_bound(x): +def lower_bound(x: NDArray[float_]) -> float: return 0 -def upper_bound(x): +def upper_bound(x: NDArray[float_]) -> float: return 1 -def f(y): +def f(y: NDArray[float_]) -> NDArray[float_]: return y @@ -39,7 +40,7 @@ def f(y): # create 20 IDESolvers ides = [ IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=0, c=c, d=d, @@ -48,7 +49,7 @@ def f(y): upper_bound=upper_bound, f=f, ) - for y_0 in np.linspace(0, 1, 20) + for y_0 in linspace(0, 1, 20) ] with multiprocessing.Pool(processes=2) as pool: diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 7b4ea44..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = python -msphinx -SPHINXPROJ = idesolver -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -E -a diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..101c02b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,7 @@ +# API Reference + +::: idesolver.solve_ide + +::: idesolver.global_error + +::: idesolver.complex_quad diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000..297c344 Binary files /dev/null and b/docs/assets/favicon.png differ diff --git a/docs/assets/mathjax.js b/docs/assets/mathjax.js new file mode 100644 index 0000000..06dbf38 --- /dev/null +++ b/docs/assets/mathjax.js @@ -0,0 +1,16 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } +}; + +document$.subscribe(() => { + MathJax.typesetPromise() +}) diff --git a/docs/assets/quickstart_comparison.png b/docs/assets/quickstart_comparison.png new file mode 100644 index 0000000..5ddbbaa Binary files /dev/null and b/docs/assets/quickstart_comparison.png differ diff --git a/docs/assets/quickstart_error.png b/docs/assets/quickstart_error.png new file mode 100644 index 0000000..3264a60 Binary files /dev/null and b/docs/assets/quickstart_error.png differ diff --git a/docs/assets/style.css b/docs/assets/style.css new file mode 100644 index 0000000..3a22901 --- /dev/null +++ b/docs/assets/style.css @@ -0,0 +1,66 @@ +:root { + --class-color: #00b8d4; + --class-header-color: #00b8d41a; + --function-color: #448aff; + --function-header-color: #448aff1a; +} + +article > .doc { + border-style: solid; + border-width: 0.05rem; + border-radius: 0.2rem; + padding: 0.6rem 0.6rem; + box-shadow: var(--md-shadow-z1); +} + +article > .doc + .doc { + margin-top: 1rem; +} + +h3.doc { + margin: -0.6rem; + padding: 0.6rem; +} + +article > .doc.doc-class { + border-color: var(--class-color); +} + +.doc-class > h3.doc { + background-color: var(--class-header-color); +} + +article > .doc.doc-function { + border-color: var(--function-color); +} + +.doc-function > h3.doc { + background-color: var(--function-header-color); +} + +/* Indentation. */ +div.doc-contents:not(.first) { + padding-left: 25px; + border-left: .05rem solid var(--md-typeset-table-color); +} + +/* Mark external links as such. */ +a.autorefs-external::after { + /* https://primer.style/octicons/arrow-up-right-24 */ + background-image: url('data:image/svg+xml,'); + content: ' '; + + display: inline-block; + position: relative; + top: 0.1em; + margin-left: 0.2em; + margin-right: 0.1em; + + height: 1em; + width: 1em; + border-radius: 100%; + background-color: var(--md-typeset-a-color); +} +a.autorefs-external:hover::after { + background-color: var(--md-accent-fg-color); +} diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..f302930 --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,51 @@ +# Changelog + +## `1.1.0` + +Released `2022-11-19` + +### Added + +- [#35](https://github.com/JoshKarpel/idesolver/pull/35](https://github.com/nbrucy)) Add support for multidimensional IDEs, by [@nbrucy](https://github.com/nbrucy). + +## `1.0.5` + +Released `2020-09-15` + +### Changed + +- Relaxed dependency version restrictions in advance of changes to `pip`. + There shouldn't be any impact on users. + +## `1.0.4` + +Released `2019-10-24` + +### Changed + +- Revision of packaging and CI flow. There shouldn't be any impact on users. + +## `1.0.3` + +Released `2022-02-27` + +### Changed + +- Revision of package structure and CI flow. There shouldn't be any impact on users. + +## `1.0.2` + +Released `2018-01-30` + +### Changed + +- IDESolver now explicitly requires Python 3.6+. Dependencies on `numpy` and `scipy` are given as lower bounds. + +## `1.0.1` + +Released `2018-01-14` + +### Changed + +- Changed the name of `IDESolver.F` to `IDESolver.f`, as intended. +- The default global error function is now injected instead of hard-coded. diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..0daf39a --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,41 @@ +# Contributing Guide + +!!! info "IDESolver is open to contributions!" + + - [Report bugs and request features](https://github.com/JoshKarpel/idesolver/issues) + - [General discussion](https://github.com/JoshKarpel/idesolver/discussions) + - [Pull requests](https://github.com/JoshKarpel/idesolver/pulls) + +## Development Environment + +IDESolver uses: + +- [`poetry`](https://python-poetry.org) to manage development dependencies. +- [`pre-commit`](https://pre-commit.com) to run various linters and formatters. +- [`pytest`](https://docs.pytest.org) for testing and [`mypy`](https://mypy-lang.org) for static type-checking. +- [`mkdocs`](https://www.mkdocs.org) with the [Material theme](https://squidfunk.github.io/mkdocs-material) for documentation. + +### Initial Setup + +To set up a local development environment after cloning the repository: + +1. [Install `poetry`](https://python-poetry.org/docs/#installation). +2. Run `poetry shell` to create a virtual environment for `idesolver` and spawn a new shell session with that virtual environment activated. + In the future you'll run `poetry shell` again to activate the virtual environment. +3. Run `poetry install` to install IDESolver's dependencies. +4. Run `pre-commit install` to configure `pre-commit`'s integration with `git`. + Do not commit without `pre-commit` installed! + +### Running Tests and Type-Checking + +Run `pytest` to run tests. + +Run `mypy` to check types. + +### Building the Docs Locally + +To build the docs and start a local web server to view the results of your edits with live reloading, run +```bash +mkdocs serve +``` +from the repository root. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..4b897e8 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,22 @@ +# IDESolver + +IDESolver is a package that provides an interface for solving +real- or complex-valued integro-differential equations (IDEs) of the form + +$$ +\begin{aligned} + \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, f( y(s) ) \, ds, \\ + & x \in [a, b], \quad y(a) = y_0. +\end{aligned} +$$ + +[Integro-differential equations](https://en.wikipedia.org/wiki/Integro-differential_equation) appear in many contexts, +particularly when trying to describe a system whose future behavior depends on its own history and not just its present state. +The IDESolver is an iterative solver, +which means it generates successive approximations to the exact solution, +using each approximation to generate the next (hopefully better) one. +The algorithm is based on a scheme devised by +[Gelmi and Jorquera](https://doi.org/10.1016/j.cpc.2013.09.008>). + +If you use IDESolver in your work, +please consider [citing it](https://doi.org/10.21105/joss.00542) [![DOI](https://joss.theoj.org/papers/10.21105/joss.00542/status.svg)](https://doi.org/10.21105/joss.00542). diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index 2c70f58..0000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=python -msphinx -) -set SOURCEDIR=source -set BUILDDIR=build -set SPHINXPROJ=idesolver - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The Sphinx module was not found. Make sure you have Sphinx installed, - echo.then set the SPHINXBUILD environment variable to point to the full - echo.path of the 'sphinx-build' executable. Alternatively you may add the - echo.Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -E -a -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/make_figs.py b/docs/make_figs.py index 32b8242..2b7f51c 100644 --- a/docs/make_figs.py +++ b/docs/make_figs.py @@ -1,21 +1,23 @@ import os +from pathlib import Path import matplotlib.pyplot as plt -import numpy as np +from numpy import abs, float_, linspace, log +from numpy.typing import NDArray from idesolver import IDESolver -FIGS_DIR = os.path.join(os.path.dirname(__file__), "source", "figs") +FIGS_DIR = Path(__file__).resolve().parent / "assets" -EXTENSIONS = ["png", "pdf"] +EXTENSIONS = ["png"] -def savefig(name): +def savefig(name: str) -> None: for ext in EXTENSIONS: plt.savefig(os.path.join(FIGS_DIR, f"{name}.{ext}")) -def make_comparison_plot(name, solver, exact): +def make_comparison_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure(dpi=600) ax = fig.add_subplot(111) @@ -32,11 +34,11 @@ def make_comparison_plot(name, solver, exact): savefig(name) -def make_error_plot(name, solver, exact): +def make_error_plot(name: str, solver: IDESolver, exact: NDArray[float_]) -> None: fig = plt.figure(dpi=600) ax = fig.add_subplot(111) - error = np.abs(solver.y - exact) + error = abs(solver.y - exact) ax.plot(solver.x, error, linewidth=3) @@ -53,19 +55,19 @@ def make_error_plot(name, solver, exact): savefig(name) -def quickstart_example(): +def quickstart_example() -> None: solver = IDESolver( - x=np.linspace(0, 1, 100), + x=linspace(0, 1, 100), y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, + c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - log(1 + x), + d=lambda x: 1 / (log(2)) ** 2, k=lambda x, s: x / (1 + s), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, ) solver.solve() - exact = np.log(1 + solver.x) + exact = log(1 + solver.x) make_comparison_plot("quickstart_comparison", solver, exact) make_error_plot("quickstart_error", solver, exact) diff --git a/docs/manual.md b/docs/manual.md new file mode 100644 index 0000000..f442dad --- /dev/null +++ b/docs/manual.md @@ -0,0 +1,61 @@ +IDESolver implements an iterative algorithm from [this paper](https://doi.org/10.1016/j.cpc.2013.09.008) for solving general IDEs. +The algorithm requires an ODE integrator and a quadrature integrator internally. +IDESolver uses [`scipy.integrate.solve_ivp`][scipy.integrate.solve_ivp] as the ODE integrator. +The quadrature integrator is either [`scipy.integrate.quad`][scipy.integrate.quad] or [`idesolver.complex_quad`][idesolver.complex_quad], +a thin wrapper over [`scipy.integrate.quad`][scipy.integrate.quad] which handles splitting the real and imaginary parts of the integral. + +## The Algorithm + +We want to find an approximate solution to + +$$ +\begin{aligned} + \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y(s) ) \, ds, \\ + & x \in [a, b], \quad y(a) = y_0. +\end{aligned} +$$ + +The algorithm begins by creating an initial guess for $y$ by using an ODE solver on + +$$ + \frac{dy}{dx} = c(y, x) +$$ + +Since there's no integral on the right-hand-side, standard ODE solvers can handle it easily. +Call this guess $y^{(0)}$. +We can then produce a better guess by seeing what we would get with the original IDE, but replacing $y` on the right-hand-side by :math:`y^{(0)}$: + +$$ + \frac{dy^{(1/2)}}{dx} = c(y^{(0)}, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y^{(0)}(s) ) \, ds +$$ + +Again, this is just an ODE, because $y^{(1/2)}$ does not appear on the right. +At this point in the algorithm we check the global error between $y^{(0)}$ and $y^{(1/2)}$. +If it's smaller than the tolerance, we stop iterating and take $y^{(1/2)}$ to be the solution. +If it's larger than the tolerance, the iteration continues. +To be conservative and to make sure we don't over-correct, we'll combine $y^{(1/2)}$ with $y^{(0)}$. + +$$ + y^{(1)} = \alpha \, y^{(0)} + (1 - \alpha) \, y^{(1/2)} +$$ + +The process then repeats: solve the IDE-turned-ODE with $y^{(1)}$ on the right-hand-side, see how different it is, maybe make a new guess, etc. + +## Stopping Conditions + +IDESolver can operate in three modes: either a nonzero global error tolerance should be given, or a maximum number of iterations should be given, or both should be given. + +- Nonzero global error tolerance is the standard mode, as described above. +- If a maximum number of iterations is given with zero global error tolerance, the algorithm will iterate that many times and then stop. +- If both are given, the algorithm terminates if either condition is met. + + +## Global Error Estimate + +The default global error estimate $G` between two possible solutions $y_1$ and $y_2$ is + +$$ + G(y_1, y_2) = \sqrt{ \sum_{x_i} \left| y_1(x_i) - y_2(x_i) \right| } +$$ + +A different global error estimator can be passed in the constructor as the argument `global_error_function`. diff --git a/docs/parallelization.md b/docs/parallelization.md new file mode 100644 index 0000000..fe361f4 --- /dev/null +++ b/docs/parallelization.md @@ -0,0 +1,70 @@ +## Can I pickle an ``IDESolver`` instance? + +Yes, with one caveat. +You'll need to define the callables somewhere that Python can find them in the global namespace (i.e., top-level functions in a module, methods in a top-level class, etc.). + +## Can I parallelize `IDESolver`? + +Not directly: the iterative algorithm is serial by nature. + +However, if you have lots of IDEs to solve, you can farm them out to individual cores using Python's `multiprocessing` module (multithreading won't provide any advantage). +Here's an example of using a [`multiprocessing.Pool`][multiprocessing.pool.Pool] to solve several IDEs in parallel: + +```python +import multiprocessing +import numpy as np +from idesolver import IDESolver + + +def run(solver): + solver.solve() + + return solver + + +def c(x, y): + return y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x) + + +def d(x): + return 1 / (np.log(2)) ** 2 + + +def k(x, s): + return x / (1 + s) + + +def lower_bound(x): + return 0 + + +def upper_bound(x): + return 1 + + +def f(y): + return y + + +if __name__ == "__main__": + ides = [ + IDESolver( + x=np.linspace(0, 1, 100), + y_0=0, + c=c, + d=d, + k=k, + lower_bound=lower_bound, + upper_bound=upper_bound, + f=f, + ) + for y_0 in np.linspace(0, 1, 10) + ] + + with multiprocessing.Pool(processes=2) as pool: + results = pool.map(run, ides) + + print(results) +``` + +Note that the callables all need to defined before the if-name-main so that they can be pickled. diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..7024ea0 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,122 @@ +Suppose we want to solve the integro-differential equation (IDE) + +$$ +\begin{aligned} + \frac{dy}{dx} & = y(x) - \frac{x}{2} + \frac{1}{1 + x} - \ln(1 + x) + \frac{1}{\left(\ln(2)\right)^2} \int_0^1 \frac{x}{1 + s} \, y(s) \, ds, \\ + & x \in [0, 1], \quad y(0) = 0. +\end{aligned} +$$ + +The analytic solution to this IDE is $y(x) = \ln(1 + x)$. +We'll find a numerical solution using IDESolver and compare it to the analytic solution. + +The first thing we need to do is install IDESolver. +If you're using `pip`, that will be something like running `python -m pip install idesolver` in your terminal, +ideally with a virtual environment activated. + +Now we can create an instance of [`IDESolver`][idesolver.IDESolver], +passing it information about the IDE that we want to solve. +The format is + +$$ +\begin{aligned} + \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y(s) ) \, ds, \\ + & x \in [a, b], \quad y(a) = y_0. +\end{aligned} +$$ + +so we have + +$$ +\begin{aligned} + a &= 0 \\ + b &= 1 \\ + y(a) &= 0 \\ \\ + c(x, y) &= y(x) - \frac{x}{2} + \frac{1}{1 + x} - \ln(1 + x) \\ + d(x) &= \frac{1}{\left(\ln(2)\right)^2} \\ + k(x, s) &= \frac{x}{1 + s} \\ + f(s) &= y(s) \\ \\ + \alpha(x) &= 0 \\ + \beta(x) &= 1. +\end{aligned} +$$ + +In code, that looks like (using `lambda` functions for compactness): + +```python +import numpy as np + +from idesolver import IDESolver + +solver = IDESolver( + x=np.linspace(0, 1, 100), + y_0=0, + c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), + d=lambda x: 1 / (np.log(2)) ** 2, + k=lambda x, s: x / (1 + s), + f=lambda y: y, + lower_bound=lambda x: 0, + upper_bound=lambda x: 1, +) +``` + + +To run the solver, we call the `solve()` method: + +```python +solver.solve() + +solver.x # whatever we passed in for x +solver.y # the solution y(x) +``` + + +The default global error tolerance is $10^{-6}$, with no maximum number of iterations. +For this IDE the algorithm converges in 40 iterations, +resulting in a solution that closely approximates the analytic solution, as seen below. + +```python +import matplotlib.pyplot as plt + +fig = plt.figure(dpi=600) +ax = fig.add_subplot(111) + +exact = np.log(1 + solver.x) + +ax.plot(solver.x, solver.y, label="IDESolver Solution", linestyle="-", linewidth=3) +ax.plot(solver.x, exact, label="Analytic Solution", linestyle=":", linewidth=3) + +ax.legend(loc="best") +ax.grid(True) + +ax.set_title(f"Solution for Global Error Tolerance = {solver.global_error_tolerance}") +ax.set_xlabel(r"$x$") +ax.set_ylabel(r"$y(x)$") + +plt.show() +``` + +![Comparison between the analytic and numerical solutions](assets/quickstart_comparison.png) + + +```python +fig = plt.figure(dpi=600) +ax = fig.add_subplot(111) + +error = np.abs(solver.y - exact) + +ax.plot(solver.x, error, linewidth=3) + +ax.set_yscale("log") +ax.grid(True) + +ax.set_title( + f"Local Error for Global Error Tolerance = {solver.global_error_tolerance}" +) +ax.set_xlabel(r"$x$") +ax.set_ylabel(r"$\left| y_{\mathrm{idesolver}}(x) - y_{\mathrm{analytic}}(x) \right|$") + +plt.show() +``` + +![Error between the analytic and numerical solutions](assets/quickstart_error.png) diff --git a/docs/source/_static/figs/quickstart_comparison.pdf b/docs/source/_static/figs/quickstart_comparison.pdf deleted file mode 100644 index 24b4ab8..0000000 Binary files a/docs/source/_static/figs/quickstart_comparison.pdf and /dev/null differ diff --git a/docs/source/_static/figs/quickstart_comparison.png b/docs/source/_static/figs/quickstart_comparison.png deleted file mode 100644 index eea34a6..0000000 Binary files a/docs/source/_static/figs/quickstart_comparison.png and /dev/null differ diff --git a/docs/source/_static/figs/quickstart_error.pdf b/docs/source/_static/figs/quickstart_error.pdf deleted file mode 100644 index 420e4ad..0000000 Binary files a/docs/source/_static/figs/quickstart_error.pdf and /dev/null differ diff --git a/docs/source/_static/figs/quickstart_error.png b/docs/source/_static/figs/quickstart_error.png deleted file mode 100644 index 777fdcf..0000000 Binary files a/docs/source/_static/figs/quickstart_error.png and /dev/null differ diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 0de5ba6..0000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,12 +0,0 @@ -API -=== - -.. py:module:: idesolver - -.. autoclass:: IDESolver - - .. automethod:: solve - -.. autofunction:: global_error - -.. autofunction:: complex_quad diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst deleted file mode 100644 index 9012915..0000000 --- a/docs/source/changelog.rst +++ /dev/null @@ -1,34 +0,0 @@ -Change Log -========== - -.. currentmodule:: idesolver - -v1.1.0 ------- -* Add support for multidimensional IDEs (PR :pr:`35` resolves :issue:`28`, thanks `nbrucy `_!) - -v1.0.5 ------- -* Relaxes dependency version restrictions in advance of changes to ``pip``. - There shouldn't be any impact on users. - -v1.0.4 ------- -* Revision of packaging and CI flow. There shouldn't be any impact on users. - -v1.0.3 ------- -* Revision of package structure and CI flow. There shouldn't be any impact on users. - -v1.0.2 ------- -* IDESolver now explicitly requires Python 3.6+ on install. Dependencies on ``numpy`` and ``scipy`` are given as lower bounds. - -v1.0.1 ------- -* Changed the name of ``IDESolver.F`` to ``f``, as intended. -* The default global error function is now injected instead of hard-coded. - -v1.0.0 ------- -Initial release. diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 312bbb6..0000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# idesolver documentation build configuration file, created by -# sphinx-quickstart on Thu Oct 12 15:45:51 2017. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -import os -import sys - -import sphinx_rtd_theme - -sys.path.insert(0, os.path.abspath("../../")) - -import idesolver - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.mathjax", - "sphinx.ext.viewcode", - "sphinx.ext.githubpages", - "sphinx.ext.napoleon", - "sphinx_rtd_theme", - "sphinx_issues", -] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = "idesolver" -copyright = "2017-2019, Joshua T Karpel" -author = "Joshua T Karpel" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = idesolver.__version__ -# The full version, including alpha/beta/rc tags. -release = idesolver.__version__ - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path -exclude_patterns = [] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# -- Options for HTML output ---------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# -# html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] - -# Custom sidebar templates, must be a dictionary that maps document names -# to template names. -# -# This is required for the alabaster theme -# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars -# html_sidebars = { -# '**': [ -# 'about.html', -# 'navigation.html', -# 'relations.html', # needs 'show_related': True theme option to display -# 'searchbox.html', -# 'donate.html', -# ] -# } - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = "idesolverdoc" - -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - ( - master_doc, - "idesolver.tex", - "idesolver Documentation", - "Joshua T Karpel", - "manual", - ) -] - -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "idesolver", "idesolver Documentation", [author], 1)] - -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - master_doc, - "idesolver", - "idesolver Documentation", - author, - "idesolver", - "A general purpose integro-differential equation solver.", - "Miscellaneous", - ) -] - -# -- Options for Epub output ---------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = project -epub_author = author -epub_publisher = author -epub_copyright = copyright - -# The unique identifier of the text. This can be a ISBN number -# or the project homepage. -# -# epub_identifier = '' - -# A unique identification for the text. -# -# epub_uid = '' - -# A list of files that should not be packed into the epub file. -epub_exclude_files = ["search.html"] - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - "https://docs.python.org/3/": None, - "https://docs.scipy.org/doc/numpy/": None, - "https://docs.scipy.org/doc/scipy/reference": None, -} - -autodoc_member_order = "bysource" -autoclass_content = "both" - -# sphinx-issues config -issues_github_path = "JoshKarpel/idesolver" diff --git a/docs/source/faq.rst b/docs/source/faq.rst deleted file mode 100644 index 5437064..0000000 --- a/docs/source/faq.rst +++ /dev/null @@ -1,88 +0,0 @@ -Frequently Asked Questions -========================== - -.. currentmodule:: idesolver - -How do I install IDESolver? ---------------------------- - -Installing IDESolver is easy, using `pip `_: - -.. code-block:: console - - $ pip install idesolver - - -Can I pickle an ``IDESolver`` instance? ---------------------------------------- - -Yes, with one caveat. -You'll need to define the callables somewhere that Python can find them in the global namespace (i.e., top-level functions in a module, methods in a top-level class, etc.). - - -Can I parallelize IDESolver over multiple cores? ------------------------------------------------- - -Not directly - the iterative algorithm is serial by nature. -However, if you have lots of IDEs to solve, you can farm them out to individual cores using Python's ``multiprocessing`` module (multithreading won't provide any advantage). -Here's an example of using a :class:`multiprocessing.Pool` to solve several IDEs in parallel: - -:: - - import multiprocessing - import numpy as np - from idesolver import IDESolver - - - def run(solver): - solver.solve() - - return solver - - - def c(x, y): - return y - (.5 * x) + (1 / (1 + x)) - np.log(1 + x) - - - def d(x): - return 1 / (np.log(2)) ** 2 - - - def k(x, s): - return x / (1 + s) - - - def lower_bound(x): - return 0 - - - def upper_bound(x): - return 1 - - - def f(y): - return y - - - if __name__ == '__main__': - ides = [ - IDESolver( - x = np.linspace(0, 1, 100), - y_0 = 0, - c = c, - d = d, - k = k, - lower_bound = lower_bound, - upper_bound = upper_bound, - f = f, - ) - for y_0 in np.linspace(0, 1, 10) - ] - - with multiprocessing.Pool(processes = 2) as pool: - results = pool.map(run, ides) - - print(results) - - -Note that the callables all need to defined before the if-name-main so that they can be pickled. diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 2cfe03f..0000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,45 +0,0 @@ -Overview -======== - -.. currentmodule:: idesolver - -`IDESolver `_ is a package that provides an interface for solving real- or complex-valued integro-differential equations (IDEs) of the form - -.. math:: - - \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y(s) ) \, ds, \\ - & x \in [a, b], \quad y(a) = y_0. - -Integro-differential equations appear in many contexts, particularly when trying to describe a system whose current behavior depends on its own history. -The IDESolver is an iterative solver, which means it generates successive approximations to the exact solution, using each approximation to generate the next (hopefully better) one. -The algorithm is based on a scheme devised by `Gelmi and Jorquera `_. - -If you use IDESolver in your work, please consider `citing it `_. - -:doc:`quickstart` - A brief tutorial in using IDESolver. - -:doc:`manual` - Details about the implementation of IDESolver. - Includes information about running the test suite. - -:doc:`api` - Detailed documentation for IDESolver's API. - -:doc:`faq` - These are questions are asked, sometimes frequently. - -:doc:`changelog` - Change logs going back to the initial release. - - -.. toctree:: - :hidden: - :maxdepth: 2 - - self - quickstart - manual - api - faq - changelog diff --git a/docs/source/manual.rst b/docs/source/manual.rst deleted file mode 100644 index 847bdf7..0000000 --- a/docs/source/manual.rst +++ /dev/null @@ -1,79 +0,0 @@ -Manual -====== - -.. currentmodule:: idesolver - -:class:`IDESolver` implements an iterative algorithm from `this paper `_ for solving general IDEs. -The algorithm requires an ODE integrator and a quadrature integrator internally. -IDESolver uses :func:`scipy.integrate.solve_ivp` as the ODE integrator. -The quadrature integrator is either :func:`scipy.integrate.quad` or :func:`complex_quad`, a thin wrapper over :func:`scipy.integrate.quad` which handles splitting the real and imaginary parts of the integral. - -.. _the-algorithm: - -The Algorithm -------------- - -We want to find an approximate solution to - -.. math:: - - \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y(s) ) \, ds, \\ - & x \in [a, b], \quad y(a) = y_0. - - -The algorithm begins by creating an initial guess for :math:`y` by using an ODE solver on - -.. math:: - - \frac{dy}{dx} = c(y, x) - - -Since there's no integral on the right-hand-side, standard ODE solvers can handle it easily. -Call this guess :math:`y^{(0)}`. -We can then produce a better guess by seeing what we would get with the original IDE, but replacing :math:`y` on the right-hand-side by :math:`y^{(0)}`: - -.. math:: - - \frac{dy^{(1/2)}}{dx} = c(y^{(0)}, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y^{(0)}(s) ) \, ds - - -Again, this is just an ODE, because :math:`y^{(1/2)}` does not appear on the right. -At this point in the algorithm we check the global error between :math:`y^{(0)}` and :math:`y^{(1/2)}`. -If it's smaller than the tolerance, we stop iterating and take :math:`y^{(1/2)}` to be the solution. -If it's larger than the tolerance, the iteration continues. -To be conservative and to make sure we don't over-correct, we'll combine :math:`y^{(1/2)}` with :math:`y^{(0)}`. - -.. math:: - - y^{(1)} = \alpha y^{(0)} + (1 - \alpha) y^{(1/2)} - - -The process then repeats: solve the IDE-turned-ODE with :math:`y^{(1)}` on the right-hand-side, see how different it is, maybe make a new guess, etc. - -Stopping Conditions -------------------- - -IDESolver can operate in three modes: either a nonzero global error tolerance should be given, or a maximum number of iterations should be given, or both should be given. -Nonzero global error tolerance is the standard mode, described in :ref:`the-algorithm`. -If a maximum number of iterations is given with zero global error tolerance, the algorithm will iterate that many times and then stop. -If both are given, the algorithm terminates if either condition is met. - - -Global Error Estimate ---------------------- - -The default global error estimate :math:`G` between two possible solutions :math:`y_1` and :math:`y_2` is - -.. math:: - - G = \sqrt{ \sum_{x_i} \left| y_1(x_i) - y_2(x_i) \right| } - -A different global error estimator can be passed in the constructor as the argument `global_error_function`. - - -Test Suite ----------- - -First, get the entire IDESolver repository via ``git clone https://github.com/JoshKarpel/idesolver.git``. -Running the test suite requires some additional Python packages: run ``pip install -r requirements-dev.txt`` from the repository root to install them. -Once installed, you can run the test suite by running ``pytest`` from the repository root. diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst deleted file mode 100644 index 95cedd7..0000000 --- a/docs/source/quickstart.rst +++ /dev/null @@ -1,116 +0,0 @@ -Quickstart -========== - -.. currentmodule:: idesolver - -Suppose we want to solve the integro-differential equation (IDE) - -.. math:: - - \frac{dy}{dx} & = y(x) - \frac{x}{2} + \frac{1}{1 + x} - \ln(1 + x) + \frac{1}{\left(\ln(2)\right)^2} \int_0^1 \frac{x}{1 + s} \, y(s) \, ds, \\ - & x \in [0, 1], \quad y(0) = 0. - -The analytic solution to this IDE is :math:`y(x) = \ln(1 + x)`. -We'll find a numerical solution using IDESolver and compare it to the analytic solution. - -The very first thing we need to do is install IDESolver. -You'll want to install it via `pip` (`pip install idesolver`) into a `virtual environment `_. - -Now we can create an instance of :class:`IDESolver`, passing it information about the IDE that we want to solve. -The format is - -.. math:: - - \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, F( y(s) ) \, ds, \\ - & x \in [a, b], \quad y(a) = y_0. - -so we have - -.. math:: - - a &= 0 \\ - b &= 1 \\ - y(a) &= 0 \\ \\ - c(x, y) =& y(x) - \frac{x}{2} + \frac{1}{1 + x} - \ln(1 + x) \\ - d(x) =& \frac{1}{\left(\ln(2)\right)^2} \\ - k(x, s) =& \frac{x}{1 + s} \\ - f(s) &= y(s) \\ \\ - \alpha(x) =& 0 \\ - \beta(x) =& 1. - -In code, that looks like (using ``lambda`` functions for simplicity): - -:: - - import numpy as np - - from idesolver import IDESolver - - solver = IDESolver( - x = np.linspace(0, 1, 100), - y_0 = 0, - c = lambda x, y: y - (.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d = lambda x: 1 / (np.log(2)) ** 2, - k = lambda x, s: x / (1 + s), - f = lambda y: y, - lower_bound = lambda x: 0, - upper_bound = lambda x: 1, - ) - - -To run the solver, we call the ``solve()`` method: - -:: - - solver.solve() - - solver.x # whatever we passed in for x - solver.y # the solution y(x) - - -The default global error tolerance is :math:`10^{-6}`, with no maximum number of iterations. -For this IDE the algorithm converges in 40 iterations, resulting in a solution that closely approximates the analytic solution, as seen below. - -:: - - import matplotlib.pyplot as plt - - fig = plt.figure(dpi = 600) - ax = fig.add_subplot(111) - - exact = np.log(1 + solver.x) - - ax.plot(solver.x, solver.y, label = 'IDESolver Solution', linestyle = '-', linewidth = 3) - ax.plot(solver.x, exact, label = 'Analytic Solution', linestyle = ':', linewidth = 3) - - ax.legend(loc = 'best') - ax.grid(True) - - ax.set_title(f'Solution for Global Error Tolerance = {solver.global_error_tolerance}') - ax.set_xlabel(r'$x$') - ax.set_ylabel(r'$y(x)$') - - plt.show() - - -.. image:: /_static/figs/quickstart_comparison.* - -:: - - fig = plt.figure(dpi = 600) - ax = fig.add_subplot(111) - - error = np.abs(solver.y - exact) - - ax.plot(solver.x, error, linewidth = 3) - - ax.set_yscale('log') - ax.grid(True) - - ax.set_title(f'Local Error for Global Error Tolerance = {solver.global_error_tolerance}') - ax.set_xlabel(r'$x$') - ax.set_ylabel(r'$\left| y_{\mathrm{idesolver}}(x) - y_{\mathrm{analytic}}(x) \right|$') - - plt.show() - -.. image:: /_static/figs/quickstart_error.* diff --git a/idesolver/__init__.py b/idesolver/__init__.py index da0db32..9d4967f 100644 --- a/idesolver/__init__.py +++ b/idesolver/__init__.py @@ -1,10 +1,22 @@ -""" -A general purpose integro-differential equation (IDE) solver. +from .constants import __version__ +from .exceptions import ( + IDEConvergenceWarning, + IDESolverException, + InvalidParameter, + ODESolutionFailed, + UnexpectedlyComplexValuedIDE, +) +from .idesolver import IDE, complex_quad, global_error, solve_ide -Copyright (C) 2017-2020 Joshua T Karpel -Full license available at https://github.com/JoshKarpel/idesolver/blob/master/LICENSE -""" - -from .exceptions import * -from .idesolver import * -from .version import __version__ +__all__ = [ + "IDE", + "solve_ide", + "IDESolverException", + "IDEConvergenceWarning", + "InvalidParameter", + "ODESolutionFailed", + "UnexpectedlyComplexValuedIDE", + "complex_quad", + "global_error", + "__version__", +] diff --git a/idesolver/constants.py b/idesolver/constants.py new file mode 100644 index 0000000..90fd5a1 --- /dev/null +++ b/idesolver/constants.py @@ -0,0 +1,3 @@ +import importlib.metadata + +__version__ = importlib.metadata.version("idesolver") diff --git a/idesolver/idesolver.py b/idesolver/idesolver.py index ad4d4d2..89de27c 100644 --- a/idesolver/idesolver.py +++ b/idesolver/idesolver.py @@ -1,12 +1,29 @@ +from __future__ import annotations + import logging import warnings -from typing import Callable, Optional, Union - -import numpy as np -import scipy.integrate as integ -import scipy.interpolate as inter - -from . import exceptions +from dataclasses import dataclass +from typing import Callable, Generic, Optional, TypeVar, Union + +from numpy import ( + ComplexWarning, + array, + complex128, + complex_, + float64, + float_, + imag, + ndarray, + real, + sqrt, + vdot, + zeros_like, +) +from numpy.typing import NDArray +from scipy.integrate import quad, solve_ivp +from scipy.interpolate import interp1d + +from idesolver import exceptions logger = logging.getLogger("idesolver") logger.setLevel(logging.DEBUG) @@ -16,14 +33,15 @@ def complex_quad( integrand: Callable, lower_bound: float, upper_bound: float, **kwargs ) -> (complex, float, float, tuple, tuple): """ - A thin wrapper over :func:`scipy.integrate.quad` that handles splitting the real and complex parts of the integral and recombining them. - Keyword arguments are passed to both of the internal ``quad`` calls. + A thin wrapper over [`scipy.integrate.quad`][scipy.integrate.quad] + that handles splitting the real and complex parts of the integral and recombining them. + Keyword arguments are passed to both of the internal [`scipy.integrate.quad`][scipy.integrate.quad] calls. """ - real_result, real_error, *real_extra = integ.quad( - lambda x: np.real(integrand(x)), lower_bound, upper_bound, **kwargs + real_result, real_error, *real_extra = quad( + lambda x: real(integrand(x)), lower_bound, upper_bound, **kwargs ) - imag_result, imag_error, *imag_extra = integ.quad( - lambda x: np.imag(integrand(x)), lower_bound, upper_bound, **kwargs + imag_result, imag_error, *imag_extra = quad( + lambda x: imag(integrand(x)), lower_bound, upper_bound, **kwargs ) return ( @@ -35,161 +53,108 @@ def complex_quad( ) -def global_error(y1: np.ndarray, y2: np.ndarray) -> float: +FloatsOrComplexes = Union[NDArray[float_], NDArray[complex_]] + + +def global_error(y1: FloatsOrComplexes, y2: FloatsOrComplexes) -> float: """ The default global error function. The estimate is the square root of the sum of squared differences between `y1` and `y2`. - Parameters - ---------- - y1 : :class:`numpy.ndarray` - A guess of the solution. - y2 : :class:`numpy.ndarray` - Another guess of the solution. + Parameters: + y1: A guess of the solution. + y2: Another guess of the solution. - Returns - ------- - error : :class:`float` + Returns: The global error estimate between `y1` and `y2`. """ diff = y1 - y2 - return np.sqrt(np.real(np.vdot(diff, diff))) + return sqrt(real(vdot(diff, diff))) def coerce_to_array( - to_coerce: Union[float, np.float64, complex, np.complex128, np.ndarray, list] -) -> np.ndarray: + to_coerce: Union[float, float64, complex, complex128, ndarray, list] +) -> ndarray: """Coerce `to_coerce` into a numpy array""" - return np.array(to_coerce, ndmin=1, copy=False) + return array(to_coerce, ndmin=1, copy=False) def dtype(n): - return n.dtype if isinstance(n, np.ndarray) else type(n) + return n.dtype if isinstance(n, ndarray) else type(n) # data types to recognize as complex in y_0 -_COMPLEX_NUMERIC_TYPES = [complex, np.complex128] +_COMPLEX_NUMERIC_TYPES = [complex, complex128] +Y = TypeVar("Y", bound=Union[float_, complex_]) -class IDESolver: - """ - A class that handles solving an integro-differential equation of the form - - .. math:: - \\frac{dy}{dx} & = c(y, x) + d(x) \\int_{\\alpha(x)}^{\\beta(x)} k(x, s) \\, F( y(s) ) \\, ds, \\\\ - & x \\in [a, b], \\quad y(a) = y_0. +@dataclass(frozen=True) +class SolveIDEResult(Generic[Y]): + ide: IDE + y: NDArray[Y] + global_error: float_ + iterations: int - Attributes - ---------- - x : :class:`numpy.ndarray` - The positions where the solution is calculated (i.e., where :math:`y` is evaluated). - y : :class:`numpy.ndarray` - The solution :math:`y(x)`. - ``None`` until :meth:`IDESolver.solve` is finished. - global_error : :class:`float` - The final global error estimate. - ``None`` until :meth:`IDESolver.solve` is finished. - iteration : :class:`int` - The current iteration. - ``None`` until :meth:`IDESolver.solve` starts. - y_intermediate : - The intermediate solutions. - Only exists if ``store_intermediate_y`` is ``True``. +class IDE(Generic[Y]): + """ + Attributes: + x: + The array of $x$ values to find the solution $y(x)$ at. + Generally something like `numpy.linspace(a, b, num_pts)`. + y_0: + The initial condition, $y_0 = y(a)$ (can be multidimensional). + c: + The function $c(y, x)$. + Defaults to $c(y, x) = 0$. + d: + The function $d(x)$. + Defaults to $d(x) = 1$. + k: + The kernel function $k(x, s)$. + Defaults to $k(x, s) = 1$. + f: + The function $F(y)$. + Defaults to $f(y) = 0$. + lower_bound: + The lower bound function $\\alpha(x)$. + Defaults to the first element of `x`. + upper_bound: + The upper bound function $\\beta(x)$. + Defaults to the last element of `x`. """ def __init__( self, - x: np.ndarray, - y_0: Union[float, np.float64, complex, np.complex128, np.ndarray, list], + x: ndarray, + y_0: Union[float, float64, complex, complex128, ndarray, list], c: Optional[Callable] = None, d: Optional[Callable] = None, k: Optional[Callable] = None, f: Optional[Callable] = None, lower_bound: Optional[Callable] = None, upper_bound: Optional[Callable] = None, - global_error_tolerance: float = 1e-6, - max_iterations: Optional[int] = None, - ode_method: str = "RK45", - ode_atol: float = 1e-8, - ode_rtol: float = 1e-8, - int_atol: float = 1e-8, - int_rtol: float = 1e-8, - interpolation_kind: str = "cubic", - smoothing_factor: float = 0.5, - store_intermediate_y: bool = False, - global_error_function: Callable = global_error, ): - """ - Parameters - ---------- - x : :class:`numpy.ndarray` - The array of :math:`x` values to find the solution :math:`y(x)` at. - Generally something like ``numpy.linspace(a, b, num_pts)``. - y_0 : :class:`float` or :class:`complex` or :class:`numpy.ndarray` - The initial condition, :math:`y_0 = y(a)` (can be multidimensional). - c : - The function :math:`c(y, x)`. - Defaults to :math:`c(y, x) = 0`. - d : - The function :math:`d(x)`. - Defaults to :math:`d(x) = 1`. - k : - The kernel function :math:`k(x, s)`. - Defaults to :math:`k(x, s) = 1`. - f : - The function :math:`F(y)`. - Defaults to :math:`f(y) = 0`. - lower_bound : - The lower bound function :math:`\\alpha(x)`. - Defaults to the first element of ``x``. - upper_bound : - The upper bound function :math:`\\beta(x)`. - Defaults to the last element of ``x``. - global_error_tolerance : :class:`float` - The algorithm will continue until the global errors goes below this or uses more than `max_iterations` iterations. If ``None``, the algorithm continues until hitting `max_iterations`. - max_iterations : :class:`int` - The maximum number of iterations to use. If ``None``, iteration will not stop unless the `global_error_tolerance` is satisfied. Defaults to ``None``. - ode_method : :class:`str` - The ODE solution method to use. As the `method` option of :func:`scipy.integrate.solve_ivp`. Defaults to ``'RK45'``, which is good for non-stiff systems. - ode_atol : :class:`float` - The absolute tolerance for the ODE solver. - As the `atol` argument of :func:`scipy.integrate.solve_ivp`. - ode_rtol : :class:`float` - The relative tolerance for the ODE solver. - As the `rtol` argument of :func:`scipy.integrate.solve_ivp`. - int_atol : :class:`float` - The absolute tolerance for the integration routine. As the `epsabs` argument of :func:`scipy.integrate.quad`. - int_rtol : :class:`float` - The relative tolerance for the integration routine. As the `epsrel` argument of :func:`scipy.integrate.quad`. - interpolation_kind : :class:`str` - The type of interpolation to use. As the `kind` argument of :class:`scipy.interpolate.interp1d`. Defaults to ``'cubic'``. - smoothing_factor : :class:`float` - The smoothing factor used to combine the current guess with the new guess at each iteration. Defaults to ``0.5``. - store_intermediate_y : :class:`bool` - If ``True``, the intermediate guesses for :math:`y(x)` at each iteration will be stored in the attribute `y_intermediate`. - global_error_function : - The function to use to calculate the global error. Defaults to :func:`global_error`. - """ self.y_0 = coerce_to_array(y_0) + zeros_like_y_0 = zeros_like(self.y_0) if dtype(self.y_0) in _COMPLEX_NUMERIC_TYPES: self.integrator = complex_quad else: - self.integrator = integ.quad + self.integrator = quad - self.x = np.array(x) + self.x = array(x) if c is None: - c = lambda x, y: self._zeros() + c = lambda x, y: zeros_like_y_0.copy() if d is None: d = lambda x: 1 if k is None: k = lambda x, s: 1 if f is None: - f = lambda y: self._zeros() + f = lambda y: zeros_like_y_0.copy() self.c = lambda x, y: coerce_to_array(c(x, y)) self.d = lambda x: coerce_to_array(d(x)) @@ -203,225 +168,547 @@ def __init__( self.lower_bound = lower_bound self.upper_bound = upper_bound - if global_error_tolerance == 0 and max_iterations is None: - raise exceptions.InvalidParameter( - "global_error_tolerance cannot be 0 if max_iterations is None" + +def solve_ide( + ide: IDE, + global_error_tolerance: float = 1e-6, + max_iterations: Optional[int] = None, + ode_method: str = "RK45", + ode_atol: float = 1e-8, + ode_rtol: float = 1e-8, + int_atol: float = 1e-8, + int_rtol: float = 1e-8, + interpolation_kind: str = "cubic", + smoothing_factor: float = 0.5, + global_error_function: Callable = global_error, + callback=None, +) -> SolveIDEResult: + r""" + A class that handles solving an integro-differential equation of the form + + $$ + \begin{aligned} + \frac{dy}{dx} & = c(y, x) + d(x) \int_{\alpha(x)}^{\beta(x)} k(x, s) \, f( y(s) ) \, ds, \\ + & x \in [a, b], \quad y(a) = y_0. + \end{aligned} + $$ + + Arguments: + ide: + The IDE to solve. + global_error_tolerance: + The algorithm will continue until the global errors goes below this or uses more than `max_iterations` iterations. If `None`, the algorithm continues until hitting `max_iterations`. + max_iterations: + The maximum number of iterations to use. If `None`, iteration will not stop unless the `global_error_tolerance` is satisfied. Defaults to `None`. + ode_method: + The ODE solution method to use. As the `method` option of :func:`scipy.integrate.solve_ivp`. Defaults to `'RK45'`, which is good for non-stiff systems. + ode_atol: + The absolute tolerance for the ODE solver. + As the `atol` argument of :func:`scipy.integrate.solve_ivp`. + ode_rtol: + The relative tolerance for the ODE solver. + As the `rtol` argument of :func:`scipy.integrate.solve_ivp`. + int_atol: + The absolute tolerance for the integration routine. As the `epsabs` argument of :func:`scipy.integrate.quad`. + int_rtol: + The relative tolerance for the integration routine. As the `epsrel` argument of :func:`scipy.integrate.quad`. + interpolation_kind: + The type of interpolation to use. As the `kind` argument of :class:`scipy.interpolate.interp1d`. Defaults to `'cubic'`. + smoothing_factor: + The smoothing factor used to combine the current guess with the new guess at each iteration. Defaults to `0.5`. + global_error_function: + The function to use to calculate the global error. Defaults to :func:`global_error`. + """ + y_0 = coerce_to_array(ide.y_0) + + if dtype(y_0) in _COMPLEX_NUMERIC_TYPES: + integrator = complex_quad + else: + integrator = quad + + if global_error_tolerance == 0 and max_iterations is None: + raise exceptions.InvalidParameter( + "global_error_tolerance cannot be 0 if max_iterations is None" + ) + if global_error_tolerance < 0: + raise exceptions.InvalidParameter("global_error_tolerance cannot be negative") + + if not 0 < smoothing_factor < 1: + raise exceptions.InvalidParameter("Smoothing factor must be between 0 and 1") + + if max_iterations is not None and max_iterations <= 0: + raise exceptions.InvalidParameter("If given, max iterations must be greater than 0") + + # check if the user messed up by not passing y_0 as a complex number when they should have + with warnings.catch_warnings(): + warnings.filterwarnings( + action="error", + message="Casting complex values", + category=ComplexWarning, + ) + + try: + # Calculate the initial guess for `y`, by considering only `c` on the right-hand side of the IDE. + y_current = _solve_ode( + rhs=ide.c, + y_0=y_0, + x=ide.x, + ode_method=ode_method, + ode_atol=ode_atol, + ode_rtol=ode_rtol, ) - if global_error_tolerance < 0: - raise exceptions.InvalidParameter("global_error_tolerance cannot be negative") - self.global_error_tolerance = global_error_tolerance - self.global_error_function = global_error_function - - self.interpolation_kind = interpolation_kind - - if not 0 < smoothing_factor < 1: - raise exceptions.InvalidParameter("Smoothing factor must be between 0 and 1") - self.smoothing_factor = smoothing_factor - - if max_iterations is not None and max_iterations <= 0: - raise exceptions.InvalidParameter("If given, max iterations must be greater than 0") - self.max_iterations = max_iterations - - self.ode_method = ode_method - self.ode_atol = ode_atol - self.ode_rtol = ode_rtol - - self.int_atol = int_atol - self.int_rtol = int_rtol - - self.store_intermediate = store_intermediate_y - if self.store_intermediate: - self.y_intermediate = [] - - self.iteration = None - self.y = None - self.global_error = None - - def _zeros(self) -> np.ndarray: - return np.zeros_like(self.y_0) - - def solve(self, callback: Optional[Callable] = None) -> np.ndarray: - """ - Compute the solution to the IDE. - - Will emit a warning message if the global error increases on an iteration. - This does not necessarily mean that the algorithm is not converging, but may indicate that it's having problems. - - Will emit a warning message if the maximum number of iterations is used without reaching the global error tolerance. - - Parameters - ---------- - callback : - A function to call after each iteration. The function is passed the :class:`IDESolver` instance, the current :math:`y` guess, and the current global error. - - Returns - ------- - :class:`numpy.ndarray` - The solution to the IDE (i.e., :math:`y(x)`). - """ - # check if the user messed up by not passing y_0 as a complex number when they should have - with warnings.catch_warnings(): - warnings.filterwarnings( - action="error", - message="Casting complex values", - category=np.ComplexWarning, + + y_guess = _solve_rhs_with_known_y( + ide=ide, + y=y_current, + integrator=integrator, + int_atol=int_atol, + int_rtol=int_rtol, + ode_method=ode_method, + ode_atol=ode_atol, + ode_rtol=ode_rtol, + interpolation_kind=interpolation_kind, ) + error_current = global_error_function(y_current, y_guess) + + iteration = 0 - try: - y_current = self._initial_y() - y_guess = self._solve_rhs_with_known_y(y_current) - error_current = self._global_error(y_current, y_guess) - if self.store_intermediate: - self.y_intermediate.append(y_current) + logger.debug(f"Advanced to iteration {iteration}. Current error: {error_current}.") + if callback is not None: + logger.debug(f"Calling {callback} after iteration {iteration}") + callback(y_guess, error_current) - self.iteration = 0 + while error_current > global_error_tolerance: + new_current = _next_y( + y_current, + y_guess, + smoothing_factor=smoothing_factor, + ) + new_guess = _solve_rhs_with_known_y( + ide=ide, + y=new_current, + integrator=integrator, + int_atol=int_atol, + int_rtol=int_rtol, + ode_method=ode_method, + ode_atol=ode_atol, + ode_rtol=ode_rtol, + interpolation_kind=interpolation_kind, + ) + new_error = global_error_function(new_current, new_guess) + if new_error > error_current: + warnings.warn( + f"Error increased on iteration {iteration}", + exceptions.IDEConvergenceWarning, + ) - logger.debug( - f"Advanced to iteration {self.iteration}. Current error: {error_current}." + y_current, y_guess, error_current = ( + new_current, + new_guess, + new_error, ) + + iteration += 1 + + logger.debug(f"Advanced to iteration {iteration}. Current error: {error_current}.") + if callback is not None: - logger.debug(f"Calling {callback} after iteration {self.iteration}") - callback(self, y_guess, error_current) - - while error_current > self.global_error_tolerance: - - new_current = self._next_y(y_current, y_guess) - new_guess = self._solve_rhs_with_known_y(new_current) - new_error = self._global_error(new_current, new_guess) - if new_error > error_current: - warnings.warn( - f"Error increased on iteration {self.iteration}", - exceptions.IDEConvergenceWarning, - ) + logger.debug(f"Calling {callback} after iteration {iteration}") + callback(y_guess, error_current) - y_current, y_guess, error_current = ( - new_current, - new_guess, - new_error, + if max_iterations is not None and iteration >= max_iterations: + warnings.warn( + exceptions.IDEConvergenceWarning( + f"Used maximum number of iterations ({max_iterations}), but only got to global error {error_current} (target {global_error_tolerance})" + ) ) + break + except (ComplexWarning, TypeError) as e: + raise exceptions.UnexpectedlyComplexValuedIDE( + "Detected complex-valued IDE. Make sure to pass y_0 as a complex number." + ) from e + + y = y_guess + global_error = error_current + + # get rid of the array wrapper if the dimension is 1 + if y_0.size == 1: + y = y[0] + + return SolveIDEResult( + ide=ide, + y=y, + global_error=global_error, + iterations=iteration, + ) - if self.store_intermediate: - self.y_intermediate.append(y_current) - self.iteration += 1 +def _next_y(curr: ndarray, guess: ndarray, smoothing_factor: float) -> ndarray: + """Calculate the next guess at the solution by merging two guesses.""" + return (smoothing_factor * curr) + ((1 - smoothing_factor) * guess) + + +def _solve_rhs_with_known_y( + ide: IDE, + y: ndarray, + integrator, + int_atol: float, + int_rtol: float, + ode_method: str, + ode_atol: float, + ode_rtol: float, + interpolation_kind: str, +) -> ndarray: + """Solves the right-hand-side of the IDE as if $y(x)$ was the fixed array `y`.""" + interpolated_y = _interpolate_y(x=ide.x, y=y, interpolation_kind=interpolation_kind) + + def integral(x): + def integrand(s): + return ide.k(x, s) * ide.f(interpolated_y(s)) + + result = [] + for i in range(ide.y_0.size): + r, *_ = integrator( + lambda s: integrand(s)[i], + ide.lower_bound(x), + ide.upper_bound(x), + epsabs=int_atol, + epsrel=int_rtol, + ) + result.append(r) + return coerce_to_array(result) - logger.debug( - f"Advanced to iteration {self.iteration}. Current error: {error_current}." - ) + def rhs(x, y): + return ide.c(x, interpolated_y(x)) + (ide.d(x) * integral(x)) - if callback is not None: - logger.debug(f"Calling {callback} after iteration {self.iteration}") - callback(self, y_guess, error_current) + return _solve_ode( + rhs=rhs, y_0=ide.y_0, x=ide.x, ode_method=ode_method, ode_atol=ode_atol, ode_rtol=ode_rtol + ) - if self.max_iterations is not None and self.iteration >= self.max_iterations: - warnings.warn( - exceptions.IDEConvergenceWarning( - f"Used maximum number of iterations ({self.max_iterations}), but only got to global error {error_current} (target {self.global_error_tolerance})" - ) - ) - break - except (np.ComplexWarning, TypeError) as e: - raise exceptions.UnexpectedlyComplexValuedIDE( - "Detected complex-valued IDE. Make sure to pass y_0 as a complex number." - ) from e - - self.y = y_guess - self.global_error = error_current - - # get rid of the array wrapper if the dimension is 1 - if self.y_0.size == 1: - self.y = self.y[0] - if self.store_intermediate: - self.y_intermediate = [y[0] for y in self.y_intermediate] - - return self.y - - def _initial_y(self) -> np.ndarray: - """Calculate the initial guess for `y`, by considering only `c` on the right-hand side of the IDE.""" - return self._solve_ode(self.c) - - def _next_y(self, curr: np.ndarray, guess: np.ndarray) -> np.ndarray: - """Calculate the next guess at the solution by merging two guesses.""" - return (self.smoothing_factor * curr) + ((1 - self.smoothing_factor) * guess) - - def _global_error(self, y1: np.ndarray, y2: np.ndarray) -> float: - """ - Return the global error estimate between `y1` and `y2`. - - Parameters - ---------- - y1 - A guess of the solution. - y2 - Another guess of the solution. - - Returns - ------- - error : :class:`float` - The global error estimate between `y1` and `y2`. - """ - return self.global_error_function(y1, y2) - - def _solve_rhs_with_known_y(self, y: np.ndarray) -> np.ndarray: - """Solves the right-hand-side of the IDE as if :math:`y` was `y`.""" - interpolated_y = self._interpolate_y(y) - - def integral(x): - def integrand(s): - return self.k(x, s) * self.f(interpolated_y(s)) - - result = [] - for i in range(self.y_0.size): - r, *_ = self.integrator( - lambda s: integrand(s)[i], - self.lower_bound(x), - self.upper_bound(x), - epsabs=self.int_atol, - epsrel=self.int_rtol, - ) - result.append(r) - return coerce_to_array(result) - - def rhs(x, y): - return self.c(x, interpolated_y(x)) + (self.d(x) * integral(x)) - - return self._solve_ode(rhs) - - def _interpolate_y(self, y: np.ndarray) -> inter.interp1d: - """ - Interpolate `y` along `x`, using `interpolation_kind`. - - Parameters - ---------- - y : :class:`numpy.ndarray` - The y values to interpolate (probably a guess at the solution). - - Returns - ------- - interpolator : :class:`scipy.interpolate.interp1d` - The interpolator function. - """ - return inter.interp1d( - x=self.x, - y=y, - kind=self.interpolation_kind, - fill_value="extrapolate", - assume_sorted=True, - ) - def _solve_ode(self, rhs: Callable) -> np.ndarray: - """Solves an ODE with the given right-hand side.""" - sol = integ.solve_ivp( - fun=rhs, - y0=self.y_0, - t_span=(self.x[0], self.x[-1]), - t_eval=self.x, - method=self.ode_method, - atol=self.ode_atol, - rtol=self.ode_rtol, - ) +def _interpolate_y(x: NDArray[float_], y: ndarray, interpolation_kind: str) -> interp1d: + """ + Interpolate `y` along `x`, using `interpolation_kind`. - if not sol.success: - raise exceptions.ODESolutionFailed(f"Error while trying to solve ODE: {sol.status}") + Parameters + ---------- + y : :class:`numpy.ndarray` + The y values to interpolate (probably a guess at the solution). + + Returns + ------- + interpolator : :class:`scipy.interpolate.interp1d` + The interpolator function. + """ + return interp1d( + x=x, + y=y, + kind=interpolation_kind, + fill_value="extrapolate", + assume_sorted=True, + ) + + +def _solve_ode( + rhs: Callable, + y_0, + x, + ode_method: str, + ode_atol: float, + ode_rtol: float, +) -> ndarray: + """Solves an ODE with the given right-hand side.""" + sol = solve_ivp( + fun=rhs, + y0=y_0, + t_span=(x[0], x[-1]), + t_eval=x, + method=ode_method, + atol=ode_atol, + rtol=ode_rtol, + ) - return sol.y + if not sol.success: + raise exceptions.ODESolutionFailed(f"Error while trying to solve ODE: {sol.status}") + + return sol.y + + +# class IDESolver: +# r""" +# +# +# Attributes: +# x: The positions where the solution is calculated (i.e., where $y$ is evaluated). +# y: The solution $y(x)$. `None` until [`IDESolver.solve`][idesolver.IDESolver.solve] is finished. +# global_error: The final global error estimate. `None` until [`IDESolver.solve`][idesolver.IDESolver.solve] is finished. +# iteration: The current iteration. `None` until [`IDESolver.solve`][idesolver.IDESolver.solve] starts. +# y_intermediate: The intermediate solutions. Only exists if `store_intermediate_y` is `True`. +# +# """ +# +# def __init__( +# self, +# x: ndarray, +# y_0: Union[float, float64, complex, complex128, ndarray, list], +# c: Optional[Callable] = None, +# d: Optional[Callable] = None, +# k: Optional[Callable] = None, +# f: Optional[Callable] = None, +# lower_bound: Optional[Callable] = None, +# upper_bound: Optional[Callable] = None, +# global_error_tolerance: float = 1e-6, +# max_iterations: Optional[int] = None, +# ode_method: str = "RK45", +# ode_atol: float = 1e-8, +# ode_rtol: float = 1e-8, +# int_atol: float = 1e-8, +# int_rtol: float = 1e-8, +# interpolation_kind: str = "cubic", +# smoothing_factor: float = 0.5, +# store_intermediate_y: bool = False, +# global_error_function: Callable = global_error, +# ): +# self.y_0 = coerce_to_array(y_0) +# +# if dtype(self.y_0) in _COMPLEX_NUMERIC_TYPES: +# self.integrator = complex_quad +# else: +# self.integrator = integ.quad +# +# self.x = array(x) +# +# if c is None: +# c = lambda x, y: self._zeros() +# if d is None: +# d = lambda x: 1 +# if k is None: +# k = lambda x, s: 1 +# if f is None: +# f = lambda y: self._zeros() +# +# self.c = lambda x, y: coerce_to_array(c(x, y)) +# self.d = lambda x: coerce_to_array(d(x)) +# self.k = lambda x, s: coerce_to_array(k(x, s)) +# self.f = lambda y: coerce_to_array(f(y)) +# +# if lower_bound is None: +# lower_bound = lambda x: self.x[0] +# if upper_bound is None: +# upper_bound = lambda x: self.x[-1] +# self.lower_bound = lower_bound +# self.upper_bound = upper_bound +# +# if global_error_tolerance == 0 and max_iterations is None: +# raise exceptions.InvalidParameter( +# "global_error_tolerance cannot be 0 if max_iterations is None" +# ) +# if global_error_tolerance < 0: +# raise exceptions.InvalidParameter("global_error_tolerance cannot be negative") +# self.global_error_tolerance = global_error_tolerance +# self.global_error_function = global_error_function +# +# self.interpolation_kind = interpolation_kind +# +# if not 0 < smoothing_factor < 1: +# raise exceptions.InvalidParameter("Smoothing factor must be between 0 and 1") +# self.smoothing_factor = smoothing_factor +# +# if max_iterations is not None and max_iterations <= 0: +# raise exceptions.InvalidParameter("If given, max iterations must be greater than 0") +# self.max_iterations = max_iterations +# +# self.ode_method = ode_method +# self.ode_atol = ode_atol +# self.ode_rtol = ode_rtol +# +# self.int_atol = int_atol +# self.int_rtol = int_rtol +# +# self.store_intermediate = store_intermediate_y +# if self.store_intermediate: +# self.y_intermediate = [] +# +# self.iteration = None +# self.y = None +# self.global_error = None +# +# def _zeros(self) -> ndarray: +# return zeros_like(self.y_0) +# +# def solve(self, callback: Optional[Callable] = None) -> ndarray: +# """ +# Compute the solution to the IDE. +# +# Will emit a warning message if the global error increases on an iteration. +# This does not necessarily mean that the algorithm is not converging, but may indicate that it's having problems. +# +# Will emit a warning message if the maximum number of iterations is used without reaching the global error tolerance. +# +# Parameters: +# callback: A function to call after each iteration. +# The function is passed the [`IDESolver`][idesolver.IDESolver] instance, the current $y$ guess, and the current global error. +# +# Returns: +# The solution to the IDE (i.e., $y(x)$). +# """ +# # check if the user messed up by not passing y_0 as a complex number when they should have +# with warnings.catch_warnings(): +# warnings.filterwarnings( +# action="error", +# message="Casting complex values", +# category=ComplexWarning, +# ) +# +# try: +# y_current = self._initial_y() +# y_guess = self._solve_rhs_with_known_y(y_current) +# error_current = self._global_error(y_current, y_guess) +# if self.store_intermediate: +# self.y_intermediate.append(y_current) +# +# self.iteration = 0 +# +# logger.debug( +# f"Advanced to iteration {self.iteration}. Current error: {error_current}." +# ) +# if callback is not None: +# logger.debug(f"Calling {callback} after iteration {self.iteration}") +# callback(self, y_guess, error_current) +# +# while error_current > self.global_error_tolerance: +# new_current = self._next_y(y_current, y_guess) +# new_guess = self._solve_rhs_with_known_y(new_current) +# new_error = self._global_error(new_current, new_guess) +# if new_error > error_current: +# warnings.warn( +# f"Error increased on iteration {self.iteration}", +# exceptions.IDEConvergenceWarning, +# ) +# +# y_current, y_guess, error_current = ( +# new_current, +# new_guess, +# new_error, +# ) +# +# if self.store_intermediate: +# self.y_intermediate.append(y_current) +# +# self.iteration += 1 +# +# logger.debug( +# f"Advanced to iteration {self.iteration}. Current error: {error_current}." +# ) +# +# if callback is not None: +# logger.debug(f"Calling {callback} after iteration {self.iteration}") +# callback(self, y_guess, error_current) +# +# if self.max_iterations is not None and self.iteration >= self.max_iterations: +# warnings.warn( +# exceptions.IDEConvergenceWarning( +# f"Used maximum number of iterations ({self.max_iterations}), but only got to global error {error_current} (target {self.global_error_tolerance})" +# ) +# ) +# break +# except (ComplexWarning, TypeError) as e: +# raise exceptions.UnexpectedlyComplexValuedIDE( +# "Detected complex-valued IDE. Make sure to pass y_0 as a complex number." +# ) from e +# +# self.y = y_guess +# self.global_error = error_current +# +# # get rid of the array wrapper if the dimension is 1 +# if self.y_0.size == 1: +# self.y = self.y[0] +# if self.store_intermediate: +# self.y_intermediate = [y[0] for y in self.y_intermediate] +# +# return self.y +# +# def _initial_y(self) -> ndarray: +# """Calculate the initial guess for `y`, by considering only `c` on the right-hand side of the IDE.""" +# return self._solve_ode(self.c) +# +# def _next_y(self, curr: ndarray, guess: ndarray) -> ndarray: +# """Calculate the next guess at the solution by merging two guesses.""" +# return (self.smoothing_factor * curr) + ((1 - self.smoothing_factor) * guess) +# +# def _global_error(self, y1: ndarray, y2: ndarray) -> float: +# """ +# Return the global error estimate between `y1` and `y2`. +# +# Parameters: +# y1: A guess of the solution. +# y2: Another guess of the solution. +# +# Returns: +# error: The global error estimate between `y1` and `y2`. +# """ +# return self.global_error_function(y1, y2) +# +# def _solve_rhs_with_known_y(self, y: ndarray) -> ndarray: +# """Solves the right-hand-side of the IDE as if $y(x)$ was the fixed array `y`.""" +# interpolated_y = self._interpolate_y(y) +# +# def integral(x): +# def integrand(s): +# return self.k(x, s) * self.f(interpolated_y(s)) +# +# result = [] +# for i in range(self.y_0.size): +# r, *_ = self.integrator( +# lambda s: integrand(s)[i], +# self.lower_bound(x), +# self.upper_bound(x), +# epsabs=self.int_atol, +# epsrel=self.int_rtol, +# ) +# result.append(r) +# return coerce_to_array(result) +# +# def rhs(x, y): +# return self.c(x, interpolated_y(x)) + (self.d(x) * integral(x)) +# +# return self._solve_ode(rhs) +# +# def _interpolate_y(self, y: ndarray) -> inter.interp1d: +# """ +# Interpolate `y` along `x`, using `interpolation_kind`. +# +# Parameters +# ---------- +# y : :class:`numpy.ndarray` +# The y values to interpolate (probably a guess at the solution). +# +# Returns +# ------- +# interpolator : :class:`scipy.interpolate.interp1d` +# The interpolator function. +# """ +# return inter.interp1d( +# x=self.x, +# y=y, +# kind=self.interpolation_kind, +# fill_value="extrapolate", +# assume_sorted=True, +# ) +# +# def _solve_ode(self, rhs: Callable) -> ndarray: +# """Solves an ODE with the given right-hand side.""" +# sol = integ.solve_ivp( +# fun=rhs, +# y0=self.y_0, +# t_span=(self.x[0], self.x[-1]), +# t_eval=self.x, +# method=self.ode_method, +# atol=self.ode_atol, +# rtol=self.ode_rtol, +# ) +# +# if not sol.success: +# raise exceptions.ODESolutionFailed(f"Error while trying to solve ODE: {sol.status}") +# +# return sol.y diff --git a/idesolver/py.typed b/idesolver/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/idesolver/version.py b/idesolver/version.py deleted file mode 100644 index e4af021..0000000 --- a/idesolver/version.py +++ /dev/null @@ -1,7 +0,0 @@ -try: - from importlib import metadata -except ImportError: - # Running on pre-3.8 Python; use importlib-metadata package - import importlib_metadata as metadata - -__version__ = metadata.version("idesolver") diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3776962 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,96 @@ +site_name: IDESolver + +repo_url: https://github.com/JoshKarpel/idesolver +edit_uri: edit/main/docs/ + +extra_css: + - assets/style.css + +extra_javascript: + - assets/mathjax.js + - https://polyfill.io/v3/polyfill.min.js?features=es6 + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + +watch: + - idesolver/ + +theme: + name: material + favicon: assets/favicon.png + icon: + logo: fontawesome/solid/calculator + palette: + - scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + media: "(prefers-color-scheme: light)" + - scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + media: "(prefers-color-scheme: dark)" + features: + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.indexes + - toc.follow + - content.code.annotate + - content.code.copy + +plugins: + - tags + - search + - mkdocstrings: + handlers: + python: + options: + show_root_heading: true + heading_level: 3 + docstring_section_style: spacy + merge_init_into_class: true + show_if_no_docstring: false + show_source: false + members_order: source + import: + - https://docs.python.org/3/objects.inv + - https://numpy.org/doc/stable/objects.inv + - https://docs.scipy.org/doc/scipy/objects.inv + - https://matplotlib.org/stable/objects.inv + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets: + base_path: ['docs'] + check_paths: true + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - attr_list + - def_list + - md_in_html + - pymdownx.tasklist: + custom_checkbox: true + - tables + - pymdownx.arithmatex: + generic: true + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/JoshKarpel/idesolver + name: IDESolver on GitHub + +nav: + - Introduction: index.md + - quickstart.md + - manual.md + - parallelization.md + - api.md + - contributing.md + - changelog.md diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..671964f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1639 @@ +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. + +[[package]] +name = "attrs" +version = "22.2.0" +description = "Classes Without Boilerplate" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, + {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, +] + +[package.extras] +cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] +tests = ["attrs[tests-no-zope]", "zope.interface"] +tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] + +[[package]] +name = "certifi" +version = "2022.12.7" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, +] + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" +optional = false +python-versions = ">=3.5.0" +files = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "contourpy" +version = "1.0.7" +description = "Python library for calculating contours of 2D quadrilateral grids" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"}, + {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"}, + {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"}, + {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"}, + {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"}, + {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"}, + {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"}, + {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"}, + {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"}, + {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"}, + {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"}, + {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"}, + {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"}, + {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"}, + {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"}, + {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"}, + {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"}, + {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"}, + {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"}, + {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"}, + {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"}, + {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"}, + {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"}, + {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"}, +] + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "chromedriver", "selenium"] +docs = ["furo", "sphinx-copybutton"] +mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"] +test = ["Pillow", "matplotlib", "pytest"] +test-no-images = ["pytest"] + +[[package]] +name = "coverage" +version = "7.2.2" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c90e73bdecb7b0d1cea65a08cb41e9d672ac6d7995603d6465ed4914b98b9ad7"}, + {file = "coverage-7.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2926b8abedf750c2ecf5035c07515770944acf02e1c46ab08f6348d24c5f94d"}, + {file = "coverage-7.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57b77b9099f172804e695a40ebaa374f79e4fb8b92f3e167f66facbf92e8e7f5"}, + {file = "coverage-7.2.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:efe1c0adad110bf0ad7fb59f833880e489a61e39d699d37249bdf42f80590169"}, + {file = "coverage-7.2.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2199988e0bc8325d941b209f4fd1c6fa007024b1442c5576f1a32ca2e48941e6"}, + {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:81f63e0fb74effd5be736cfe07d710307cc0a3ccb8f4741f7f053c057615a137"}, + {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:186e0fc9cf497365036d51d4d2ab76113fb74f729bd25da0975daab2e107fd90"}, + {file = "coverage-7.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:420f94a35e3e00a2b43ad5740f935358e24478354ce41c99407cddd283be00d2"}, + {file = "coverage-7.2.2-cp310-cp310-win32.whl", hash = "sha256:38004671848b5745bb05d4d621526fca30cee164db42a1f185615f39dc997292"}, + {file = "coverage-7.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:0ce383d5f56d0729d2dd40e53fe3afeb8f2237244b0975e1427bfb2cf0d32bab"}, + {file = "coverage-7.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3eb55b7b26389dd4f8ae911ba9bc8c027411163839dea4c8b8be54c4ee9ae10b"}, + {file = "coverage-7.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2b96123a453a2d7f3995ddb9f28d01fd112319a7a4d5ca99796a7ff43f02af5"}, + {file = "coverage-7.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:299bc75cb2a41e6741b5e470b8c9fb78d931edbd0cd009c58e5c84de57c06731"}, + {file = "coverage-7.2.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e1df45c23d4230e3d56d04414f9057eba501f78db60d4eeecfcb940501b08fd"}, + {file = "coverage-7.2.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:006ed5582e9cbc8115d2e22d6d2144a0725db542f654d9d4fda86793832f873d"}, + {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d683d230b5774816e7d784d7ed8444f2a40e7a450e5720d58af593cb0b94a212"}, + {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8efb48fa743d1c1a65ee8787b5b552681610f06c40a40b7ef94a5b517d885c54"}, + {file = "coverage-7.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c752d5264053a7cf2fe81c9e14f8a4fb261370a7bb344c2a011836a96fb3f57"}, + {file = "coverage-7.2.2-cp311-cp311-win32.whl", hash = "sha256:55272f33da9a5d7cccd3774aeca7a01e500a614eaea2a77091e9be000ecd401d"}, + {file = "coverage-7.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:92ebc1619650409da324d001b3a36f14f63644c7f0a588e331f3b0f67491f512"}, + {file = "coverage-7.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5afdad4cc4cc199fdf3e18088812edcf8f4c5a3c8e6cb69127513ad4cb7471a9"}, + {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0484d9dd1e6f481b24070c87561c8d7151bdd8b044c93ac99faafd01f695c78e"}, + {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d530191aa9c66ab4f190be8ac8cc7cfd8f4f3217da379606f3dd4e3d83feba69"}, + {file = "coverage-7.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac0f522c3b6109c4b764ffec71bf04ebc0523e926ca7cbe6c5ac88f84faced0"}, + {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ba279aae162b20444881fc3ed4e4f934c1cf8620f3dab3b531480cf602c76b7f"}, + {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:53d0fd4c17175aded9c633e319360d41a1f3c6e352ba94edcb0fa5167e2bad67"}, + {file = "coverage-7.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c99cb7c26a3039a8a4ee3ca1efdde471e61b4837108847fb7d5be7789ed8fd9"}, + {file = "coverage-7.2.2-cp37-cp37m-win32.whl", hash = "sha256:5cc0783844c84af2522e3a99b9b761a979a3ef10fb87fc4048d1ee174e18a7d8"}, + {file = "coverage-7.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:817295f06eacdc8623dc4df7d8b49cea65925030d4e1e2a7c7218380c0072c25"}, + {file = "coverage-7.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6146910231ece63facfc5984234ad1b06a36cecc9fd0c028e59ac7c9b18c38c6"}, + {file = "coverage-7.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:387fb46cb8e53ba7304d80aadca5dca84a2fbf6fe3faf6951d8cf2d46485d1e5"}, + {file = "coverage-7.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:046936ab032a2810dcaafd39cc4ef6dd295df1a7cbead08fe996d4765fca9fe4"}, + {file = "coverage-7.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e627dee428a176ffb13697a2c4318d3f60b2ccdde3acdc9b3f304206ec130ccd"}, + {file = "coverage-7.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4fa54fb483decc45f94011898727802309a109d89446a3c76387d016057d2c84"}, + {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3668291b50b69a0c1ef9f462c7df2c235da3c4073f49543b01e7eb1dee7dd540"}, + {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7c20b731211261dc9739bbe080c579a1835b0c2d9b274e5fcd903c3a7821cf88"}, + {file = "coverage-7.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5764e1f7471cb8f64b8cda0554f3d4c4085ae4b417bfeab236799863703e5de2"}, + {file = "coverage-7.2.2-cp38-cp38-win32.whl", hash = "sha256:4f01911c010122f49a3e9bdc730eccc66f9b72bd410a3a9d3cb8448bb50d65d3"}, + {file = "coverage-7.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:c448b5c9e3df5448a362208b8d4b9ed85305528313fca1b479f14f9fe0d873b8"}, + {file = "coverage-7.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfe7085783cda55e53510482fa7b5efc761fad1abe4d653b32710eb548ebdd2d"}, + {file = "coverage-7.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9d22e94e6dc86de981b1b684b342bec5e331401599ce652900ec59db52940005"}, + {file = "coverage-7.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:507e4720791977934bba016101579b8c500fb21c5fa3cd4cf256477331ddd988"}, + {file = "coverage-7.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc4803779f0e4b06a2361f666e76f5c2e3715e8e379889d02251ec911befd149"}, + {file = "coverage-7.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db8c2c5ace167fd25ab5dd732714c51d4633f58bac21fb0ff63b0349f62755a8"}, + {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4f68ee32d7c4164f1e2c8797535a6d0a3733355f5861e0f667e37df2d4b07140"}, + {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d52f0a114b6a58305b11a5cdecd42b2e7f1ec77eb20e2b33969d702feafdd016"}, + {file = "coverage-7.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:797aad79e7b6182cb49c08cc5d2f7aa7b2128133b0926060d0a8889ac43843be"}, + {file = "coverage-7.2.2-cp39-cp39-win32.whl", hash = "sha256:db45eec1dfccdadb179b0f9ca616872c6f700d23945ecc8f21bb105d74b1c5fc"}, + {file = "coverage-7.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:8dbe2647bf58d2c5a6c5bcc685f23b5f371909a5624e9f5cd51436d6a9f6c6ef"}, + {file = "coverage-7.2.2-pp37.pp38.pp39-none-any.whl", hash = "sha256:872d6ce1f5be73f05bea4df498c140b9e7ee5418bfa2cc8204e7f9b817caa968"}, + {file = "coverage-7.2.2.tar.gz", hash = "sha256:36dd42da34fe94ed98c39887b86db9d06777b1c8f860520e21126a75507024f2"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.1.1" +description = "Backport of PEP 654 (exception groups)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"}, + {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "filelock" +version = "3.10.0" +description = "A platform independent file lock." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.10.0-py3-none-any.whl", hash = "sha256:e90b34656470756edf8b19656785c5fea73afa1953f3e1b0d645cef11cab3182"}, + {file = "filelock-3.10.0.tar.gz", hash = "sha256:3199fd0d3faea8b911be52b663dfccceb84c95949dd13179aa21436d1a79c4ce"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.1)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "fonttools" +version = "4.39.2" +description = "Tools to manipulate font files" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fonttools-4.39.2-py3-none-any.whl", hash = "sha256:85245aa2fd4cf502a643c9a9a2b5a393703e150a6eaacc3e0e84bb448053f061"}, + {file = "fonttools-4.39.2.zip", hash = "sha256:e2d9f10337c9e3b17f9bce17a60a16a885a7d23b59b7f45ce07ea643e5580439"}, +] + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=15.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + +[[package]] +name = "griffe" +version = "0.25.5" +description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "griffe-0.25.5-py3-none-any.whl", hash = "sha256:1fb9edff48e66d4873014a2ebf21aca5f271d0006a4c937826e3cf592ffb3706"}, + {file = "griffe-0.25.5.tar.gz", hash = "sha256:11ea3403ef0560a1cbcf7f302eb5d21cf4c1d8ed3f8a16a75aa9f6f458caf3f1"}, +] + +[package.dependencies] +colorama = ">=0.4" + +[package.extras] +async = ["aiofiles (>=0.7,<1.0)"] + +[[package]] +name = "hypothesis" +version = "6.70.0" +description = "A library for property-based testing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "hypothesis-6.70.0-py3-none-any.whl", hash = "sha256:be395f71d6337a5e8ed2f695c568360a686056c3b00c98bd818874c674b24586"}, + {file = "hypothesis-6.70.0.tar.gz", hash = "sha256:f5cae09417d0ffc7711f602cdcfa3b7baf344597a672a84658186605b04f4a4f"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=3.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=1.0)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.7)"] +cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=3.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark (>=0.10.1)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=1.0)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.7)"] + +[[package]] +name = "identify" +version = "2.5.21" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.21-py2.py3-none-any.whl", hash = "sha256:69edcaffa8e91ae0f77d397af60f148b6b45a8044b2cc6d99cafa5b04793ff00"}, + {file = "identify-2.5.21.tar.gz", hash = "sha256:7671a05ef9cfaf8ff63b15d45a91a1147a03aaccb2976d4e9bd047cbbc508471"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.1.0" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, + {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +description = "Read resources from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, + {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] + +[[package]] +name = "markdown" +version = "3.3.7" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markupsafe" +version = "2.1.2" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, + {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, + {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, + {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, + {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, + {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, + {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, +] + +[[package]] +name = "matplotlib" +version = "3.7.1" +description = "Python plotting package" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"}, + {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"}, + {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"}, + {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"}, + {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"}, + {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"}, + {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"}, + {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"}, + {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"}, + {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"}, + {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"}, + {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"}, + {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"}, + {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"}, + {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"}, + {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"}, + {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"}, + {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"}, + {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"}, + {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"}, +] + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""} +kiwisolver = ">=1.0.1" +numpy = ">=1.20" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.3.1" +python-dateutil = ">=2.7" + +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.4.2" +description = "Project documentation with Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, + {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.2.1,<3.4" +mergedeep = ">=1.3.4" +packaging = ">=20.5" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-autorefs" +version = "0.4.1" +description = "Automatically link across pages in MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, + {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, +] + +[package.dependencies] +Markdown = ">=3.3" +mkdocs = ">=1.1" + +[[package]] +name = "mkdocs-material" +version = "9.1.3" +description = "Documentation that simply works" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material-9.1.3-py3-none-any.whl", hash = "sha256:a8d14d03569008afb0f5a5785c253249b5ff038e3a5509f96a393b8596bf5062"}, + {file = "mkdocs_material-9.1.3.tar.gz", hash = "sha256:0be1b5d76c00efc9b2ecbd2d71014be950351e710f5947f276264878afc82ca0"}, +] + +[package.dependencies] +colorama = ">=0.4" +jinja2 = ">=3.0" +markdown = ">=3.2" +mkdocs = ">=1.4.2" +mkdocs-material-extensions = ">=1.1" +pygments = ">=2.14" +pymdown-extensions = ">=9.9.1" +regex = ">=2022.4.24" +requests = ">=2.26" + +[[package]] +name = "mkdocs-material-extensions" +version = "1.1.1" +description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, +] + +[[package]] +name = "mkdocstrings" +version = "0.20.0" +description = "Automatic documentation from sources, for MkDocs." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"}, + {file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"}, +] + +[package.dependencies] +Jinja2 = ">=2.11.1" +Markdown = ">=3.3" +MarkupSafe = ">=1.1" +mkdocs = ">=1.2" +mkdocs-autorefs = ">=0.3.1" +mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""} +pymdown-extensions = ">=6.3" + +[package.extras] +crystal = ["mkdocstrings-crystal (>=0.3.4)"] +python = ["mkdocstrings-python (>=0.5.2)"] +python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] + +[[package]] +name = "mkdocstrings-python" +version = "0.8.3" +description = "A Python handler for mkdocstrings." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, + {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, +] + +[package.dependencies] +griffe = ">=0.24" +mkdocstrings = ">=0.19" + +[[package]] +name = "mypy" +version = "1.1.1" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39c7119335be05630611ee798cc982623b9e8f0cff04a0b48dfc26100e0b97af"}, + {file = "mypy-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:61bf08362e93b6b12fad3eab68c4ea903a077b87c90ac06c11e3d7a09b56b9c1"}, + {file = "mypy-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbb19c9f662e41e474e0cff502b7064a7edc6764f5262b6cd91d698163196799"}, + {file = "mypy-1.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:315ac73cc1cce4771c27d426b7ea558fb4e2836f89cb0296cbe056894e3a1f78"}, + {file = "mypy-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:5cb14ff9919b7df3538590fc4d4c49a0f84392237cbf5f7a816b4161c061829e"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26cdd6a22b9b40b2fd71881a8a4f34b4d7914c679f154f43385ca878a8297389"}, + {file = "mypy-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5b5f81b40d94c785f288948c16e1f2da37203c6006546c5d947aab6f90aefef2"}, + {file = "mypy-1.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b437be1c02712a605591e1ed1d858aba681757a1e55fe678a15c2244cd68a5"}, + {file = "mypy-1.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d809f88734f44a0d44959d795b1e6f64b2bbe0ea4d9cc4776aa588bb4229fc1c"}, + {file = "mypy-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:a380c041db500e1410bb5b16b3c1c35e61e773a5c3517926b81dfdab7582be54"}, + {file = "mypy-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b7c7b708fe9a871a96626d61912e3f4ddd365bf7f39128362bc50cbd74a634d5"}, + {file = "mypy-1.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c10fa12df1232c936830839e2e935d090fc9ee315744ac33b8a32216b93707"}, + {file = "mypy-1.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0a28a76785bf57655a8ea5eb0540a15b0e781c807b5aa798bd463779988fa1d5"}, + {file = "mypy-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:ef6a01e563ec6a4940784c574d33f6ac1943864634517984471642908b30b6f7"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d64c28e03ce40d5303450f547e07418c64c241669ab20610f273c9e6290b4b0b"}, + {file = "mypy-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:64cc3afb3e9e71a79d06e3ed24bb508a6d66f782aff7e56f628bf35ba2e0ba51"}, + {file = "mypy-1.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce61663faf7a8e5ec6f456857bfbcec2901fbdb3ad958b778403f63b9e606a1b"}, + {file = "mypy-1.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2b0c373d071593deefbcdd87ec8db91ea13bd8f1328d44947e88beae21e8d5e9"}, + {file = "mypy-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:2888ce4fe5aae5a673386fa232473014056967f3904f5abfcf6367b5af1f612a"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19ba15f9627a5723e522d007fe708007bae52b93faab00f95d72f03e1afa9598"}, + {file = "mypy-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:59bbd71e5c58eed2e992ce6523180e03c221dcd92b52f0e792f291d67b15a71c"}, + {file = "mypy-1.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9401e33814cec6aec8c03a9548e9385e0e228fc1b8b0a37b9ea21038e64cdd8a"}, + {file = "mypy-1.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b398d8b1f4fba0e3c6463e02f8ad3346f71956b92287af22c9b12c3ec965a9f"}, + {file = "mypy-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:69b35d1dcb5707382810765ed34da9db47e7f95b3528334a3c999b0c90fe523f"}, + {file = "mypy-1.1.1-py3-none-any.whl", hash = "sha256:4e4e8b362cdf99ba00c2b218036002bdcdf1e0de085cdb296a49df03fb31dfc4"}, + {file = "mypy-1.1.1.tar.gz", hash = "sha256:ae9ceae0f5b9059f33dbc62dea087e942c0ccab4b7a003719cb70f9b8abfa32f"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "numpy" +version = "1.24.2" +description = "Fundamental package for array computing in Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eef70b4fc1e872ebddc38cddacc87c19a3709c0e3e5d20bf3954c147b1dd941d"}, + {file = "numpy-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d2859428712785e8a8b7d2b3ef0a1d1565892367b32f915c4a4df44d0e64f5"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6524630f71631be2dabe0c541e7675db82651eb998496bbe16bc4f77f0772253"}, + {file = "numpy-1.24.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a51725a815a6188c662fb66fb32077709a9ca38053f0274640293a14fdd22978"}, + {file = "numpy-1.24.2-cp310-cp310-win32.whl", hash = "sha256:2620e8592136e073bd12ee4536149380695fbe9ebeae845b81237f986479ffc9"}, + {file = "numpy-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:97cf27e51fa078078c649a51d7ade3c92d9e709ba2bfb97493007103c741f1d0"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7de8fdde0003f4294655aa5d5f0a89c26b9f22c0a58790c38fae1ed392d44a5a"}, + {file = "numpy-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4173bde9fa2a005c2c6e2ea8ac1618e2ed2c1c6ec8a7657237854d42094123a0"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cecaed30dc14123020f77b03601559fff3e6cd0c048f8b5289f4eeabb0eb281"}, + {file = "numpy-1.24.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a23f8440561a633204a67fb44617ce2a299beecf3295f0d13c495518908e910"}, + {file = "numpy-1.24.2-cp311-cp311-win32.whl", hash = "sha256:e428c4fbfa085f947b536706a2fc349245d7baa8334f0c5723c56a10595f9b95"}, + {file = "numpy-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:557d42778a6869c2162deb40ad82612645e21d79e11c1dc62c6e82a2220ffb04"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d0a2db9d20117bf523dde15858398e7c0858aadca7c0f088ac0d6edd360e9ad2"}, + {file = "numpy-1.24.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c72a6b2f4af1adfe193f7beb91ddf708ff867a3f977ef2ec53c0ffb8283ab9f5"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29e6bd0ec49a44d7690ecb623a8eac5ab8a923bce0bea6293953992edf3a76a"}, + {file = "numpy-1.24.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2eabd64ddb96a1239791da78fa5f4e1693ae2dadc82a76bc76a14cbb2b966e96"}, + {file = "numpy-1.24.2-cp38-cp38-win32.whl", hash = "sha256:e3ab5d32784e843fc0dd3ab6dcafc67ef806e6b6828dc6af2f689be0eb4d781d"}, + {file = "numpy-1.24.2-cp38-cp38-win_amd64.whl", hash = "sha256:76807b4063f0002c8532cfeac47a3068a69561e9c8715efdad3c642eb27c0756"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4199e7cfc307a778f72d293372736223e39ec9ac096ff0a2e64853b866a8e18a"}, + {file = "numpy-1.24.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:adbdce121896fd3a17a77ab0b0b5eedf05a9834a18699db6829a64e1dfccca7f"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:889b2cc88b837d86eda1b17008ebeb679d82875022200c6e8e4ce6cf549b7acb"}, + {file = "numpy-1.24.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f64bb98ac59b3ea3bf74b02f13836eb2e24e48e0ab0145bbda646295769bd780"}, + {file = "numpy-1.24.2-cp39-cp39-win32.whl", hash = "sha256:63e45511ee4d9d976637d11e6c9864eae50e12dc9598f531c035265991910468"}, + {file = "numpy-1.24.2-cp39-cp39-win_amd64.whl", hash = "sha256:a77d3e1163a7770164404607b7ba3967fb49b24782a6ef85d9b5f54126cc39e5"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:92011118955724465fb6853def593cf397b4a1367495e0b59a7e69d40c4eb71d"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9006288bcf4895917d02583cf3411f98631275bc67cce355a7f39f8c14338fa"}, + {file = "numpy-1.24.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:150947adbdfeceec4e5926d956a06865c1c690f2fd902efede4ca6fe2e657c3f"}, + {file = "numpy-1.24.2.tar.gz", hash = "sha256:003a9f530e880cb2cd177cba1af7220b9aa42def9c4afc2a2fc3ee6be7eb2b22"}, +] + +[[package]] +name = "packaging" +version = "23.0" +description = "Core utilities for Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, + {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, +] + +[[package]] +name = "pillow" +version = "9.4.0" +description = "Python Imaging Library (Fork)" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"}, + {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"}, + {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"}, + {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"}, + {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"}, + {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"}, + {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"}, + {file = "Pillow-9.4.0-2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:9d9a62576b68cd90f7075876f4e8444487db5eeea0e4df3ba298ee38a8d067b0"}, + {file = "Pillow-9.4.0-2-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:87708d78a14d56a990fbf4f9cb350b7d89ee8988705e58e39bdf4d82c149210f"}, + {file = "Pillow-9.4.0-2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8a2b5874d17e72dfb80d917213abd55d7e1ed2479f38f001f264f7ce7bae757c"}, + {file = "Pillow-9.4.0-2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:83125753a60cfc8c412de5896d10a0a405e0bd88d0470ad82e0869ddf0cb3848"}, + {file = "Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9e5f94742033898bfe84c93c831a6f552bb629448d4072dd312306bab3bd96f1"}, + {file = "Pillow-9.4.0-2-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:013016af6b3a12a2f40b704677f8b51f72cb007dac785a9933d5c86a72a7fe33"}, + {file = "Pillow-9.4.0-2-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:99d92d148dd03fd19d16175b6d355cc1b01faf80dae93c6c3eb4163709edc0a9"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"}, + {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3049a10261d7f2b6514d35bbb7a4dfc3ece4c4de14ef5876c4b7a23a0e566d"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a8df99701f9095bea8a6c4b3197da105df6f74e6176c5b410bc2df2fd29a57"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:94cdff45173b1919350601f82d61365e792895e3c3a3443cf99819e6fbf717a5"}, + {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ed3e4b4e1e6de75fdc16d3259098de7c6571b1a6cc863b1a49e7d3d53e036070"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5b2f8a31bd43e0f18172d8ac82347c8f37ef3e0b414431157718aa234991b28"}, + {file = "Pillow-9.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:09b89ddc95c248ee788328528e6a2996e09eaccddeeb82a5356e92645733be35"}, + {file = "Pillow-9.4.0-cp310-cp310-win32.whl", hash = "sha256:f09598b416ba39a8f489c124447b007fe865f786a89dbfa48bb5cf395693132a"}, + {file = "Pillow-9.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:f6e78171be3fb7941f9910ea15b4b14ec27725865a73c15277bc39f5ca4f8391"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:3fa1284762aacca6dc97474ee9c16f83990b8eeb6697f2ba17140d54b453e133"}, + {file = "Pillow-9.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eaef5d2de3c7e9b21f1e762f289d17b726c2239a42b11e25446abf82b26ac132"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4dfdae195335abb4e89cc9762b2edc524f3c6e80d647a9a81bf81e17e3fb6f0"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6abfb51a82e919e3933eb137e17c4ae9c0475a25508ea88993bb59faf82f3b35"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451f10ef963918e65b8869e17d67db5e2f4ab40e716ee6ce7129b0cde2876eab"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6663977496d616b618b6cfa43ec86e479ee62b942e1da76a2c3daa1c75933ef4"}, + {file = "Pillow-9.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60e7da3a3ad1812c128750fc1bc14a7ceeb8d29f77e0a2356a8fb2aa8925287d"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:19005a8e58b7c1796bc0167862b1f54a64d3b44ee5d48152b06bb861458bc0f8"}, + {file = "Pillow-9.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f715c32e774a60a337b2bb8ad9839b4abf75b267a0f18806f6f4f5f1688c4b5a"}, + {file = "Pillow-9.4.0-cp311-cp311-win32.whl", hash = "sha256:b222090c455d6d1a64e6b7bb5f4035c4dff479e22455c9eaa1bdd4c75b52c80c"}, + {file = "Pillow-9.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba6612b6548220ff5e9df85261bddc811a057b0b465a1226b39bfb8550616aee"}, + {file = "Pillow-9.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5f532a2ad4d174eb73494e7397988e22bf427f91acc8e6ebf5bb10597b49c493"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dd5a9c3091a0f414a963d427f920368e2b6a4c2f7527fdd82cde8ef0bc7a327"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef21af928e807f10bf4141cad4746eee692a0dd3ff56cfb25fce076ec3cc8abe"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:847b114580c5cc9ebaf216dd8c8dbc6b00a3b7ab0131e173d7120e6deade1f57"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:653d7fb2df65efefbcbf81ef5fe5e5be931f1ee4332c2893ca638c9b11a409c4"}, + {file = "Pillow-9.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:46f39cab8bbf4a384ba7cb0bc8bae7b7062b6a11cfac1ca4bc144dea90d4a9f5"}, + {file = "Pillow-9.4.0-cp37-cp37m-win32.whl", hash = "sha256:7ac7594397698f77bce84382929747130765f66406dc2cd8b4ab4da68ade4c6e"}, + {file = "Pillow-9.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:46c259e87199041583658457372a183636ae8cd56dbf3f0755e0f376a7f9d0e6"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:0e51f608da093e5d9038c592b5b575cadc12fd748af1479b5e858045fff955a9"}, + {file = "Pillow-9.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:765cb54c0b8724a7c12c55146ae4647e0274a839fb6de7bcba841e04298e1011"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:519e14e2c49fcf7616d6d2cfc5c70adae95682ae20f0395e9280db85e8d6c4df"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d197df5489004db87d90b918033edbeee0bd6df3848a204bca3ff0a903bef837"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0845adc64fe9886db00f5ab68c4a8cd933ab749a87747555cec1c95acea64b0b"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:e1339790c083c5a4de48f688b4841f18df839eb3c9584a770cbd818b33e26d5d"}, + {file = "Pillow-9.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:a96e6e23f2b79433390273eaf8cc94fec9c6370842e577ab10dabdcc7ea0a66b"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7cfc287da09f9d2a7ec146ee4d72d6ea1342e770d975e49a8621bf54eaa8f30f"}, + {file = "Pillow-9.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d7081c084ceb58278dd3cf81f836bc818978c0ccc770cbbb202125ddabec6628"}, + {file = "Pillow-9.4.0-cp38-cp38-win32.whl", hash = "sha256:df41112ccce5d47770a0c13651479fbcd8793f34232a2dd9faeccb75eb5d0d0d"}, + {file = "Pillow-9.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a21222644ab69ddd9967cfe6f2bb420b460dae4289c9d40ff9a4896e7c35c9a"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0f3269304c1a7ce82f1759c12ce731ef9b6e95b6df829dccd9fe42912cc48569"}, + {file = "Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb362e3b0976dc994857391b776ddaa8c13c28a16f80ac6522c23d5257156bed"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2e0f87144fcbbe54297cae708c5e7f9da21a4646523456b00cc956bd4c65815"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28676836c7796805914b76b1837a40f76827ee0d5398f72f7dcc634bae7c6264"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0884ba7b515163a1a05440a138adeb722b8a6ae2c2b33aea93ea3118dd3a899e"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:53dcb50fbdc3fb2c55431a9b30caeb2f7027fcd2aeb501459464f0214200a503"}, + {file = "Pillow-9.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:e8c5cf126889a4de385c02a2c3d3aba4b00f70234bfddae82a5eaa3ee6d5e3e6"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6c6b1389ed66cdd174d040105123a5a1bc91d0aa7059c7261d20e583b6d8cbd2"}, + {file = "Pillow-9.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0dd4c681b82214b36273c18ca7ee87065a50e013112eea7d78c7a1b89a739153"}, + {file = "Pillow-9.4.0-cp39-cp39-win32.whl", hash = "sha256:6d9dfb9959a3b0039ee06c1a1a90dc23bac3b430842dcb97908ddde05870601c"}, + {file = "Pillow-9.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:54614444887e0d3043557d9dbc697dbb16cfb5a35d672b7a0fcc1ed0cf1c600b"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b9b752ab91e78234941e44abdecc07f1f0d8f51fb62941d32995b8161f68cfe5"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d3b56206244dc8711f7e8b7d6cad4663917cd5b2d950799425076681e8766286"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aabdab8ec1e7ca7f1434d042bf8b1e92056245fb179790dc97ed040361f16bfd"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db74f5562c09953b2c5f8ec4b7dfd3f5421f31811e97d1dbc0a7c93d6e3a24df"}, + {file = "Pillow-9.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e9d7747847c53a16a729b6ee5e737cf170f7a16611c143d95aa60a109a59c336"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b52ff4f4e002f828ea6483faf4c4e8deea8d743cf801b74910243c58acc6eda3"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:575d8912dca808edd9acd6f7795199332696d3469665ef26163cd090fa1f8bfa"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c4ed2ff6760e98d262e0cc9c9a7f7b8a9f61aa4d47c58835cdaf7b0b8811bb"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e621b0246192d3b9cb1dc62c78cfa4c6f6d2ddc0ec207d43c0dedecb914f152a"}, + {file = "Pillow-9.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8f127e7b028900421cad64f51f75c051b628db17fb00e099eb148761eed598c9"}, + {file = "Pillow-9.4.0.tar.gz", hash = "sha256:a1c2d7780448eb93fbcc3789bf3916aa5720d942e37945f4056680317f1cd23e"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "platformdirs" +version = "3.1.1" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.1.1-py3-none-any.whl", hash = "sha256:e5986afb596e4bb5bde29a79ac9061aa955b94fca2399b7aaac4090860920dd8"}, + {file = "platformdirs-3.1.1.tar.gz", hash = "sha256:024996549ee88ec1a9aa99ff7f8fc819bb59e2c3477b410d90a16d32d6e707aa"}, +] + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.2.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.2.0-py2.py3-none-any.whl", hash = "sha256:f712d3688102e13c8e66b7d7dbd8934a6dda157e58635d89f7d6fecdca39ce8a"}, + {file = "pre_commit-3.2.0.tar.gz", hash = "sha256:818f0d998059934d0f81bb3667e3ccdc32da6ed7ccaac33e43dc231561ddaaa9"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pymdown-extensions" +version = "9.10" +description = "Extension pack for Python Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pymdown_extensions-9.10-py3-none-any.whl", hash = "sha256:31eaa76ce6f96aabfcea98787c2fff2c5c0611b20a53a94213970cfbf05f02b8"}, + {file = "pymdown_extensions-9.10.tar.gz", hash = "sha256:562c38eee4ce3f101ce631b804bfc2177a8a76c7e4dc908871fb6741a90257a7"}, +] + +[package.dependencies] +markdown = ">=3.2" +pyyaml = "*" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "dev" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pytest" +version = "7.2.2" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, + {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, +] + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "4.0.0" +description = "Pytest plugin for measuring coverage." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pytest-cov-4.0.0.tar.gz", hash = "sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"}, + {file = "pytest_cov-4.0.0-py3-none-any.whl", hash = "sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.10.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.10.0.tar.gz", hash = "sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"}, + {file = "pytest_mock-3.10.0-py3-none-any.whl", hash = "sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "pytest-xdist" +version = "3.2.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-xdist-3.2.1.tar.gz", hash = "sha256:1849bd98d8b242b948e472db7478e090bf3361912a8fed87992ed94085f54727"}, + {file = "pytest_xdist-3.2.1-py3-none-any.whl", hash = "sha256:37290d161638a20b672401deef1cba812d110ac27e35d213f091d15b8beb40c9"}, +] + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] + +[[package]] +name = "pyyaml-env-tag" +version = "0.1" +description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "regex" +version = "2022.10.31" +description = "Alternative regular expression module, to replace re." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, + {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66"}, + {file = "regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1"}, + {file = "regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7"}, + {file = "regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af"}, + {file = "regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61"}, + {file = "regex-2022.10.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4"}, + {file = "regex-2022.10.31-cp36-cp36m-win32.whl", hash = "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066"}, + {file = "regex-2022.10.31-cp36-cp36m-win_amd64.whl", hash = "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6"}, + {file = "regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95"}, + {file = "regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394"}, + {file = "regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c"}, + {file = "regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc"}, + {file = "regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, + {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, + {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, + {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, +] + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "scipy" +version = "1.9.3" +description = "Fundamental algorithms for scientific computing in Python" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"}, + {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"}, + {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"}, + {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"}, + {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"}, + {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"}, + {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"}, + {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"}, + {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"}, + {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"}, + {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"}, + {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"}, + {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"}, + {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"}, + {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"}, + {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"}, + {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"}, + {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"}, + {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"}, + {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"}, + {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"}, +] + +[package.dependencies] +numpy = ">=1.18.5,<1.26.0" + +[package.extras] +dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] +doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] +test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setuptools" +version = "67.6.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-67.6.0-py3-none-any.whl", hash = "sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2"}, + {file = "setuptools-67.6.0.tar.gz", hash = "sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "typing-extensions" +version = "4.5.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"}, + {file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"}, +] + +[[package]] +name = "urllib3" +version = "1.26.15" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "urllib3-1.26.15-py2.py3-none-any.whl", hash = "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"}, + {file = "urllib3-1.26.15.tar.gz", hash = "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "virtualenv" +version = "20.21.0" +description = "Virtual Python Environment builder" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.21.0-py3-none-any.whl", hash = "sha256:31712f8f2a17bd06234fa97fdf19609e789dd4e3e4bf108c3da71d710651adbc"}, + {file = "virtualenv-20.21.0.tar.gz", hash = "sha256:f50e3e60f990a0757c9b68333c9fdaa72d7188caa417f96af9e52407831a3b68"}, +] + +[package.dependencies] +distlib = ">=0.3.6,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<4" + +[package.extras] +docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "watchdog" +version = "3.0.0" +description = "Filesystem events monitoring" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:336adfc6f5cc4e037d52db31194f7581ff744b67382eb6021c868322e32eef41"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a70a8dcde91be523c35b2bf96196edc5730edb347e374c7de7cd20c43ed95397"}, + {file = "watchdog-3.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:adfdeab2da79ea2f76f87eb42a3ab1966a5313e5a69a0213a3cc06ef692b0e96"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b57a1e730af3156d13b7fdddfc23dea6487fceca29fc75c5a868beed29177ae"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7ade88d0d778b1b222adebcc0927428f883db07017618a5e684fd03b83342bd9"}, + {file = "watchdog-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7e447d172af52ad204d19982739aa2346245cc5ba6f579d16dac4bfec226d2e7"}, + {file = "watchdog-3.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9fac43a7466eb73e64a9940ac9ed6369baa39b3bf221ae23493a9ec4d0022674"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8ae9cda41fa114e28faf86cb137d751a17ffd0316d1c34ccf2235e8a84365c7f"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f70b4aa53bd743729c7475d7ec41093a580528b100e9a8c5b5efe8899592fc"}, + {file = "watchdog-3.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4f94069eb16657d2c6faada4624c39464f65c05606af50bb7902e036e3219be3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7c5f84b5194c24dd573fa6472685b2a27cc5a17fe5f7b6fd40345378ca6812e3"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3aa7f6a12e831ddfe78cdd4f8996af9cf334fd6346531b16cec61c3b3c0d8da0"}, + {file = "watchdog-3.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:233b5817932685d39a7896b1090353fc8efc1ef99c9c054e46c8002561252fb8"}, + {file = "watchdog-3.0.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:13bbbb462ee42ec3c5723e1205be8ced776f05b100e4737518c67c8325cf6100"}, + {file = "watchdog-3.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8f3ceecd20d71067c7fd4c9e832d4e22584318983cabc013dbf3f70ea95de346"}, + {file = "watchdog-3.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c9d8c8ec7efb887333cf71e328e39cffbf771d8f8f95d308ea4125bf5f90ba64"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0e06ab8858a76e1219e68c7573dfeba9dd1c0219476c5a44d5333b01d7e1743a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:d00e6be486affb5781468457b21a6cbe848c33ef43f9ea4a73b4882e5f188a44"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:c07253088265c363d1ddf4b3cdb808d59a0468ecd017770ed716991620b8f77a"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:5113334cf8cf0ac8cd45e1f8309a603291b614191c9add34d33075727a967709"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:51f90f73b4697bac9c9a78394c3acbbd331ccd3655c11be1a15ae6fe289a8c83"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:ba07e92756c97e3aca0912b5cbc4e5ad802f4557212788e72a72a47ff376950d"}, + {file = "watchdog-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d429c2430c93b7903914e4db9a966c7f2b068dd2ebdd2fa9b9ce094c7d459f33"}, + {file = "watchdog-3.0.0-py3-none-win32.whl", hash = "sha256:3ed7c71a9dccfe838c2f0b6314ed0d9b22e77d268c67e015450a29036a81f60f"}, + {file = "watchdog-3.0.0-py3-none-win_amd64.whl", hash = "sha256:4c9956d27be0bb08fc5f30d9d0179a855436e655f046d288e2bcc11adfae893c"}, + {file = "watchdog-3.0.0-py3-none-win_ia64.whl", hash = "sha256:5d9f3a10e02d7371cd929b5d8f11e87d4bad890212ed3901f9b4d68767bee759"}, + {file = "watchdog-3.0.0.tar.gz", hash = "sha256:4d98a320595da7a7c5a18fc48cb633c2e73cda78f93cac2ef42d42bf609a33f9"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, + {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8" +content-hash = "0b05e7ded8f73a040b39ed2ca3642cb0f17f4222ff8b0c9c477c6de9db15794d" diff --git a/pyproject.toml b/pyproject.toml index 9cdeb00..774e422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,77 @@ [build-system] -requires = ["setuptools >= 40.6.0", "wheel"] -build-backend = "setuptools.build_meta" +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry] +name = "idesolver" +version = "2.0.0" +description = "A general purpose iterative numeric integro-differential equation (IDE) solver" +readme = "README.md" +homepage = "https://github.com/JoshKarpel/idesolver" +repository = "https://github.com/JoshKarpel/idesolver" +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Mathematics", +] +authors = ["JoshKarpel "] +license = "GPL-3.0" +include = ["py.typed"] + +[tool.poetry.dependencies] +python = ">=3.8" +numpy = ">=1.24" +scipy = ">=1.9" + +[tool.poetry.group.dev.dependencies] +pre-commit = ">=3" +pytest = ">=7" +pytest-mock = ">=3" +pytest-cov = ">=3" +pytest-xdist = ">=3" +hypothesis = ">=6" +mypy = ">=1" +mkdocs = ">=1.4" +mkdocs-material = ">=9" +mkdocstrings = {extras = ["python"], version = ">=0.19.0"} +matplotlib = ">=3.7" [tool.black] line-length = 100 -target-version = ["py36", "py37", "py38"] include = "\\.pyi?$" [tool.isort] -known_third_party = ["hypothesis", "matplotlib", "numpy", "pytest", "scipy", "setuptools", "sphinx_rtd_theme"] profile = "black" line_length = 100 +[tool.pycln] +all = true + [tool.pytest.ini_options] -testpaths = ["tests"] -console_output_style = "count" +addopts = ["--strict-markers", "-n", "auto"] +testpaths = ["tests", "idesolver", "docs"] + +[tool.mypy] +pretty = true +show_error_codes = true + +files = ["."] + +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_defs = true +no_implicit_optional = true +disallow_any_generics = true + +warn_unused_configs = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +warn_redundant_casts = true + +ignore_missing_imports = true diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 36642ba..0000000 --- a/setup.cfg +++ /dev/null @@ -1,46 +0,0 @@ -[metadata] -name = idesolver -version = 1.1.0 -description = A general purpose iterative numeric integro-differential equation (IDE) solver -long_description = file: README.rst -long_description_content_type = text/x-rst -url = https://github.com/JoshKarpel/idesolver -author = Josh Karpel -author_email = karpel@wisc.edu -maintainer = Josh Karpel -maintainer_email = karpel@wisc.edu -license = GPL-3.0 -license_file = LICENSE -classifiers = - Development Status :: 5 - Production/Stable - Intended Audience :: Science/Research - License :: OSI Approved :: GNU General Public License v3 (GPLv3) - Operating System :: OS Independent - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Topic :: Scientific/Engineering - Topic :: Scientific/Engineering :: Mathematics - -[options] -packages = find: -install_requires = - numpy>=1.18 - scipy>=1.4 - importlib-metadata>=1.0;python_version < "3.8" -python_requires = >=3.7 - -[options.extras_require] -docs = - sphinx - sphinx-autodoc-typehints - sphinx-issues - sphinx-rtd-theme -tests = - coverage>=6 - hypothesis>=5.41 - pytest>=7.1 - pytest-cov>=3 - pytest-mock>=3 - -[bdist_wheel] -universal = 1 diff --git a/setup.py b/setup.py deleted file mode 100644 index 6068493..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() diff --git a/tests/conftest.py b/tests/conftest.py index 13c8f18..e69de29 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +0,0 @@ -import numpy as np -import pytest - - -@pytest.fixture(scope="session") -def dummy_args(): - x = np.linspace(0, 1, 100) - y_0 = 1 - - return x, y_0 diff --git a/tests/test_complex_quad.py b/tests/test_complex_quad.py index c103f57..876c062 100644 --- a/tests/test_complex_quad.py +++ b/tests/test_complex_quad.py @@ -1,13 +1,19 @@ -import numpy as np +from typing import Callable + import pytest -import scipy.integrate as integ +from numpy import exp, float_, linspace, log +from numpy.typing import NDArray +from scipy.integrate import quad from idesolver import complex_quad @pytest.fixture(scope="module") -def x(): - return np.linspace(0, 1, 1000) +def x() -> NDArray[float_]: + return linspace(0, 1, 1000) + + +ArrayToArray = Callable[[NDArray[float_]], NDArray[float_]] @pytest.fixture( @@ -16,30 +22,30 @@ def x(): lambda x: 1, lambda x: 2.3 * x, lambda x: 0.1 * x**2, - lambda x: np.exp(x), - lambda x: np.log(x), + lambda x: exp(x), + lambda x: log(x), lambda x: 1 / (x + 0.1), ], ) -def real_integrand(request): +def real_integrand(request) -> ArrayToArray: return request.param -def test_real_part_passes_through(x, real_integrand): +def test_real_part_passes_through(x: NDArray[float_], real_integrand: ArrayToArray) -> None: cq_result, cq_real_error, cq_imag_error, *_ = complex_quad(real_integrand, x[0], x[-1]) - quad_result, quad_error = integ.quad(real_integrand, x[0], x[-1]) + quad_result, quad_error = quad(real_integrand, x[0], x[-1]) assert cq_result == quad_result assert cq_real_error == quad_error -def test_imag_part_passes_through(x, real_integrand): +def test_imag_part_passes_through(x: NDArray[float_], real_integrand: ArrayToArray) -> None: imag_integrand = lambda x: 1j * real_integrand(x) cq_result, cq_real_error, cq_imag_error, *_ = complex_quad(imag_integrand, x[0], x[-1]) - quad_result, quad_error = integ.quad(real_integrand, x[0], x[-1]) + quad_result, quad_error = quad(real_integrand, x[0], x[-1]) assert cq_result == 1j * quad_result assert cq_imag_error == quad_error @@ -49,14 +55,16 @@ def test_imag_part_passes_through(x, real_integrand): second_integrand = real_integrand -def test_real_and_imag_parts_combined(x, real_integrand, second_integrand): +def test_real_and_imag_parts_combined( + x: NDArray[float_], real_integrand: ArrayToArray, second_integrand: ArrayToArray +) -> None: imag_integrand = lambda x: 1j * second_integrand(x) combined_integrand = lambda x: real_integrand(x) + imag_integrand(x) cq_result, cq_real_error, cq_imag_error, *_ = complex_quad(combined_integrand, x[0], x[-1]) - quad_real_result, quad_real_error = integ.quad(real_integrand, x[0], x[-1]) - quad_imag_result, quad_imag_error = integ.quad(second_integrand, x[0], x[-1]) + quad_real_result, quad_real_error = quad(real_integrand, x[0], x[-1]) + quad_imag_result, quad_imag_error = quad(second_integrand, x[0], x[-1]) assert cq_result == quad_real_result + (1j * quad_imag_result) assert cq_real_error == quad_real_error diff --git a/tests/test_complex_valued.py b/tests/test_complex_valued.py index a081d90..1cfbbda 100644 --- a/tests/test_complex_valued.py +++ b/tests/test_complex_valued.py @@ -1,35 +1,35 @@ -import numpy as np import pytest +from numpy import linspace -from idesolver import IDESolver, UnexpectedlyComplexValuedIDE +from idesolver import IDE, UnexpectedlyComplexValuedIDE, solve_ide -def test_raise_exception_if_unexpectedly_complex(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0, # this not being 0j is what makes the test fail - c=lambda x, y: (5 * y) + 1, - d=lambda x: -3j, - k=lambda x, s: 1, - lower_bound=lambda x: 0, - upper_bound=lambda x: x, - f=lambda y: y, - ) - +def test_raise_exception_if_unexpectedly_complex() -> None: with pytest.raises(UnexpectedlyComplexValuedIDE): - solver.solve() + solve_ide( + ide=IDE( + x=linspace(0, 1, 100), + y_0=0, # this not being 0j is what makes the test fail + c=lambda x, y: (5 * y) + 1, + d=lambda x: -3j, + k=lambda x, s: 1, + lower_bound=lambda x: 0, + upper_bound=lambda x: x, + f=lambda y: y, + ) + ) -def test_no_exception_if_expected_complex(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0j, - c=lambda x, y: (5 * y) + 1, - d=lambda x: -3j, - k=lambda x, s: 1, - lower_bound=lambda x: 0, - upper_bound=lambda x: x, - f=lambda y: y, +def test_no_exception_if_expected_complex() -> None: + solve_ide( + ide=IDE( + x=linspace(0, 1, 100), + y_0=0j, + c=lambda x, y: (5 * y) + 1, + d=lambda x: -3j, + k=lambda x, s: 1, + lower_bound=lambda x: 0, + upper_bound=lambda x: x, + f=lambda y: y, + ) ) - - solver.solve() diff --git a/tests/test_misc.py b/tests/test_misc.py index 07f4821..f8548f1 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2,12 +2,13 @@ import hypothesis.strategies as st import numpy as np import pytest +from pytest_mock import MockerFixture -from idesolver import IDEConvergenceWarning, IDESolver +from idesolver import IDE, IDEConvergenceWarning, solve_ide -def test_warning_when_not_enough_iterations(): - args = dict( +def test_warning_when_not_enough_iterations() -> None: + ide = IDE( x=np.linspace(0, 1, 100), y_0=0, c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), @@ -16,139 +17,66 @@ def test_warning_when_not_enough_iterations(): lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, - global_error_tolerance=1e-6, ) - - good_solver = IDESolver(**args) - good_solver.solve() - - bad_solver = IDESolver(**args, max_iterations=int(good_solver.iteration / 2)) + good_result = solve_ide(ide=ide) with pytest.warns(IDEConvergenceWarning): - bad_solver.solve() - - -def test_y_intermediate_list_exists_if_store_intermediate_y_is_true(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, - k=lambda x, s: x / (1 + s), - lower_bound=lambda x: 0, - upper_bound=lambda x: 1, - f=lambda y: y, - global_error_tolerance=1e-6, - store_intermediate_y=True, - ) - - assert hasattr(solver, "y_intermediate") - - -def test_number_of_intermediate_solutions_is_same_as_iteration_count_plus_one(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, - k=lambda x, s: x / (1 + s), - lower_bound=lambda x: 0, - upper_bound=lambda x: 1, - f=lambda y: y, - global_error_tolerance=1e-6, - store_intermediate_y=True, - ) - solver.solve() - - # the +1 is for the initial value, which isn't counted as an iteration, but is counted as a y_intermediate - assert len(solver.y_intermediate) == solver.iteration + 1 - + bad_result = solve_ide( + ide=ide, + max_iterations=int(good_result.iterations / 2), + ) -def test_intermediate_solutions_of_scalar_problem_is_list_of_scalar_arrays(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, - k=lambda x, s: x / (1 + s), - lower_bound=lambda x: 0, - upper_bound=lambda x: 1, - f=lambda y: y, - global_error_tolerance=1e-6, - store_intermediate_y=True, - ) - solver.solve() - - assert np.all([y.ndim == 1 for y in solver.y_intermediate]) - - -def test_intermediate_solutions_of_vector_problem_is_list_of_vector_arrays(): - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=[0, 1, 0], - c=lambda x, y: [y[0] - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), y[0], 1], - d=lambda x: [1 / (np.log(2)) ** 2, 0, 0], - k=lambda x, s: x / (1 + s), - lower_bound=lambda x: 0, - upper_bound=lambda x: 1, - f=lambda y: y, - global_error_tolerance=1e-6, - store_intermediate_y=True, - ) - solver.solve() - - assert np.all([y.shape == (3, 100) for y in solver.y_intermediate]) - -def test_callback_is_called_correct_number_of_times(mocker): +def test_callback_is_called_correct_number_of_times(mocker: MockerFixture) -> None: callback = mocker.Mock() - solver = IDESolver( - x=np.linspace(0, 1, 100), - y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, - k=lambda x, s: x / (1 + s), - lower_bound=lambda x: 0, - upper_bound=lambda x: 1, - f=lambda y: y, + result = solve_ide( + ide=IDE( + x=np.linspace(0, 1, 100), + y_0=0, + c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), + d=lambda x: 1 / (np.log(2)) ** 2, + k=lambda x, s: x / (1 + s), + lower_bound=lambda x: 0, + upper_bound=lambda x: 1, + f=lambda y: y, + ), global_error_tolerance=1e-6, - store_intermediate_y=True, + callback=callback, ) - solver.solve(callback=callback) # first iteration is number 0, so add one to left to get total number of callback calls - assert callback.call_count == solver.iteration + 1 + assert callback.call_count == result.iterations + 1 @pytest.fixture(scope="module") -def default_solver(): - return IDESolver(x=np.linspace(0, 1, 100), y_0=0) +def default_solver() -> IDE: + return IDE(x=np.linspace(0, 1, 100), y_0=0) @hyp.given(x=st.complex_numbers(), y=st.complex_numbers()) -def test_default_c(default_solver, x, y): +def test_default_c(default_solver: IDE, x: complex, y: complex) -> None: assert default_solver.c(x, y) == 0 @hyp.given(x=st.complex_numbers()) -def test_default_d(default_solver, x): +def test_default_d(default_solver: IDE, x: complex) -> None: assert default_solver.d(x) == 1 @hyp.given(x=st.complex_numbers(), s=st.complex_numbers()) -def test_default_k(default_solver, x, s): +def test_default_k(default_solver: IDE, x: complex, s: complex) -> None: assert default_solver.k(x, s) == 1 @hyp.given(y=st.complex_numbers()) -def test_default_f(default_solver, y): +def test_default_f(default_solver: IDE, y: complex) -> None: assert default_solver.f(y) == 0 -def test_default_lower_bound(default_solver): +def test_default_lower_bound(default_solver: IDE) -> None: assert default_solver.lower_bound(default_solver.x) == default_solver.x[0] -def test_default_upper_bound(default_solver): +def test_default_upper_bound(default_solver: IDE) -> None: assert default_solver.upper_bound(default_solver.x) == default_solver.x[-1] diff --git a/tests/test_solver_against_analytic_solutions.py b/tests/test_solver_against_analytic_solutions.py index 805b09b..a7dbbe8 100644 --- a/tests/test_solver_against_analytic_solutions.py +++ b/tests/test_solver_against_analytic_solutions.py @@ -1,43 +1,44 @@ -import numpy as np +from typing import Callable + import pytest +from numpy import allclose, cos, exp, float_, linspace, log, pi, sin, sinh, sqrt +from numpy.typing import NDArray -from idesolver import IDESolver +from idesolver import IDE, solve_ide GELMI_EXAMPLES = [ ( # 1 - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=0, - c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - np.log(1 + x), - d=lambda x: 1 / (np.log(2)) ** 2, + c=lambda x, y: y - (0.5 * x) + (1 / (1 + x)) - log(1 + x), + d=lambda x: 1 / (log(2)) ** 2, k=lambda x, s: x / (1 + s), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, - global_error_tolerance=1e-6, ), - lambda x: np.log(1 + x), + lambda x: log(1 + x), ), ( # 2 - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=1, c=lambda x, y: y - - np.cos(2 * np.pi * x) - - (2 * np.pi * np.sin(2 * np.pi * x)) - - (0.5 * np.sin(4 * np.pi * x)), + - cos(2 * pi * x) + - (2 * pi * sin(2 * pi * x)) + - (0.5 * sin(4 * pi * x)), d=lambda x: 1, - k=lambda x, s: np.sin(2 * np.pi * ((2 * x) + s)), + k=lambda x, s: sin(2 * pi * ((2 * x) + s)), lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y, - global_error_tolerance=1e-6, ), - lambda x: np.cos(2 * np.pi * x), + lambda x: cos(2 * pi * x), ), ( # 3 - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=1, c=lambda x, y: 1 - (29 / 60) * x, d=lambda x: 1, @@ -45,31 +46,28 @@ lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: y**2, - global_error_tolerance=1e-6, ), lambda x: 1 + x + x**2, ), ( # 4 - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=1, - c=lambda x, y: (x * (1 + np.sqrt(x)) * np.exp(-np.sqrt(x))) - - (((x**2) + x + 1) * np.exp(-x)), + c=lambda x, y: (x * (1 + sqrt(x)) * exp(-sqrt(x))) - (((x**2) + x + 1) * exp(-x)), d=lambda x: 1, k=lambda x, s: x * s, lower_bound=lambda x: x, - upper_bound=lambda x: np.sqrt(x), + upper_bound=lambda x: sqrt(x), f=lambda y: y, - global_error_tolerance=1e-6, ), - lambda x: np.exp(-x), + lambda x: exp(-x), ), ] REAL_IDES = [ ( # RHS = 0 - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=1, c=lambda x, y: 0, d=lambda x: 0, @@ -77,20 +75,19 @@ lower_bound=lambda x: 0, upper_bound=lambda x: 1, f=lambda y: 0, - global_error_tolerance=1e-6, ), lambda x: 1, ), ( # RHS = 0 is the default, so if we pass nothing, we should get that - IDESolver(x=np.linspace(0, 1, 100), y_0=1, global_error_tolerance=1e-6), + IDE(x=linspace(0, 1, 100), y_0=1), lambda x: 1, ), ] COMPLEX_IDES = [ ( - IDESolver( - x=np.linspace(0, 1, 100), + IDE( + x=linspace(0, 1, 100), y_0=0j, c=lambda x, y: (5 * y) + 1, d=lambda x: -3j, @@ -98,16 +95,15 @@ lower_bound=lambda x: 0, upper_bound=lambda x: x, f=lambda y: y, - global_error_tolerance=1e-6, ), - lambda x: 2 * np.exp(5 * x / 2) * np.sinh(0.5 * np.sqrt(25 - 12j) * x) / np.sqrt(25 - 12j), + lambda x: 2 * exp(5 * x / 2) * sinh(0.5 * sqrt(25 - 12j) * x) / sqrt(25 - 12j), ) ] MULTIDIM = [ ( - IDESolver( - x=np.linspace(0, 7, 100), + IDE( + x=linspace(0, 7, 100), y_0=[0, 1], c=lambda x, y: [0.5 * (y[1] + 1), -0.5 * y[0]], d=lambda x: -0.5, @@ -115,17 +111,19 @@ lower_bound=lambda x: 0, upper_bound=lambda x: x, ), - lambda x: [np.sin(x), np.cos(x)], + lambda x: [sin(x), cos(x)], ) ] -@pytest.mark.parametrize("solver, exact", GELMI_EXAMPLES + REAL_IDES + COMPLEX_IDES + MULTIDIM) -def test_real_ide_against_analytic_solution(solver, exact): - solver.solve() +@pytest.mark.parametrize("ide, exact", GELMI_EXAMPLES + REAL_IDES + COMPLEX_IDES + MULTIDIM) +def test_real_ide_against_analytic_solution( + ide: IDE, exact: Callable[[NDArray[float_]], NDArray[float_]] +) -> None: + result = solve_ide(ide) - y_exact = exact(solver.x) + y_exact = exact(ide.x) - assert solver.global_error < solver.global_error_tolerance + assert result.global_error < 1e-6 - assert np.allclose(solver.y, y_exact, atol=1e-6) + assert allclose(result.y, y_exact, atol=1e-6) diff --git a/tests/test_solver_constructor_checks.py b/tests/test_solver_constructor_checks.py index 3cad216..3950a10 100644 --- a/tests/test_solver_constructor_checks.py +++ b/tests/test_solver_constructor_checks.py @@ -1,71 +1,73 @@ -import hypothesis as hyp -import hypothesis.strategies as st -import pytest - -from idesolver import IDESolver, InvalidParameter - - -@hyp.given(max_iterations=st.integers(min_value=1)) -def test_can_construct_with_positive_max_iterations(dummy_args, max_iterations): - IDESolver(*dummy_args, max_iterations=max_iterations) - - -@hyp.given(max_iterations=st.integers(max_value=0)) -def test_cannot_construct_with_nonpositive_max_iterations(dummy_args, max_iterations): - with pytest.raises(InvalidParameter): - IDESolver(*dummy_args, max_iterations=max_iterations) - - -@hyp.given(smoothing_factor=st.floats(min_value=0, max_value=1)) -def test_can_construct_with_good_smoothing_factor(dummy_args, smoothing_factor): - hyp.assume(smoothing_factor != 0 and smoothing_factor != 1) - - IDESolver(*dummy_args, smoothing_factor=smoothing_factor) - - -@hyp.given(smoothing_factor=st.one_of(st.floats(max_value=0), st.floats(min_value=1))) -def test_cannot_construct_with_bad_smoothing_factor(dummy_args, smoothing_factor): - with pytest.raises(InvalidParameter): - IDESolver(*dummy_args, smoothing_factor=smoothing_factor) - - -def test_can_construct_with_global_error_tolerance_set_and_without_max_iterations( - dummy_args, -): - IDESolver(*dummy_args, global_error_tolerance=1e-6, max_iterations=None) +from typing import Tuple - -def test_can_construct_with_global_error_tolerance_set_and_with_max_iterations_set( - dummy_args, -): - IDESolver(*dummy_args, global_error_tolerance=1e-6, max_iterations=50) - - -def test_can_construct_without_global_error_tolerance_set_and_with_max_iterations( - dummy_args, -): - IDESolver(*dummy_args, global_error_tolerance=0, max_iterations=50) - - -def test_cannot_construct_without_global_error_tolerance_set_and_without_max_iterations( - dummy_args, -): - with pytest.raises(InvalidParameter): - IDESolver(*dummy_args, global_error_tolerance=0, max_iterations=None) - - -@hyp.given(global_error_tolerance=st.floats(min_value=0)) -def test_can_construct_with_positive_global_error_tolerance(dummy_args, global_error_tolerance): - hyp.assume( - global_error_tolerance > 0 - ) # this test does not cover the case where tol = 0 and max_iterations = None - - IDESolver(*dummy_args, global_error_tolerance=global_error_tolerance) - - -@hyp.given(global_error_tolerance=st.floats(max_value=0)) -def test_cannot_construct_with_negative_global_error_tolerance(dummy_args, global_error_tolerance): - hyp.assume(global_error_tolerance < 0) - - with pytest.raises(InvalidParameter): - IDESolver(*dummy_args, global_error_tolerance=global_error_tolerance) +import pytest +from numpy import float_, linspace +from numpy.typing import NDArray + + +@pytest.fixture(scope="session") +def dummy_args() -> Tuple[NDArray[float_], float]: + x = linspace(0, 1, 100) + y_0 = 1 + + return x, y_0 + + +x = linspace(0, 1, 100) +y_0 = 1 + + +# @given(max_iterations=integers(min_value=1)) +# def test_can_construct_with_positive_max_iterations(max_iterations: int) -> None: +# IDESolver(x=x, y_0=y_0, max_iterations=max_iterations) +# +# +# @given(max_iterations=integers(max_value=0)) +# def test_cannot_construct_with_nonpositive_max_iterations(max_iterations: int) -> None: +# with pytest.raises(InvalidParameter): +# IDESolver(x=x, y_0=y_0, max_iterations=max_iterations) +# +# +# @given(smoothing_factor=floats(min_value=0, max_value=1)) +# def test_can_construct_with_good_smoothing_factor(smoothing_factor: float) -> None: +# assume(smoothing_factor != 0 and smoothing_factor != 1) +# +# IDESolver(x=x, y_0=y_0, smoothing_factor=smoothing_factor) +# +# +# @given(smoothing_factor=one_of(floats(max_value=0), floats(min_value=1))) +# def test_cannot_construct_with_bad_smoothing_factor(smoothing_factor: float) -> None: +# with pytest.raises(InvalidParameter): +# IDESolver(x=x, y_0=y_0, smoothing_factor=smoothing_factor) +# +# +# def test_can_construct_with_global_error_tolerance_set_and_without_max_iterations() -> None: +# IDESolver(x=x, y_0=y_0, global_error_tolerance=1e-6, max_iterations=None) +# +# +# def test_can_construct_with_global_error_tolerance_set_and_with_max_iterations_set() -> None: +# IDESolver(x=x, y_0=y_0, global_error_tolerance=1e-6, max_iterations=50) +# +# +# def test_can_construct_without_global_error_tolerance_set_and_with_max_iterations() -> None: +# IDESolver(x=x, y_0=y_0, global_error_tolerance=0, max_iterations=50) +# +# +# def test_cannot_construct_without_global_error_tolerance_set_and_without_max_iterations() -> None: +# with pytest.raises(InvalidParameter): +# IDESolver(x=x, y_0=y_0, global_error_tolerance=0, max_iterations=None) +# +# +# @given(global_error_tolerance=floats(min_value=0, exclude_min=True)) +# def test_can_construct_with_positive_global_error_tolerance(global_error_tolerance: float) -> None: +# IDESolver(x=x, y_0=y_0, global_error_tolerance=global_error_tolerance) +# +# +# @given(global_error_tolerance=floats(max_value=0)) +# def test_cannot_construct_with_negative_global_error_tolerance( +# global_error_tolerance: float, +# ) -> None: +# assume(global_error_tolerance < 0) +# +# with pytest.raises(InvalidParameter): +# IDESolver(x=x, y_0=y_0, global_error_tolerance=global_error_tolerance) diff --git a/tests/test_version.py b/tests/test_version.py index 262eb97..b70f7bc 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,5 @@ import idesolver -def test_version_info(): +def test_version() -> None: assert isinstance(idesolver.__version__, str)