From 698a209928bd58951e09246af76fdccd240b7f6f Mon Sep 17 00:00:00 2001 From: yuetan Date: Sat, 6 Jan 2024 11:00:42 +0800 Subject: [PATCH] init --- .github/ISSUE_TEMPLATE/bug-report.md | 8 + .github/ISSUE_TEMPLATE/feature-request.md | 8 + .github/ISSUE_TEMPLATE/question.md | 13 + .github/PULL_REQUEST_TEMPLATE.md | 13 + .github/workflows/codeql-analysis.yml | 52 + .github/workflows/lint.yml | 70 + .github/workflows/pypi_release.yml | 39 + .github/workflows/test.yml | 125 + .gitignore | 92 + .pre-commit-config.yaml | 29 + .readthedocs.yml | 26 + CHANGELOG.md | 18 + LICENSE | 201 ++ Makefile | 22 + README.md | 110 + README_zh_CN.md | 86 + codecov.yml | 8 + docker/Dockerfile | 9 + docs/Makefile | 20 + docs/make.bat | 35 + docs/requirements_docs.txt | 13 + docs/source/_static/backward-scheduling.png | Bin 0 -> 128704 bytes docs/source/_static/forward-scheduling.png | Bin 0 -> 137718 bytes docs/source/_static/logo.svg | 4 + .../_static/network-backward-algorithm.svg | 1 + .../_static/simple-backward-algorithm.svg | 1 + .../_templates/custom-module-template.rst | 67 + docs/source/api.rst | 13 + docs/source/application.rst | 146 + docs/source/conf.py | 146 + docs/source/demand.rst | 3 + docs/source/heuristics.rst | 28 + docs/source/index.rst | 75 + docs/source/rl.rst | 2 + docs/source/rules.rst | 71 + examples/__init__.py | 0 examples/data/k1.json | 129 + examples/genetic_example.py | 0 examples/rule_example.py | 101 + lekin/__init__.py | 12 + lekin/dashboard/__init__.py | 0 lekin/dashboard/gantt.py | 70 + lekin/dashboard/pages.py | 3 + lekin/datasets/__init__.py | 0 lekin/datasets/check_data.py | 15 + lekin/datasets/get_data.py | 13 + lekin/datasets/parse_data.py | 0 lekin/forecast/__init__.py | 0 lekin/lekin_struct/__init__.py | 15 + lekin/lekin_struct/job.py | 154 + lekin/lekin_struct/material.py | 7 + lekin/lekin_struct/operation.py | 146 + lekin/lekin_struct/relation.py | 18 + lekin/lekin_struct/resource.py | 265 ++ lekin/lekin_struct/route.py | 59 + lekin/lekin_struct/timeslot.py | 44 + lekin/objective/__init__.py | 2 + lekin/objective/makespan.py | 19 + lekin/objective/tardiness.py | 30 + lekin/scheduler.py | 25 + lekin/solver/__init__.py | 363 +++ .../construction_heuristics/__init__.py | 9 + lekin/solver/construction_heuristics/atcs.py | 38 + lekin/solver/construction_heuristics/base.py | 19 + lekin/solver/construction_heuristics/cr.py | 30 + lekin/solver/construction_heuristics/edd.py | 30 + lekin/solver/construction_heuristics/epst.py | 109 + lekin/solver/construction_heuristics/fifo.py | 31 + lekin/solver/construction_heuristics/lpst.py | 146 + lekin/solver/construction_heuristics/lsf.py | 46 + lekin/solver/construction_heuristics/mix.py | 72 + lekin/solver/construction_heuristics/spt.py | 102 + lekin/solver/meta_heuristics/__init__.py | 6 + .../meta_heuristics/branch_and_bound.py | 57 + lekin/solver/meta_heuristics/critical_path.py | 70 + lekin/solver/meta_heuristics/genetic.py | 98 + lekin/solver/meta_heuristics/hill_climbing.py | 60 + lekin/solver/meta_heuristics/nsga3.py | 87 + lekin/solver/meta_heuristics/pso.py | 1 + .../meta_heuristics/shifting_bottle_neck.py | 63 + lekin/solver/meta_heuristics/tabu_search.py | 74 + .../variable_neighborhood_search.py | 89 + lekin/solver/operation_research/__init__.py | 1 + lekin/solver/operation_research/ortool.py | 69 + .../solver/reinforcement_learning/__init__.py | 1 + lekin/solver/reinforcement_learning/dqn.py | 99 + lekin/solver/utils/__init__.py | 0 lekin/solver/utils/push_dense.py | 28 + lekin/utils/__init__.py | 0 lekin/utils/group_op_ds.py | 67 + lekin/utils/push_dense.py | 17 + poetry.lock | 2669 +++++++++++++++++ pyproject.toml | 91 + setup.cfg | 69 + tests/__init__.py | 0 tests/test_dashboard/__init__.py | 0 tests/test_dashboard/test_gantt.py | 0 tests/test_datasets/__init__.py | 0 tests/test_lekin_struct/__init__.py | 0 tests/test_lekin_struct/test_job.py | 0 tests/test_lekin_struct/test_operation.py | 0 tests/test_lekin_struct/test_resource.py | 0 tests/test_lekin_struct/test_route.py | 0 tests/test_lekin_struct/test_timeslot.py | 0 tests/test_objective/__init__.py | 0 tests/test_solver/__init__.py | 0 .../test_construction_heuristics/__init__.py | 0 .../test_construction_heuristics/test_lpst.py | 3 + .../test_construction_heuristics/test_rule.py | 3 + .../test_construction_heuristics/test_spt.py | 3 + .../test_meta_heuristics/__init__.py | 0 .../test_branch_and_bound.py | 26 + .../test_meta_heuristics/test_genetic.py | 0 .../test_meta_heuristics/test_nsga3.py | 0 .../test_meta_heuristics/test_tabu_search.py | 0 .../test_reinforcement_learning/__init__.py | 0 .../test_reinforcement_learning/test_dqn.py | 0 117 files changed, 7427 insertions(+) create mode 100755 .github/ISSUE_TEMPLATE/bug-report.md create mode 100755 .github/ISSUE_TEMPLATE/feature-request.md create mode 100755 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/pypi_release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .readthedocs.yml create mode 100644 CHANGELOG.md create mode 100755 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 README_zh_CN.md create mode 100644 codecov.yml create mode 100644 docker/Dockerfile create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/requirements_docs.txt create mode 100644 docs/source/_static/backward-scheduling.png create mode 100644 docs/source/_static/forward-scheduling.png create mode 100644 docs/source/_static/logo.svg create mode 100644 docs/source/_static/network-backward-algorithm.svg create mode 100644 docs/source/_static/simple-backward-algorithm.svg create mode 100644 docs/source/_templates/custom-module-template.rst create mode 100644 docs/source/api.rst create mode 100644 docs/source/application.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/demand.rst create mode 100644 docs/source/heuristics.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/rl.rst create mode 100644 docs/source/rules.rst create mode 100644 examples/__init__.py create mode 100644 examples/data/k1.json create mode 100644 examples/genetic_example.py create mode 100644 examples/rule_example.py create mode 100644 lekin/__init__.py create mode 100644 lekin/dashboard/__init__.py create mode 100644 lekin/dashboard/gantt.py create mode 100644 lekin/dashboard/pages.py create mode 100644 lekin/datasets/__init__.py create mode 100644 lekin/datasets/check_data.py create mode 100644 lekin/datasets/get_data.py create mode 100644 lekin/datasets/parse_data.py create mode 100644 lekin/forecast/__init__.py create mode 100644 lekin/lekin_struct/__init__.py create mode 100644 lekin/lekin_struct/job.py create mode 100644 lekin/lekin_struct/material.py create mode 100644 lekin/lekin_struct/operation.py create mode 100644 lekin/lekin_struct/relation.py create mode 100644 lekin/lekin_struct/resource.py create mode 100644 lekin/lekin_struct/route.py create mode 100644 lekin/lekin_struct/timeslot.py create mode 100644 lekin/objective/__init__.py create mode 100644 lekin/objective/makespan.py create mode 100644 lekin/objective/tardiness.py create mode 100644 lekin/scheduler.py create mode 100644 lekin/solver/__init__.py create mode 100644 lekin/solver/construction_heuristics/__init__.py create mode 100644 lekin/solver/construction_heuristics/atcs.py create mode 100644 lekin/solver/construction_heuristics/base.py create mode 100644 lekin/solver/construction_heuristics/cr.py create mode 100644 lekin/solver/construction_heuristics/edd.py create mode 100644 lekin/solver/construction_heuristics/epst.py create mode 100644 lekin/solver/construction_heuristics/fifo.py create mode 100644 lekin/solver/construction_heuristics/lpst.py create mode 100644 lekin/solver/construction_heuristics/lsf.py create mode 100644 lekin/solver/construction_heuristics/mix.py create mode 100644 lekin/solver/construction_heuristics/spt.py create mode 100644 lekin/solver/meta_heuristics/__init__.py create mode 100644 lekin/solver/meta_heuristics/branch_and_bound.py create mode 100644 lekin/solver/meta_heuristics/critical_path.py create mode 100644 lekin/solver/meta_heuristics/genetic.py create mode 100644 lekin/solver/meta_heuristics/hill_climbing.py create mode 100644 lekin/solver/meta_heuristics/nsga3.py create mode 100644 lekin/solver/meta_heuristics/pso.py create mode 100644 lekin/solver/meta_heuristics/shifting_bottle_neck.py create mode 100644 lekin/solver/meta_heuristics/tabu_search.py create mode 100644 lekin/solver/meta_heuristics/variable_neighborhood_search.py create mode 100644 lekin/solver/operation_research/__init__.py create mode 100644 lekin/solver/operation_research/ortool.py create mode 100644 lekin/solver/reinforcement_learning/__init__.py create mode 100644 lekin/solver/reinforcement_learning/dqn.py create mode 100644 lekin/solver/utils/__init__.py create mode 100644 lekin/solver/utils/push_dense.py create mode 100644 lekin/utils/__init__.py create mode 100644 lekin/utils/group_op_ds.py create mode 100644 lekin/utils/push_dense.py create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 tests/__init__.py create mode 100644 tests/test_dashboard/__init__.py create mode 100644 tests/test_dashboard/test_gantt.py create mode 100644 tests/test_datasets/__init__.py create mode 100644 tests/test_lekin_struct/__init__.py create mode 100644 tests/test_lekin_struct/test_job.py create mode 100644 tests/test_lekin_struct/test_operation.py create mode 100644 tests/test_lekin_struct/test_resource.py create mode 100644 tests/test_lekin_struct/test_route.py create mode 100644 tests/test_lekin_struct/test_timeslot.py create mode 100644 tests/test_objective/__init__.py create mode 100644 tests/test_solver/__init__.py create mode 100644 tests/test_solver/test_construction_heuristics/__init__.py create mode 100644 tests/test_solver/test_construction_heuristics/test_lpst.py create mode 100644 tests/test_solver/test_construction_heuristics/test_rule.py create mode 100644 tests/test_solver/test_construction_heuristics/test_spt.py create mode 100644 tests/test_solver/test_meta_heuristics/__init__.py create mode 100644 tests/test_solver/test_meta_heuristics/test_branch_and_bound.py create mode 100644 tests/test_solver/test_meta_heuristics/test_genetic.py create mode 100644 tests/test_solver/test_meta_heuristics/test_nsga3.py create mode 100644 tests/test_solver/test_meta_heuristics/test_tabu_search.py create mode 100644 tests/test_solver/test_reinforcement_learning/__init__.py create mode 100644 tests/test_solver/test_reinforcement_learning/test_dqn.py diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100755 index 0000000..5a3baa2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,8 @@ +--- +name: "🐛 Bug Report" +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100755 index 0000000..972de88 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,8 @@ +--- +name: "🚀 Feature Request" +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100755 index 0000000..2c22aea --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,13 @@ +--- +name: "❓Question" +about: Ask a general question +title: '' +labels: question +assignees: '' + +--- + +## ❔Question + + +## Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..0af54de --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,13 @@ +### Description + +This PR ... + +### Checklist + +- [ ] Linked issues (if existing) +- [ ] Amended changelog.md for large changes (and added myself there as contributor) +- [ ] Added/modified tests +- [ ] Used pre-commit hooks when committing to ensure that code is compliant with hooks. Install hooks with `pre-commit install`. + To run hooks independent of commit, execute `pre-commit run --all-files` + +Thank you for joining. Have fun coding! diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..f16fcfb --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,52 @@ +# This action runs GitHub's industry-leading static analysis engine, CodeQL, against a repository's source code to find security vulnerabilities. +# https://github.com/github/codeql-action + +name: "CodeQL" + +on: + schedule: + - cron: '0 0 27 * *' # Runs at 00:00 UTC on the 27th of every month + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: ['python'] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..8acd81b --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,70 @@ +name: Lint + +on: + push: + branches: [master, main, dev] + pull_request: + branches: [master, main] + +jobs: + linter-black: + name: Check code formatting with Black + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install Black + run: pip install black[jupyter] + - name: Run Black + run: black --check . + + imports-check-isort: + name: Check valid import formatting with isort + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up Python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install isort + run: pip install isort==5.6.4 + - name: Run isort + run: isort --check-only --diff . + + linter-flake8: + name: Check valid formatting with flake8 + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install dependencies + run: pip install flake8==3.9.2 + - name: Run checks + run: flake8 + + pre-commit-hooks: + name: Check that pre-commit hooks pass + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install dependencies + run: pip install pre-commit + + - name: Run checks + run: pre-commit run --all-files diff --git a/.github/workflows/pypi_release.yml b/.github/workflows/pypi_release.yml new file mode 100644 index 0000000..a366f6c --- /dev/null +++ b/.github/workflows/pypi_release.yml @@ -0,0 +1,39 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: PyPi Release + +on: + push: + branches: [main] + release: + types: [created] + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + + - name: Install poetry + shell: bash + run: | + curl -sSL https://install.python-poetry.org | python3 - + python -m pip install poetry-dynamic-versioning[plugin] + + - name: Set poetry path variable + run: echo "/Users/runner/.local/bin" >> $GITHUB_PATH + + - name: Build + run: | + poetry build + + - name: Publish distribution 📦 to PyPI + if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release' + run: | + poetry publish --username "${{ secrets.PYPI_USERNAME }}" --password "${{ secrets.PYPI_PASSWORD }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..b78dac0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,125 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Test + +on: + push: + branches: [master, main, dev] + pull_request: + branches: [master, main, dev] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest] # add windows-2019 when poetry allows installation with `-f` flag + python-version: [3.8, 3.9] # python 3.9 is not supported by all dependencies yet + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get full Python version + id: full-python-version + shell: bash + run: echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") + + - name: Install poetry + shell: bash + run: | + curl -sSL https://install.python-poetry.org | python3 - + - name: Set poetry path variable + run: echo "/Users/runner/.local/bin" >> $GITHUB_PATH + + - name: Configure poetry + shell: bash + run: poetry config virtualenvs.in-project true + + - name: Set up cache + uses: actions/cache@v2 + id: cache + with: + path: .venv + key: venv-${{ runner.os }}-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} + + - name: Ensure cache is healthy + if: steps.cache.outputs.cache-hit == 'true' + shell: bash + run: poetry run pip --version >/dev/null 2>&1 || rm -rf .venv + + - name: Upgrade pip + shell: bash + run: poetry run python -m pip install pip -U + + - name: Install dependencies + shell: bash + run: poetry install --no-interaction --no-root + + - name: Run unittest + shell: bash + run: poetry run coverage run -m unittest discover -s ./tests -p 'test_*.py' + + - name: Statistics + if: success() + run: | + poetry run coverage report -i + poetry run coverage xml -i + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + if: always() + continue-on-error: true + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: coverage.xml + flags: cpu, unittest + name: CPU-coverage + fail_ci_if_error: false + + docs: + name: Test docs build + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('docs/requirements_docs.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install dependencies + run: | + sudo apt-get update && sudo apt-get install -y pandoc + python -m pip install --upgrade pip + pip install -r docs/requirements_docs.txt + shell: bash + + - name: Build sphinx documentation + run: | + cd docs + make clean + make html --debug --jobs 2 SPHINXOPTS="-W" + + - name: Upload built docs + uses: actions/upload-artifact@v2 + with: + name: docs-results-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.requires }} + path: docs/build/html/ + # Use always() to always run this step to publish test results when there are test failures + if: success() diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..569e40e --- /dev/null +++ b/.gitignore @@ -0,0 +1,92 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# Sphinx documentation +docs/_build/ +docs/source/api/ +docs/source/CHANGELOG.md + +# mkdocs documentation +/site + +# pycharm +.idea + +# vscode +.vscode + +# checkpoints +*.ckpt +*.pkl +.DS_Store + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# yuetan +**/nohup.out +/reference/ +/data/ +/weights/ +/reference/jobshoppro/* +/business/* +/examples/*.xlsx diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ae55273 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-ast + - repo: https://github.com/PyCQA/flake8 + rev: "3.9.2" + hooks: + - id: flake8 + - repo: https://github.com/pre-commit/mirrors-isort + rev: v5.10.1 + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/nbQA-dev/nbQA + rev: 1.2.3 + hooks: + - id: nbqa-black + - id: nbqa-isort + - id: nbqa-flake8 + - id: nbqa-check-ast diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..3a16e74 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,26 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +# reference: https://docs.readthedocs.io/en/stable/config-file/v2.html#sphinx +sphinx: + configuration: docs/source/conf.py + fail_on_warning: false + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +formats: + - htmlzip + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.8 + install: + - requirements: docs/requirements_docs.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4eb3502 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# Release notes + +## v0.0.1 Initial release (15/10/2022) + +### Added +- solver support + - dispatching rules + - spt + - fifo + - edd + - forward scheduling + - backward scheduling + - meta heuristics + - local search + - genetic + +### Contributor +- LongxingTan diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..e9f1ff4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2023] [Longxing Tan] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b96002 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +.PHONY: style test docs + +check_dirs := lekin examples tests + +# run checks on all files and potentially modifies some of them + +style: + black $(check_dirs) + isort $(check_dirs) + flake8 $(check_dirs) + pre-commit run --files $(check_dirs) + +# run tests for the library + +test: + python -m unittest + +# run tests for the docs + +docs: + make -C docs clean M=$(shell pwd) + make -C docs html M=$(shell pwd) diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e8d2a2 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +[license-image]: https://img.shields.io/badge/License-Apache%202.0-blue.svg +[license-url]: https://opensource.org/licenses/Apache-2.0 +[pypi-image]: https://badge.fury.io/py/lekin.svg +[pypi-url]: https://pypi.python.org/pypi/lekin +[pepy-image]: https://pepy.tech/badge/lekin/month +[pepy-url]: https://pepy.tech/project/lekin +[build-image]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml/badge.svg?branch=master +[build-url]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml?query=branch%3Amaster +[lint-image]: https://github.com/LongxingTan/python-lekin/actions/workflows/lint.yml/badge.svg?branch=master +[lint-url]: https://github.com/LongxingTan/python-lekin/actions/workflows/lint.yml?query=branch%3Amaster +[docs-image]: https://readthedocs.org/projects/python-lekin/badge/?version=latest +[docs-url]: https://python-lekin.readthedocs.io/en/latest/ +[coverage-image]: https://codecov.io/gh/longxingtan/python-lekin/branch/master/graph/badge.svg +[coverage-url]: https://codecov.io/github/longxingtan/python-lekin?branch=master +[codeql-image]: https://github.com/longxingtan/python-lekin/actions/workflows/codeql-analysis.yml/badge.svg +[codeql-url]: https://github.com/longxingtan/python-lekin/actions/workflows/codeql-analysis.yml + +

+ +


+ +[![LICENSE][license-image]][license-url] +[![PyPI Version][pypi-image]][pypi-url] +[![Download][pepy-image]][pepy-url] +[![Build Status][build-image]][build-url] +[![Lint Status][lint-image]][lint-url] +[![Docs Status][docs-image]][docs-url] +[![Code Coverage][coverage-image]][coverage-url] +[![CodeQL Status][codeql-image]][codeql-url] + +**[Documentation](https://python-lekin.readthedocs.io)** | **[Tutorials](https://python-lekin.readthedocs.io/en/latest/tutorials.html)** | **[Release Notes](https://python-lekin.readthedocs.io/en/latest/CHANGELOG.html)** | **[中文](https://github.com/LongxingTan/python-lekin/blob/master/README_zh_CN.md)** + +**python-lekin** is a rapid-to-implement and easy-to-use Flexible Job Shop Scheduler Library, named after and inspired by [Lekin](https://web-static.stern.nyu.edu/om/software/lekin/). As a core function in **APS (advanced planning and scheduler)**, it helps manufacturers optimize the allocation of materials and production capacity optimally to balance demand and capacity. + +- accelerate by +- Changeover Optimization +- Ready for demo, research and maybe production + +# **DEVELOPING - NOT FINISHED AND DON'T USE IT NOW!** + +## Feature + +- constrained optimization + - route + - production + - material kit + - together + +- soft constrained optimization + - objective + + +## Tutorial + +**Installation** + +``` shell +$ pip install lekin +``` + +**Usage** + +``` python +from lekin import Heuristics, Rule +from lekin import Scheduler + +solver = Rule('SPT') +scheduler = Scheduler(solver) +scheduler.solve(job_list, machine_list) + +scheduler.draw() +``` + +## Examples + +In real world, Lekin integrates with MES to deploy production plans on the shop floor. Integration with ERP system is also required to exchange information on demand, inventory, and production + +- Exhaustive search + - branch and bound + +- Construction heuristics + - [SPT]() + - [critical path]() + +- Meta heuristics + - [local search]() + - [hill climbing]() + - [tabu search]() + - [evolutionary algorithms]() + - [genetic algorithms]() + +- Operation search + - [or-tools]() + +- Reinforcement learning + +Metaheuristics combined with Construction +Heuristics to initialize is the recommended choice. + +## Citation +``` +@misc{python-lekin2022, + author = {Yue Tan}, + title = {python lekin}, + year = {2020}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\url{https://github.com/yuetan1988/python-lekin}}, +} +``` diff --git a/README_zh_CN.md b/README_zh_CN.md new file mode 100644 index 0000000..cf97c5f --- /dev/null +++ b/README_zh_CN.md @@ -0,0 +1,86 @@ +[license-image]: https://img.shields.io/badge/License-Apache%202.0-blue.svg +[license-url]: https://opensource.org/licenses/Apache-2.0 +[pypi-image]: https://badge.fury.io/py/python-lekin.svg +[pypi-url]: https://pypi.python.org/pypi/python-lekin +[pepy-image]: https://pepy.tech/badge/lekin/month +[pepy-url]: https://pepy.tech/project/lekin +[build-image]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml/badge.svg?branch=master +[build-url]: https://github.com/LongxingTan/python-lekin/actions/workflows/test.yml?query=branch%3Amaster +[lint-image]: https://github.com/LongxingTan/python-lekin/actions/workflows/lint.yml/badge.svg?branch=master +[lint-url]: https://github.com/LongxingTan/python-lekin/actions/workflows/lint.yml?query=branch%3Amaster +[docs-image]: https://readthedocs.org/projects/python-lekin/badge/?version=latest +[docs-url]: https://python-lekin.readthedocs.io/en/latest/ +[coverage-image]: https://codecov.io/gh/longxingtan/python-lekin/branch/master/graph/badge.svg +[coverage-url]: https://codecov.io/github/longxingtan/python-lekin?branch=master + +

+ +


+ +[![LICENSE][license-image]][license-url] +[![PyPI Version][pypi-image]][pypi-url] +[![Download][pepy-image]][pepy-url] +[![Build Status][build-image]][build-url] +[![Lint Status][lint-image]][lint-url] +[![Docs Status][docs-image]][docs-url] +[![Code Coverage][coverage-image]][coverage-url] + +**[文档](https://python-lekin.readthedocs.io)** | **[教程](https://python-lekin.readthedocs.io/en/latest/tutorials.html)** | **[发布日志](https://python-lekin.readthedocs.io/en/latest/CHANGELOG.html)** | **[English](https://github.com/LongxingTan/python-lekin/blob/master/README.md)** + +**python-lekin**是一个APS智能排产调度工具,名字来源于[Lekin](https://web-static.stern.nyu.edu/om/software/lekin/)。在考虑实际约束的前提下,实现动态调整计划排程,高效响应客户订单承诺。 + + +- 支持工艺路线约束 +- 支持产能约束 +- 支持物料齐套约束 +- 支持顺排、倒排等排产方法 +- 支持遗传算法排产 +- 支持强化学习排产 + +# **开发中- 目前请不要使用包,可用代码跑和学习!** + +## 快速入门 + +### 安装 + +``` shell +$ pip install lekin +``` + +### 使用 + +``` python +from lekin import Heuristics, Genetics +from lekin import Scheduler + +solver = Heuristics() +scheduler = Scheduler(solver) +scheduler.solve(jobs, machines) + +scheduler.draw() +``` + +## 示例 +在实际APS系统开发中, + +- 按工艺路线拆分工序 +- 按BOM拆分物料 + +### 数据准备 +- Job +- Task +- Machine +- Route + +## 引用 + +``` +@misc{python-lekin2022, + author = {Yue Tan}, + title = {python lekin}, + year = {2020}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\url{https://github.com/yuetan1988/python-lekin}}, +} +``` diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..cb9489b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + precision: 2 + round: down + range: "70...100" + status: + project: + default: + threshold: 0.2% diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..8e9b12b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,9 @@ +FROM ubuntu:18.04 + +RUN apt-get update +RUN apt-get install -y wget vim python3.8 + +RUN pip install --no-cache-dir lekin + +# Set the default command to python3. +CMD ["python3"] diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +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) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..6247f7e --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt new file mode 100644 index 0000000..b1acf8e --- /dev/null +++ b/docs/requirements_docs.txt @@ -0,0 +1,13 @@ +recommonmark>=0.7.1 +nbconvert>=6.3.0 +pandoc>=1.0 +ipython +sphinx>3.2 +nbsphinx==0.8.8 +sphinx_markdown_tables==0.0.17 +pydata_sphinx_theme==0.8.0 +docutils +sphinx-autobuild + +pandas +numpy diff --git a/docs/source/_static/backward-scheduling.png b/docs/source/_static/backward-scheduling.png new file mode 100644 index 0000000000000000000000000000000000000000..99a9b8f56c28b2fc4fe41aed782177bcdf49cf32 GIT binary patch literal 128704 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 F{|}2kjUE61 literal 0 HcmV?d00001 diff --git a/docs/source/_static/forward-scheduling.png b/docs/source/_static/forward-scheduling.png new file mode 100644 index 0000000000000000000000000000000000000000..3eaf98df94c75613572b4dfc79386e46f66f8420 GIT binary patch literal 137718 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/logo.svg b/docs/source/_static/logo.svg new file mode 100644 index 0000000..bf2fc9c --- /dev/null +++ b/docs/source/_static/logo.svg @@ -0,0 +1,4 @@ + + + +
python-lekin
python-lekin
Text is not SVG - cannot display
diff --git a/docs/source/_static/network-backward-algorithm.svg b/docs/source/_static/network-backward-algorithm.svg new file mode 100644 index 0000000..8247950 --- /dev/null +++ b/docs/source/_static/network-backward-algorithm.svg @@ -0,0 +1 @@ +13342 diff --git a/docs/source/_static/simple-backward-algorithm.svg b/docs/source/_static/simple-backward-algorithm.svg new file mode 100644 index 0000000..8c9f3c9 --- /dev/null +++ b/docs/source/_static/simple-backward-algorithm.svg @@ -0,0 +1 @@ +13332 diff --git a/docs/source/_templates/custom-module-template.rst b/docs/source/_templates/custom-module-template.rst new file mode 100644 index 0000000..8617479 --- /dev/null +++ b/docs/source/_templates/custom-module-template.rst @@ -0,0 +1,67 @@ +{{ fullname.split(".")[-1] | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Module Attributes + + .. autosummary:: + :toctree: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + :template: custom-base-template.rst + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + :template: custom-class-template.rst + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..1fc7aaa --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,13 @@ +API +==== + +.. currentmodule:: lekin + +.. autosummary:: + :toctree: api + :template: custom-module-template.rst + :recursive: + + scheduler + solver + lekin_struct diff --git a/docs/source/application.rst b/docs/source/application.rst new file mode 100644 index 0000000..d84d113 --- /dev/null +++ b/docs/source/application.rst @@ -0,0 +1,146 @@ +Application +=========== + +基本概念 +---------------- +均衡生产:heijunka + + +数据 +---------------- + +MRP: Material Requirements Planning + + +BOM: Bill Of Materials + + +功能 +------------------------ + +一个完善的APS系统包含以下模块。 +- 需求中心 +- 排程中心 +- 排程工作台 +- 物料中心 + + +建模 +---------- + +Activities represent operations with time and resource requirements +Resources have calendars defining availability +Demand represents customer orders to fulfill + + +过程 +---------- + +实际APS会涉及到各个车间,各个工序的复杂区分,以及BOM中涉及到多工厂的部分。 +- 其实和APS关系不大,APS只要把最后一层工序展开,最后一层BOM展开,按实际的资源约束进行计算,最后只是展现形式上的区别 + +首先解决m个工序、n个机器的车间排产问题,然后把实际问题往车间问题靠。 + +1、根据一定的规则(产品总工作时长、工序B的最早开工时间等)获得产品的优先级 +2、初始化任务的初始状态,除了每个产品的工序A为可开工状态其余皆为不可开工状态; +3、根据优先级对工序A对应的任务进行加工,并更新任务的状态及紧后工序的状态; +4、对机器的空闲时间进行排序,取最早可开工机器k; +5、根据机器k的空闲开始时间以及任务状态检索任务,存储为任务列表R; +6、判断任务列表R是否为空,是则k=k+1,返回步骤五,否则进行下一步; +7、根据任务的最早可加工时间进行排序,选择最早开始的任务进行加工,更新机器状态、任务状态及后续的工序状态; + ·确定任务的开工时间及结束时间 + ·更新机器的释放时间 + ·更新当前任务的状态、开工时间、完工时间 + ·更新当前任务后续节点的最早开工时间,若当前任务为产品的最后一个工序则无须更新 +8、判断所有任务是否均已完工,是则结束,否则返回步骤四。 + +解决冲突的过程,即是一个顺排的过程。把所有分布在该资源上的任务根据顺序进行顺排 + +车间过程1-倒排+顺排 +------------------- + +先分发冻结期,按锁定期 +- 关键问题:成组后的工序部分处于冻结期;job的工序不是完整的工序 +- 只assign资源和日历,由于不完整进行顺排。 +- 检查物料齐套约束,如果物料不齐套,按齐套排程并给出报警消息 + + +再排锁定任务 +- 只倒排,如果倒排不可行则返回错误。按优先级 + - 非关键工序: 按lead_time(倒排lead_time,顺排lag_time)、节拍往前排,不考虑齐套时间。 + - 关键工序: 如有锁定资源,则按资源情况进行编排。不考虑齐套时间,因为锁定任务是为了确保优先级,物料通过缺料去人工追料 + - 共享工序: 共享工序本身取多个job中靠前的时间断。 + - 原本已经排过的其他job,前序工序需要以此为新的往前推 + - 同时拥有共享工序的job排序适时进行调整,尽量避免以上修改 + - 在冻结期且物料约束不满足: 按最早齐套时间进行排产,同时已排工序进行 + + +再根据优先级排产其他任务 +- 先倒排 + - 第一道关键工序前的非关键工序,先按lead time进行排,后面需要二次更新。 + - 关键工序倒排,(task分割会有的)非关键工序则按lead time前推 + - 倒排中时间约束都是最晚时间,但物料约束是最早开始时间。如果时间不足以排产,则该工序及之后的工序都转为顺排重排 + - 另一个思路是,先直接道排到第一个工序,后续一起按照可开始时间进行顺排。甚至等到推紧时一起顺排是否可行呢 + - 遇到共享task,如果之前的共享task被安排的时间更晚,那么剩余工序也转为顺排重排 + - 第一步剩余的非关键工序后拉 + - 如果任务一步中,资源日历不足则进入下一步 +- 倒排有问题则顺排 + - 按各个资源最早可用日期开始排 (此时应该选可以最早的资源),非关键工序按lead time排,并需要进行二次更新 + - 关键工序顺排 + - 如果遇到共享task不满足时间约束 + - 第一步剩余的非关键工序前拉 + - 如果任何一步中,资源日历不足则返回错误 +- 关键工序完全没有设置的job,按无限产能倒排 + + +任务推紧规整 +- 所有任务都采用顺排,类似按资源排产的方法。 +- 按关键工序设置,“是否可以挪动”。每一个资源的第一道关键工序都可以向前,并跟新后续的可挪动状态与开始时间约束 +- 迭代更新 + + +未排任务再次尝试 +- 推紧之后,再次尝试将之前未排的任务进行排产 + + +委外/外协的排产 +- 委外的指定是针对供应商,排到日历中 +- 根据关键工序的产能,按优先级将各委外的部分进行排产 + + +车间过程2-倒排+顺排2 +------------------- +仍然是先排锁定任务 + +把所有任务按照交期和最早开工日期进行倒排或顺排,不考虑资源的约束本身 【带来的问题是:资源优先级的选择】 + + +顺排的时候,按照job优先级 【指定 > 优先级】 +- 每一个job都按第一道工序其最早开工日期开始, + + + +车间过程3-按资源增量排产 +--------------------- +输入: 排产任务(MO+计划单) +输出: 各工序的排产资源与结果 +1. 筛选出主工单与部件工单,建立子部件的属性联系 +2. 筛选出主工单中的关键工序与非关键工序 +3. 初始化历史已排且其资源仍存在的关键工序的资源队列 +4. 对于新任务计划单或资源不存在的情况下, 重新分配任务. 完成资源中任务队列初始化 +5. 资源中任务队列重排 +6. 主工单非关键工序的前推后拉 +7. 部件工单和工序的前推 + + +可视化 +------------ +- 资源在时间线上的计划情况 +- 按订单,在时间线上的操作情况 + + +可视化重排 +------------------- +输入: 资源和资源任务队列顺序 +输出: +1. 初始化到增量排产队列任务 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..4953ee3 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,146 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# 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 +from pathlib import Path +import shutil +import sys + +from sphinx.application import Sphinx +from sphinx.ext.autosummary import Autosummary +from sphinx.pycode import ModuleAnalyzer + +SOURCE_PATH = Path(os.path.dirname(__file__)) # noqa # docs source +PROJECT_PATH = SOURCE_PATH.joinpath("../..") # noqa # project root + +sys.path.insert(0, str(PROJECT_PATH)) # noqa + +import lekin # isort:skip + +# -- Project information ----------------------------------------------------- + +project = "python-lekin" +copyright = "2022, Longxing Tan" +author = "Longxing Tan" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "nbsphinx", + "recommonmark", + "sphinx_markdown_tables", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinx.ext.napoleon", +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- 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 = "alabaster" + + +# 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"] + + +# setup configuration +def skip(app, what, name, obj, skip, options): + """ + Document __init__ methods + """ + if name == "__init__": + return True + return skip + + +apidoc_output_folder = SOURCE_PATH.joinpath("api") +PACKAGES = [lekin.__name__] + + +def get_by_name(string: str): + """ + Import by name and return imported module/function/class + Args: + string (str): module/function/class to import, e.g. 'pandas.read_csv' will return read_csv function as + defined by pandas + Returns: + imported object + """ + class_name = string.split(".")[-1] + module_name = ".".join(string.split(".")[:-1]) + + if module_name == "": + return getattr(sys.modules[__name__], class_name) + + mod = __import__(module_name, fromlist=[class_name]) + return getattr(mod, class_name) + + +class ModuleAutoSummary(Autosummary): + def get_items(self, names): + new_names = [] + for name in names: + mod = sys.modules[name] + mod_items = getattr(mod, "__all__", mod.__dict__) + for t in mod_items: + if "." not in t and not t.startswith("_"): + obj = get_by_name(f"{name}.{t}") + if hasattr(obj, "__module__"): + mod_name = obj.__module__ + t = f"{mod_name}.{t}" + if t.startswith("pytorch_forecasting"): + new_names.append(t) + new_items = super().get_items(sorted(new_names)) + return new_items + + +def setup(app: Sphinx): + app.add_css_file("custom.css") + app.connect("autodoc-skip-member", skip) + app.add_directive("moduleautosummary", ModuleAutoSummary) + app.add_js_file("https://buttons.github.io/buttons.js", **{"async": "async"}) + + +# autosummary +autosummary_generate = True +shutil.rmtree(SOURCE_PATH.joinpath("api"), ignore_errors=True) + + +# copy changelog +shutil.copy( + "../../CHANGELOG.md", + "CHANGELOG.md", +) diff --git a/docs/source/demand.rst b/docs/source/demand.rst new file mode 100644 index 0000000..a57a0af --- /dev/null +++ b/docs/source/demand.rst @@ -0,0 +1,3 @@ +demand +======================================== + diff --git a/docs/source/heuristics.rst b/docs/source/heuristics.rst new file mode 100644 index 0000000..3171229 --- /dev/null +++ b/docs/source/heuristics.rst @@ -0,0 +1,28 @@ +heuristics +============ + + +禁忌搜索 +------------ + + +遗传算法 +------------- + +遗传算法应用在排产中的关键就是如何将排产结果进行编码、以及如何计算fitness。 + +每一个可行解被称为一个染色体,一个染色体由多个元素构成,这个元素称为基因。 + +遗传算法应用在排产问题时,以及TSP、VRP等经典问题,模型的解不表示数量,而表示顺序。 +对于m个job,n个机器的排产问题。基因序列的长度是 m * n,因为每个job都需要在n台机器上加工。 + +两个序列表示模型的解,一个是工序的OS,一个是机器的MS。 +- 其中,OS基因序列的数值表示第i个job,这个数值第几次出现决定的是该job的第几道工序。 +- MS同理表示的选择的机器。 + + + +强化学习 +------------- + +Q-learning方法的关键是在agent探索过程中保存一个状态收益表。 diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..276d1b3 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,75 @@ +.. python-lekin documentation master file, created by + sphinx-quickstart on Fri Sep 30 17:57:17 2022. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +python-lekin documentation +======================================== +.. raw:: html + + GitHub + + +**python-lekin** 是一个工厂智能排产调度工具,名字来源于`Lekin `_. + +组合优化基础 +------------- + +- 装箱问题(Bin Packing, BP) +- 背包问题(Knapsack Problem, KP) +- 车间调度问题(Job-shop Scheduling Problem, JSP) +- 整数规划问题(Integer Programming, IP) + +- 旅行商问题(Traveling Salesman Problem, TSP) +- 车辆路径问题(Vehicle Routing Problem, VRP) +- 图着色问题(Graph Coloring, GC) +- 图匹配问题(Graph Matching, GM) + + +- 精确算法:分支定界法(Branch and Bound)和动态规划法(Dynamic Programming) + +- 近似算法:近似算法(Approximate Algorithms)和启发式算法(Heuristic Algorithms) + - 贪心算法、局部搜索算法、线性规划、松弛算法、序列算法 + - 模拟退火算法、禁忌搜索、进化算法、蚁群优化算法、粒子群算法、迭代局部搜索、变邻域搜索 + + +车间排产快速入门 +--------------- + +排产是一个分配任务,将有限的资源分配给需求。因此需求需要有优先级,约束主要有产能约束与物料约束。产能约束,将订单中的成品按工艺路线分解为工序,而每一道工序有对应的生产机器;物料约束,将订单的成品按BOM(bill of materials)展开为原材料需求,每一道工序开始前需要对应原材料齐套。 + +下标,机器k kk加工为任务i ii后加工任务j jj + +其中,:math:`A_\text{c} = (\pi/4) d^2` + + +subject to: + +.. math:: \alpha{}_t(i) = P(O_1, O_2, … O_t, q_t = S_i \lambda{}) + +Flexible Job-Shop Scheduling problem(FJSP)包含两个任务 +- Machine assignment: 选择机器 +- Operation sequencing:工序顺序 + + +Finite Capacity Planning + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + rules + heuristics + rl + application + demand + api + GitHub + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/rl.rst b/docs/source/rl.rst new file mode 100644 index 0000000..2d0a87b --- /dev/null +++ b/docs/source/rl.rst @@ -0,0 +1,2 @@ +rl +========= diff --git a/docs/source/rules.rst b/docs/source/rules.rst new file mode 100644 index 0000000..ef8e91d --- /dev/null +++ b/docs/source/rules.rst @@ -0,0 +1,71 @@ +Rules +============ + +分为rule-based、event-based、resource-based两种思路。 + +SPT最短加工时间 +-------------------- + +按任务所需工序时间长短,从短到长顺序排列. +实现中,为保证工艺路径的先后约束关系,构造规则法通过循环的先后关系来保证满足约束。 + + +EDD最早预定交货期规则 +--------------------------- + +按生产任务规定完成时刻(预定交货期)的先后,从先到后顺次排列 + +SPT—EDD规则 +----------------- + +1)根据EDD规则安排D(max)为最小的方案。 +2)计算所有任务的总流程时间。 +3)查找方案中,预定交贷期(di)大于总流程时间的生产任务(不惟一),按SPT规则,将其中加工时间最大者排于最后。 +4)舍弃第3步能排定的最后任务者及其后序任务,回到第2步重复。 + + +关键路径法 +------------- + +关键路径是决定项目完成的最短时间,关键路径可能不止一条。 + +其基本概念: +最早开始时间 (Early start) +最晚开始时间 (Late start) +最早完成时间 (Early finish) +最晚完成时间 (Late finish) +松弛时间 (slack) + +正推方法确定每个任务的最早开始时间和最早完成时间,逆推方法确定每个任务的最晚完成时间和最晚开始时间。 + + +顺排 +------------- + +顺排和倒排,和其他规则启发式算法一样,一个工序集一个工序集的排。每排一个工序,工序job完成后,更新机器、job状态、后续job状态。 +顺排对下一道工序的约束是:最早开始时间 + +.. code-block:: python + backward(operations, next_op_start_until, with_material_kitting_constraint, align_with_same_production_line, latest_start_time, latest_end_time) -> remaining_operations: list[operations], + + +.. code-block:: python + assign_op(operation, is_critical, direction: str, ) -> chosen_resource, chosen_production_id, chosen_hours, + +在顺排中,排的比较紧密的资源往往就是瓶颈资源。 + +倒排 +--------------- + +每一个MO最早开始时间初始化:max(ESD, today)。确保开始时间不早于今天,或不早于资源日历最早开始时间 +倒排对下一道工序的约束是: 最晚结束时间 + +倒排 +- 从业务上可以减少库存, just-in-time +- 带来的结果是不连续 +- 影响连续排产的判断 +- 考虑物料齐套时,导致倒排可能需要一次次推倒重来 + + +.. code-block:: python + forward(operations, next_op_start_until, with_material_kitting_constraint, align_with_same_production_line, earliest_start_time, earliest_end_time) diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/data/k1.json b/examples/data/k1.json new file mode 100644 index 0000000..1fdeef5 --- /dev/null +++ b/examples/data/k1.json @@ -0,0 +1,129 @@ +{ + "itineraries": [ + { + "itineraryName": "Itinerary 1", + "tasksList": [ + { + "taskName": "Task 1", + "taskMachine": { + "machineName": "M1" + }, + "taskDuration": 10.0 + }, + { + "taskName": "Task 2", + "taskMachine": { + "machineName": "M2" + }, + "taskDuration": 5.0 + }, + { + "taskName": "Task 3", + "taskMachine": { + "machineName": "M3" + }, + "taskDuration": 35.0 + } + ] + }, + { + "itineraryName": "Itinerary 2", + "tasksList": [ + { + "taskName": "Task 1", + "taskMachine": { + "machineName": "M2" + }, + "taskDuration": 25.0 + }, + { + "taskName": "Task 2", + "taskMachine": { + "machineName": "M1" + }, + "taskDuration": 5.0 + }, + { + "taskName": "Task 3", + "taskMachine": { + "machineName": "M3" + }, + "taskDuration": 30.0 + }, + { + "taskName": "Task 4", + "taskMachine": { + "machineName": "M4" + }, + "taskDuration": 15.0 + } + ] + }, + { + "itineraryName": "Itinerary 3", + "tasksList": [ + { + "taskName": "Task 1", + "taskMachine": { + "machineName": "M2" + }, + "taskDuration": 5.0 + }, + { + "taskName": "Task 2", + "taskMachine": { + "machineName": "M4" + }, + "taskDuration": 10.0 + } + ] + }, + { + "itineraryName": "Itinerary 4", + "tasksList": [ + { + "taskName": "Task 1", + "taskMachine": { + "machineName": "M2" + }, + "taskDuration": 15.0 + }, + { + "taskName": "Task 2", + "taskMachine": { + "machineName": "M3" + }, + "taskDuration": 10.0 + }, + { + "taskName": "Task 3", + "taskMachine": { + "machineName": "M4" + }, + "taskDuration": 20.0 + }, + { + "taskName": "Task 4", + "taskMachine": { + "machineName": "M1" + }, + "taskDuration": 10.0 + } + ] + } + ], + "machines": [ + { + "machineName": "M1" + }, + { + "machineName": "M2" + }, + { + "machineName": "M3" + }, + { + "machineName": "M4" + } + ] +} diff --git a/examples/genetic_example.py b/examples/genetic_example.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/rule_example.py b/examples/rule_example.py new file mode 100644 index 0000000..46d0cb3 --- /dev/null +++ b/examples/rule_example.py @@ -0,0 +1,101 @@ +import json +import logging + +from lekin.dashboard.gantt import get_scheduling_res_from_all_jobs, plot_gantt_chart +from lekin.lekin_struct import ( + Job, + JobCollector, + Operation, + OperationCollector, + Resource, + ResourceCollector, + Route, + RouteCollector, +) +from lekin.solver.construction_heuristics import BackwardScheduler, ForwardScheduler, LPSTScheduler, SPTScheduler + +logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.DEBUG) + + +def prepare_data(file_path="./data/k1.json"): + with open(file_path, "r", encoding="utf8") as file: # read file from path + data = json.loads(file.read()) + + job_collector = JobCollector() + # operation_collector = OperationCollector() + route_collector = RouteCollector() + resource_collector = ResourceCollector() + + if list(data.keys()) == ["itineraries", "machines"]: + resources = data["machines"] # is first level structure is correct, then split + routes = data["itineraries"] + + # parse the resource + for re in resources: + re_name = re["machineName"] + re_id = int(re_name.replace("M", "")) + resource = Resource(resource_id=re_id, resource_name=re_name) + resource_collector.add_resource_dict({re_id: resource}) + # print([i.resource_id for i in resource_collector.get_all_resources()]) + # print(resource_collector.get_all_resources()[0].available_hours) + + # parse the job and route + for ro in routes: + # ro_name = ro["itineraryName"] + ro_id = int(ro["itineraryName"].replace("Itinerary ", "")) + route = Route(route_id=ro_id) + operations_sequence = [] + for ta in ro["tasksList"]: + op_name = ta["taskName"] + op_id = ta["taskName"].replace("Task ", "") + + op_pt = ta["taskDuration"] + + op_tm = [] + if isinstance(ta["taskMachine"], list): + for re in ta["taskMachine"]: + re_name = re["machineName"] + re_id = int(re_name.replace("M", "")) + op_tm.append(resource_collector.get_resource_by_id(re_id)) + else: + re_name = ta["taskMachine"]["machineName"] + re_id = int(re_name.replace("M", "")) + op_tm.append(resource_collector.get_resource_by_id(re_id)) + + operations_sequence.append( + Operation( + operation_id=op_id, + operation_name=op_name, + quantity=1, + processing_time=op_pt, + parent_job_id=ro_id, # route defines job here + available_resource=op_tm, + ) + ) + + route.operations_sequence = operations_sequence + route_collector.add_route(route) + + job_collector.add_job(Job(job_id=ro_id, assigned_route_id=ro_id)) + + # print(resources) + # print(routes) + + return job_collector, resource_collector, route_collector + + +def run_scheduling(job_collector, resource_collector, route_collector): + # scheduler = ForwardScheduler(job_collector, resource_collector, route_collector) + scheduler = BackwardScheduler(job_collector, resource_collector, route_collector) + + scheduler.run() + return + + +if __name__ == "__main__": + job_collector, resource_collector, route_collector = prepare_data(file_path="./data/k1.json") + run_scheduling(job_collector, resource_collector, route_collector) + + scheduling_res = get_scheduling_res_from_all_jobs(job_collector) + print(scheduling_res) + plot_gantt_chart(job_collector, scheduling_res) diff --git a/lekin/__init__.py b/lekin/__init__.py new file mode 100644 index 0000000..a743725 --- /dev/null +++ b/lekin/__init__.py @@ -0,0 +1,12 @@ +from lekin.datasets.get_data import get_data + +# from lekin.lekin_struct.job import Job +# from lekin.lekin_struct.machine import Machine +# from lekin.lekin_struct.operation import Operation +# from lekin.lekin_struct.route import Route +from lekin.scheduler import Scheduler +from lekin.solver.meta_heuristics import Heuristics + +__all__ = ["Job", "Machine", "Route", "Operation", "Scheduler", "get_data"] + +__version__ = "0.0.0" diff --git a/lekin/dashboard/__init__.py b/lekin/dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/dashboard/gantt.py b/lekin/dashboard/gantt.py new file mode 100644 index 0000000..c3caabc --- /dev/null +++ b/lekin/dashboard/gantt.py @@ -0,0 +1,70 @@ +""" +Gantt +""" + +import logging +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +from matplotlib import ticker +import matplotlib.patches as patches +import matplotlib.pyplot as plt +import pandas as pd + +logging.getLogger("matplotlib.font_manager").disabled = True + + +def get_scheduling_res_from_all_jobs(job_collector): + ops = [] + for job in job_collector.job_list: + ops += job.operations + + scheduling_res = [] + for op in ops: + scheduling_res.append( + [ + op.operation_id, + op.parent_job_id, + op.quantity, + op.assigned_resource.resource_id, + min(op.assigned_hours), + max(op.assigned_hours), + ] + ) + scheduling_res = pd.DataFrame(scheduling_res, columns=["Operation", "Job", "Quantity", "Resource", "Start", "End"]) + scheduling_res["Duration"] = scheduling_res["End"] - scheduling_res["Start"] # + 1 + return scheduling_res + + +def plot_gantt_chart(job_collector, scheduling_res): + color_dict = job_collector.generate_color_list_for_jobs() + + # gantt + resource_list = [] + for resource, group in scheduling_res.groupby("Resource"): + resource_list.append(resource) + start_tuple = [] + color_tuple = [] + for _, op in group.iterrows(): + start_tuple.append(op[["Start", "Duration"]].tolist()) + color_tuple.append(color_dict.get(op["Job"])) + + plt.gca().broken_barh(start_tuple, ((resource + 1) * 10, 9), facecolors=color_tuple) + + # legend + legends_colors = [] + for job in job_collector.job_list: + legends_colors.append(patches.Patch(color=color_dict.get(job.job_id), label=f"job{job.job_id}")) + plt.legend(handles=legends_colors, fontsize=8) + + # resource tick + resources = resource_list # list(reversed(resource_list)) + resource_ticks = [15] + for i in range(len(resources)): + resource_ticks.append(resource_ticks[i] + 10) # machine increase + 10 + plt.yticks(resource_ticks[1:], resources) + + plt.grid(True) + plt.xlabel("Time") + plt.ylabel("Resources") + plt.title("Gantt Chart - Scheduling Result") + plt.show() diff --git a/lekin/dashboard/pages.py b/lekin/dashboard/pages.py new file mode 100644 index 0000000..51f2925 --- /dev/null +++ b/lekin/dashboard/pages.py @@ -0,0 +1,3 @@ +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +import streamlit as st diff --git a/lekin/datasets/__init__.py b/lekin/datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/datasets/check_data.py b/lekin/datasets/check_data.py new file mode 100644 index 0000000..c1c8cab --- /dev/null +++ b/lekin/datasets/check_data.py @@ -0,0 +1,15 @@ +"""Check the input job shop format and necessary information""" + +import logging + + +def check_data(data): + if data.keys() != ["routes", "machines"]: + logging.error("key") + + if len(data["machines"]) < 1: + logging.error("machine") + if len(data["routes"]) < 1: + logging.error("route") + + return diff --git a/lekin/datasets/get_data.py b/lekin/datasets/get_data.py new file mode 100644 index 0000000..715ca63 --- /dev/null +++ b/lekin/datasets/get_data.py @@ -0,0 +1,13 @@ +"""Generate the example jobshoppro""" + +import json +import logging + + +def get_data(name): + # machine_list = [] + # route_list = [] + if name == "simple": + pass + + return diff --git a/lekin/datasets/parse_data.py b/lekin/datasets/parse_data.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/forecast/__init__.py b/lekin/forecast/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/lekin_struct/__init__.py b/lekin/lekin_struct/__init__.py new file mode 100644 index 0000000..10496be --- /dev/null +++ b/lekin/lekin_struct/__init__.py @@ -0,0 +1,15 @@ +""" +https://gitee.com/EnCode/APS?_from=gitee_search#%E5%BB%BA%E6%A8%A1 + +工厂:订单 +车间:工单 +班组:工序 +操作动作:动作 + +""" + +from lekin.lekin_struct.job import Job, JobCollector # 成品需求 +from lekin.lekin_struct.operation import Operation, OperationCollector # 工序 +from lekin.lekin_struct.resource import Resource, ResourceCollector # 机器 +from lekin.lekin_struct.route import Route, RouteCollector +from lekin.lekin_struct.timeslot import TimeSlot diff --git a/lekin/lekin_struct/job.py b/lekin/lekin_struct/job.py new file mode 100644 index 0000000..88667f8 --- /dev/null +++ b/lekin/lekin_struct/job.py @@ -0,0 +1,154 @@ +""" +Struct Job/订单作业 + - a job could finish one product while finished + - job/mo/operation/activity +""" + +from datetime import datetime +import random +from typing import Any, Callable, Dict, List, Optional, Tuple + +from lekin.lekin_struct.operation import Operation + +random.seed(315) + + +class Job(object): + def __init__( + self, + job_id: str, + priority: int = None, + quantity: int = None, + demand_date: datetime = None, + job_type: str = None, + earliest_start_time=None, + assigned_route_id=None, + assigned_bom_id=None, + **kwargs: Dict, + ) -> None: + self.job_id: str = job_id + self.priority: int = priority + self.demand_date: datetime = demand_date + self.quantity: int = quantity + self.job_type: str = job_type + self.earliest_start_time: datetime = earliest_start_time # Material constraint + # cached scheduling result until the whole job is finished + self.cached_scheduling: dict = {} + self.assigned_route_id: str = assigned_route_id # Route object assigned to this job + self.assigned_bom_id: str = assigned_bom_id + self.current_operation_index: int = 0 # Record the current processing operation + self._operations_sequence: List[Operation] = [] # List of Operation objects for this job + + for key, value in kwargs.items(): + setattr(self, key, value) + + def assign_route(self, route_id): + self.assigned_route_id = route_id + + @property + def operations(self): + return self._operations_sequence + + def ger_next_operation(self): + if self.current_operation_index < len(self._operations_sequence): + return self._operations_sequence[self.current_operation_index] + else: + return None + + def assign_cached_scheduling(self): + """assign all cached scheduling officially while all ops are fine""" + pass + + def clear_cached_scheduling(self, all, start, dir): + """clear the cached scheduling result""" + pass + + @operations.setter + def operations(self, operations_sequence): + self._operations_sequence = operations_sequence + + def __eq__(self, other): + return + + def __hash__(self): + return + + def __str__(self): + return f"{self.job_id}" + + +class JobCollector: + def __init__(self): + self.job_list = [] # List to store Job objects + self.color_dict = dict() # List to store colors for job + self.index = -1 + # self.route_list = [] + # self.operation_list = [] + # self.resource_list = [] + # self.time_slot_list = [] + + def __iter__(self): + return self + + def __next__(self): + self.index += 1 + if self.index < len(self.job_list): + return self.job_list[self.index] + else: + raise StopIteration("Stop") + + def add_job(self, job: Job) -> None: + self.job_list.append(job) + + def get_job_by_id(self, job_id: str): + for job in self.job_list: + if job.job_id == job_id: + return job + return None + + def sort_jobs(self, jobs): + # Custom sorting function based on priority and continuity + def custom_sort(job): + priority_weight = (job.priority, job.demand_date) + # continuity_weight = -self.calculate_gap_time(job) + return priority_weight # + continuity_weight + + # jobs = sorted(jobs, key=custom_sort, reverse=True) + return [i[0] for i in sorted(enumerate(jobs), key=lambda x: custom_sort(x[1]), reverse=False)] + + def get_schedule(self): + schedule = {} + + for resource in self.resource_list: + scheduled_operations = [] + for operation in self.operation_list: + if operation.resource == resource: + scheduled_operations.append({"operation_id": operation.id, "start_time": operation.start_time}) + + if scheduled_operations: + schedule[resource.id] = scheduled_operations + + return schedule + + def generate_color_list_for_jobs(self, pastel_factor=0.5): + for job in self.job_list: + max_distance = None + best_color = None + for i in range(0, 100): + color = [ + (x + pastel_factor) / (1.0 + pastel_factor) for x in [random.uniform(0, 1.0) for i in [1, 2, 3]] + ] + if color not in self.color_dict.values(): + best_color = color + break + else: + best_distance = min([self.color_distance(color, c) for c in self.color_dict.values()]) + if not max_distance or best_distance > max_distance: + max_distance = best_distance + best_color = color + self.color_dict.update({job.job_id: best_color}) + return self.color_dict + + @staticmethod + def color_distance(c1, c2): + return sum([abs(x[0] - x[1]) for x in zip(c1, c2)]) diff --git a/lekin/lekin_struct/material.py b/lekin/lekin_struct/material.py new file mode 100644 index 0000000..5501117 --- /dev/null +++ b/lekin/lekin_struct/material.py @@ -0,0 +1,7 @@ +""" +material struct +- 物料属于需求的属性,连续排产时根据物料进行判断 +""" + +a = 1 +print(f"Schedule Job {a}/{a} backwards, " f"Num of ops: {a}, " "Demand date {a}, " "Priority {a}, " "Material {a}") diff --git a/lekin/lekin_struct/operation.py b/lekin/lekin_struct/operation.py new file mode 100644 index 0000000..2979741 --- /dev/null +++ b/lekin/lekin_struct/operation.py @@ -0,0 +1,146 @@ +""" +Operation Struct +op: 单个工单或新增需求的某一道工序 +GroupOP: 单个需求或多个需求必须在一起的一道工序 +MaterialOP: 同一物料多个需求的一道工序 +""" + +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + + +class Operation: + def __init__( + self, + operation_id: str, + operation_name: str, + quantity: int, + beat_time: Union[int, List[int], float, List[float]], + processing_time: Union[int, List[int], float, List[float]], + pre_time: float = 0, # setup times + post_time: float = 0, + lead_time: float = 0, + lag_time: float = 0, + route_constraint=None, + available_resource=None, + available_resource_priority=None, + parent_job_id=None, + prev_operation_ids=None, + next_operation_ids=None, + **kwargs, + ): + self.operation_id = operation_id + self.operation_name = operation_name + self.quantity = quantity + self.beat_time = beat_time + self.processing_time = processing_time + self.pre_time = pre_time + self.post_time = post_time + self.lead_time = lead_time + self.lag_time = lag_time + # self.demand_time = demand_time + self.route_constraint = route_constraint + self.available_resource = available_resource + self.available_resource_priority = available_resource_priority + self.parent_job_id = parent_job_id + self.prev_operation_ids = prev_operation_ids # predecessors + self.next_operation_ids = next_operation_ids # successors + + self.earliest_start_time = None + self.latest_start_time = None + self.earliest_end_time = None + self.latest_end_time = None + + self.statue = "none" # none -> waiting -> pending -> done + self.assigned_resource = None # Track the assigned resource + self.assigned_time_slot = None # Track the assigned time slot + + for key, value in kwargs.items(): + setattr(self, key, value) + + # Add a method to calculate granularity metric based on processing time and available time slot + def calculate_granularity_metric(self, available_time_slot): + # Calculate the granularity metric based on processing time and available time slot + pass + + def is_finished(self): + return self.assigned_resource is not None + + def __str__(self): + return f"{self.operation_id}-{self.operation_name}" + + +class JobOperations(object): + """Operations from same job""" + def __init__(self, operation_list): + pass + + @property + def job(self): + return + + +class MaterialOperation(object): + """ + 排产时, MaterialOperation, JobOperation, Operation是三层抽象结构 + resource.assigned_material_op = list[MaterialOperation] + """ + def __init__(self, job_operation_list): + pass + + @property + def material(self): + return + + +class OperationCollector: + def __init__(self): + self.operation_list = [] # List to store Operation objects + + def add_operation(self, operation): + self.operation_list.append(operation) + + def get_operation_by_id(self, operation_id): + for operation in self.operation_list: + if operation.operation_id == operation_id: + return operation + return None + + def get_operations_by_job_and_route(self, job_list, route_list): + assert len(job_list) == len(route_list) + + for job, route in zip(job_list, route_list): + # Get the operations for the current job and route + # route = route_list[route_index] + job_operations = route.get_operations() + + # Fill in the job_id for each operation + for i, operation in enumerate(job_operations): + if i > 0: + operation.parent_operation_id = job_operations[i - 1].operation_id + if i < len(job_operations) - 1: + operation.next_operation_id = job_operations[i + 1].operation_id + + operation.job_id = job.job_id + + # Extend the list of all operations with the current job's operations + self.operation_list.extend(job_operations) + + # Assign the operations to the current job + job.operations = job_operations + return self.operation_list + + # def get_operations_by_job_and_route(self, job_list, route_list): + # assert len(job_list) == len(route_list) + # operation_list = [] + # for operation in self.operations: + # if operation.parent_operation_id is None and operation.route_id == route_id: + # # If the operation is the first operation in the route + # current_operation = operation + # while current_operation: + # if current_operation.job_id == job_id: + # job_operations.append(current_operation) + # next_operation_id = current_operation.next_operation_id + # current_operation = next( + # (op for op in self.operations if op.operation_id == next_operation_id), None + # ) + # return job_operations diff --git a/lekin/lekin_struct/relation.py b/lekin/lekin_struct/relation.py new file mode 100644 index 0000000..113bb77 --- /dev/null +++ b/lekin/lekin_struct/relation.py @@ -0,0 +1,18 @@ +""" +工序之间的关系(工业工程) +ES: 前工序完成后,后工序开始 +ES-Split: 前工序分割后,后工序应在所有分割工作完成后开始 +SS: 前后工序可以同时开始。即进程的“异步”关系 +SS-Split: 前工序分割后,后工序和最后一个分割工作同时开始 +EE: 后工序制造时间(自定义制造时间短)小于前工序,前后工序一起结束 +EE-SS: 后工序制造时间(自定义制造时间长)大于前工序,后工序开始时间不能超过前工序开始时间,所以前后工序一起开始后,无法保证前后工序一起结束。 +EE-Split: 后工序制造时间和前分割工序时间长度进行比较 +""" + + +class OpRelation(object): + def __init__(self) -> None: + pass + + def calculate(self, type: str): + return diff --git a/lekin/lekin_struct/resource.py b/lekin/lekin_struct/resource.py new file mode 100644 index 0000000..0e517d1 --- /dev/null +++ b/lekin/lekin_struct/resource.py @@ -0,0 +1,265 @@ +""" +Resource/Machine Struct +""" + +import math +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +import numpy as np +import pandas as pd + +from lekin.lekin_struct.timeslot import TimeSlot + + +class Resource: + def __init__(self, resource_id, resource_name=None, max_tasks=1, **kwargs): + self.resource_id = resource_id + self.resource_name = resource_name + self.max_tasks = max_tasks # maximum task can be done in same time, capacity + self.tasks = {time_slot: None for time_slot in range(1, max_tasks + 1)} + self._available_timeslots = [] + self._available_hours = [] + + self.assigned_operations = [] + self.assigned_time_slots = [] + self.assigned_hours = [] + self.pending_list = [] # 临时缓冲区 + self.changeover_number = None # number of times + self.changeover_time = None # total time costs + + for key, value in kwargs.items(): + setattr(self, key, value) + + def add_timeslot(self, start_time, end_time): + self._available_timeslots.append(TimeSlot(start_time, end_time)) + self._available_hours += TimeSlot(start_time, end_time).hours + + def add_available_hours(self, hours): + self._available_hours = hours + + @property + def available_hours(self): + return self._available_hours + + @available_hours.setter + def available_hours(self, available_hours): + self._available_hours = available_hours + + def init_job_op_from_op(self): + """op-> job_op""" + pass + + def init_material_op_from_group_op(self): + """group_op-> material_op""" + pass + + def update_op_from_material_op(self): + """material_op -> op""" + pass + + def get_available_timeslot_for_op(self, start=None, end=None, periods=None, freq="1H", forward=True): + self.update_continuous_empty_hours() + select_hours = [i for i in self.available_hours if i <= end] + + assert len(self.available_hours) == len(self.continuous_empty_hours) + front_available = self.continuous_empty_hours[: len(select_hours)] + + chosen_hours_index = self.find_last_index_larger(periods, front_available) + + if not chosen_hours_index: + back_available = self.continuous_empty_hours[len(select_hours) :] + chosen_hours_index = self.find_first_index_larger(periods, back_available) + + if chosen_hours_index: + chosen_hours = self.available_hours[int(chosen_hours_index - periods) : chosen_hours_index] + return chosen_hours + else: + return [] + + def get_earliest_available_time(self, duration=None, start=None): + if len(self.available_hours) > len(self.assigned_hours): + return min(set(self.available_hours).difference(set(self.assigned_hours))) + else: + return None + + def get_latest_available_time(self, duration=None, end=None): + self.update_continuous_empty_hours() + return max([i + 1 for (i, v) in enumerate(self.continuous_empty_hours[:end]) if v >= duration]) + + def update_continuous_empty_hours(self): + if len(self.available_hours) != len(self._available_timeslots): + pass + if len(self.assigned_hours) != len(self.assigned_time_slots): + pass + + # for hours_list in self.available_hours: + empty_hours = [] + continuous_hours = 0 + + for hour in self.available_hours: + if hour in self.assigned_hours: # Hour is not available + continuous_hours = 0 + else: + continuous_hours += 1 + empty_hours.append(continuous_hours) + self.continuous_empty_hours = empty_hours + + def find_first_index_larger(self, input_value, lists): + for j, value in enumerate(lists): + if value > input_value: + return j + return None # If no value is larger than the input + + def find_last_index_larger(self, input_value, lists): + lists = lists[::-1] + for j, value in enumerate(lists): + if value > input_value: + return len(lists) - j + return None + + def get_available_time_slots_within_time(self, start=None, end=None, periods=None, freq="1H", forward=True): + available_hours = [] + + # check_periods = pd.date_range(start=start, end=end, periods=periods, freq=freq) + # if not forward: + # check_periods = check_periods[::-1] + # occupied_periods = [i.hours for i in self.assigned_time_slot] + # for period in check_periods: + # if period not in occupied_periods: + # available_hours.append(period) + # else: # considering the continuous assignment + # break + + # all_available_end_time = [slot.end_time for slot in self.available_timeslots] + # just_in_time_end_time_slot = max([i for i in all_available_end_time if i <= end]) + if forward: + selected_time_slot = [i for i in self.available_timeslots if i.start_time >= start] + else: + selected_time_slot = [i for i in self.available_timeslots if i.end_time <= end][::-1] + + # print(just_in_time_end_time_slot) + # print([i.start_time for i in self.available_timeslots]) + # print([i.end_time for i in self.available_timeslots]) + # print(self.available_timeslots[0].hours) + + left_periods = periods + + for time_slot in selected_time_slot: + if left_periods <= 0: + break + + if start and time_slot.start_time < start: + continue + + if end and time_slot.start_time >= end: + break + + # if forward: + # current_time = max(start, time_slot.start_time) + # else: + # current_time = min(end, time_slot.end_time) + + if time_slot in self.assigned_time_slot: + break + + if time_slot.duration_of_hours < left_periods: + available_hours += time_slot.hours + left_periods -= time_slot.duration_of_hours + + else: + available_hours += time_slot.hours[-math.ceil(left_periods) :] + break + # + # print(left_periods, available_hours) + + # for current_time in range(max(start, time_slot.start_time), + # min(end, time_slot.end_time - period) + 1): + # if all(self.is_available(current_time + offset, current_time +offset+1) for offset in range(periods)): + # available_hours.append(current_time) + + return available_hours + + def is_available(self, start_time, end_time): + for assigned_time_slot in self.assigned_time_slots: + if not (end_time <= assigned_time_slot.start_time or start_time >= assigned_time_slot.end_time): + return False + return True + + def get_unoccupied_time_slots_within_time(self): + unoccupied_slots = [] + prev_end_time = None + for time_slot in self.time_slots: + if not time_slot.is_occupied(): + if prev_end_time: + # Check if there is a gap between unoccupied time slots + if time_slot.start_time > prev_end_time: + unoccupied_slots.append(TimeSlot(prev_end_time, time_slot.start_time)) + unoccupied_slots.append(time_slot) + prev_end_time = time_slot.end_time + return unoccupied_slots + + def merge_schedules(self): + # Sort timeslots based on start time + self.timeslots.sort(key=lambda x: x.start_time) + + merged_slots = [] + current_slot = None + + for slot in self.timeslots: + if not current_slot: + current_slot = slot + else: + # If the current slot and the next slot overlap, merge them + if current_slot.end_time >= slot.start_time: + current_slot.end_time = max(current_slot.end_time, slot.end_time) + else: + merged_slots.append(current_slot) + current_slot = slot + + if current_slot: + merged_slots.append(current_slot) + + self.timeslots = merged_slots + + def __hash__(self): + return hash(str(self)) + + def __str__(self): + return f"{self.resource_id}" + + def __eq__(self, other): + return self.resource_id == other.resource_id + + def __lt__(self, other): + return self.resource_id < other.resource_id + + +class ResourceCollector: + def __init__(self): + self.resources = {} + self.index = -1 + + def __iter__(self): + return self + + def __next__(self): + self.index += 1 + if self.index < len(self.resources): + return list(self.resources.values())[self.index] + else: + raise StopIteration("Stop") + + def add_resource_dict(self, resource: Resource): + self.resources.update({resource.resource_id: resource}) + + def get_resource_by_id(self, resource_id): + return self.resources.get(resource_id) + + def get_all_resources(self): + return list(self.resources.values()) + + def get_unoccupied_time_slots(self): + unoccupied_slots = [] + for resource in self.get_all_resources(): + unoccupied_slots.extend(resource.get_unoccupied_time_slots()) + return unoccupied_slots diff --git a/lekin/lekin_struct/route.py b/lekin/lekin_struct/route.py new file mode 100644 index 0000000..a4ea487 --- /dev/null +++ b/lekin/lekin_struct/route.py @@ -0,0 +1,59 @@ +""" +Route map Struct +""" + +from typing import Any, Callable, Dict, List, Optional, Tuple + +from lekin.lekin_struct.operation import Operation + + +class Route: + def __init__(self, route_id, operations_sequence=None, available_resources=None, **kwargs): + self.route_id = route_id + self.operations_sequence = operations_sequence # List of Operation objects + self.available_resources = available_resources # List of Resource objects representing available machines, + # When assigning operations to resources, check for resource availability and consider resource capacities + self.available_time_slots = [] # List of time slots when machines are available + + for key, value in kwargs.items(): + setattr(self, key, value) + + def add_operation(self, operation: Operation): + self.operations_sequence.append(operation) + + def get_operations(self) -> List[Operation]: + return self.operations_sequence + + def add_resource(self, resource): + self.available_resources.append(resource) + + def add_time_slot(self, time_slot): + self.available_time_slots.append(time_slot) + + def __repr__(self): + return ( + f"Route(route_id={self.route_id}, operation_ids={self.operations_sequence}," + f" resources_available={self.available_resources})" + ) + + +class RouteCollector: + def __init__(self): + self.routes = {} + self.index = -1 + + def __iter__(self): + return self + + def __next__(self): + self.index += 1 + if self.index < len(self.routes): + return list(self.routes.values())[self.index] + else: + raise StopIteration("Stop") + + def add_route(self, route): + self.routes[route.route_id] = route + + def get_route_by_id(self, route_id): + return self.routes.get(route_id, None) diff --git a/lekin/lekin_struct/timeslot.py b/lekin/lekin_struct/timeslot.py new file mode 100644 index 0000000..861f309 --- /dev/null +++ b/lekin/lekin_struct/timeslot.py @@ -0,0 +1,44 @@ +""" +Calendar for resource Struct +""" + +from datetime import datetime, timedelta +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +import pandas as pd + + +class TimeSlot(object): + def __init__(self, start_time, end_time, **kwargs): + self.start_time = start_time + self.end_time = end_time + self.duration = end_time - start_time + self.assigned_operation = None + + for key, value in kwargs.items(): + setattr(self, key, value) + + def assign_operation(self, operation, processing_time): + self.assigned_operation = operation + self.end_time = self.start_time + timedelta(hours=processing_time) + + def is_occupied(self): + return self.assigned_operation is not None + + @property + def hours(self): + return pd.date_range(start=self.start_time, end=self.end_time, freq="1H").tolist()[:-1] + + @property + def duration_of_hours(self): + return len(pd.date_range(start=self.start_time, end=self.end_time, freq="1H")) - 1 + + def overlaps_with(self, timeslot): + overlap_start = max(self.start_time, timeslot.start_time) + overlap_end = min(self.end_time, timeslot.end_time) + + if overlap_start < overlap_end: + overlap_hours = (overlap_end - overlap_start).total_seconds() / 3600 + return overlap_hours + else: + return 0 diff --git a/lekin/objective/__init__.py b/lekin/objective/__init__.py new file mode 100644 index 0000000..428b4b1 --- /dev/null +++ b/lekin/objective/__init__.py @@ -0,0 +1,2 @@ +from lekin.objective.makespan import calculate_makespan +from lekin.objective.tardiness import calculate_tardiness diff --git a/lekin/objective/makespan.py b/lekin/objective/makespan.py new file mode 100644 index 0000000..c354ece --- /dev/null +++ b/lekin/objective/makespan.py @@ -0,0 +1,19 @@ +def calculate_makespan(job_collector): + for job in job_collector.job_list: + op = job.operations + job.makespan = op.assigned_hours[-1] + + if job.demand_date is not None: + job.tardiness = job.makespan - job.demand_date + return + + +def calculate_changeover_time(schedule_result, job_collector): + changeover_time = 0 + for resource in job_collector.resources: + previous_end_time = 0 + for operation in schedule_result: + if operation.resource == resource: + changeover_time += max(0, operation.start_time - previous_end_time) + previous_end_time = operation.end_time + return changeover_time diff --git a/lekin/objective/tardiness.py b/lekin/objective/tardiness.py new file mode 100644 index 0000000..785ea5e --- /dev/null +++ b/lekin/objective/tardiness.py @@ -0,0 +1,30 @@ +"""Tardiness total/maximum/weighted""" + + +def calculate_tardiness(schedule_result, job): + end_time = schedule_result[job.route.operations[-1]][1] + return max(0, end_time - job.demand_date) + + +def calculate_total_tardiness(schedule_result, jobs): + total_tardiness = 0 + for job in jobs: + total_tardiness += calculate_tardiness(schedule_result, job) + return total_tardiness + + +def calculate_total_late_jobs(schedule_result, jobs): + total_late_jobs = 0 + for job in jobs: + if calculate_tardiness(schedule_result, job) > 0: + total_late_jobs += 1 + return total_late_jobs + + +def calculate_total_late_time(schedule_result, jobs): + total_late_time = 0 + for job in jobs: + tardiness = calculate_tardiness(schedule_result, job) + if tardiness > 0: + total_late_time += tardiness + return total_late_time diff --git a/lekin/scheduler.py b/lekin/scheduler.py new file mode 100644 index 0000000..4662266 --- /dev/null +++ b/lekin/scheduler.py @@ -0,0 +1,25 @@ +"""Flexible job shop scheduler +Rescheduler due to inserted order or default machine +""" + +from collections import OrderedDict +import logging +from typing import Any, Callable, Dict, List, Optional, Tuple + +from lekin.datasets.check_data import check_data + + +class Scheduler(object): + def __init__(self, objective, solver, max_operations, **kwargs): + self.objective = objective + self.solver = solver + self.max_operations = max_operations + + def run(self, jobs, machines): + self.solver.solve(jobs, machines) + + def evaluate(self): + pass + + def plot(self): + pass diff --git a/lekin/solver/__init__.py b/lekin/solver/__init__.py new file mode 100644 index 0000000..cd5f2f6 --- /dev/null +++ b/lekin/solver/__init__.py @@ -0,0 +1,363 @@ +""" +交付 +连续 +均衡 +""" + +from datetime import datetime, timedelta +import heapq +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + +import pandas as pd + +from lekin.lekin_struct.operation import Operation + + +class CTPSolver(object): + def __init__(self, optimizers=None): + self.optimizers = optimizers + + def run(self, job_collector, route_list, resource_collector, operation_resource): + sorted_index = sort_jobs(job_collector.job_list) + job_list = [job_collector.job_list[i] for i in sorted_index] + + for i, job in enumerate(job_list): + route_id = job.assigned_route_id + route = None + for r in route_list: + if r.route_id == route_id: + route = r + break + if not route: + print(f"Route with ID '{job.assigned_route_id}' not found for Job ID '{job.job_id}'. Skipping job.") + continue + + operations_sequence = route.operations_sequence[::-1] # Reverse the operations in the route + print([i.operation_id for i in operations_sequence]) + + current_end_time = job.demand_date + + for operation in operations_sequence: + job_id = job.job_id + operation_id = operation.operation_id + + temp = operation_resource.loc[ + (operation_resource["产品ID"] == job_id) & (operation_resource["工序ID"] == operation_id) + ] + + operation.available_resource = [ + resource_collector.get_resource_by_id(i) for i in temp["资源需求组合ID"].tolist() + ] + operation.processing_time = temp["任务加工时长"].tolist() + operation.quantity = job.quantity + + print( + operation.operation_id, + operation.available_resource, + operation.processing_time, + operation.quantity, + current_end_time, + ) + + # 对于不同工序依赖进行更新 & calculate the time constraint + # Calculate earliest and latest start times based on demand time and route constraint + latest_end_time = current_end_time + if operation.next_operation_ids is not None: + pass + # get_operation_time_constraint() + + resource, start_time, end_time = find_best_resource_and_timeslot_for_operation( + operation, latest_end_time=latest_end_time + ) + + def reschedule_operation(self, operation): + # TODO: Implement dynamic rescheduling logic here, considering conflicts with other operations, + # resource availability, and any other relevant constraints. + pass + + +def sort_jobs(jobs): + # Custom sorting function based on priority and continuity + def custom_sort(job): + priority_weight = job.priority # You may adjust the weight based on your requirements + # continuity_weight = -self.calculate_gap_time(job) + return priority_weight # + continuity_weight + + # jobs = sorted(jobs, key=custom_sort, reverse=True) + return [i[0] for i in sorted(enumerate(jobs), key=lambda x: custom_sort(x[1]), reverse=False)] + + +def get_operation_time_constraint(): + # calculate the earliest start time and latest end time + return + + +def find_best_resource_and_timeslot_for_operation( + operation: Operation, earliest_start_time=None, latest_end_time=None, allowed_conflict=False +): + # assign operation + resource_timeslots_pq: List = [] # Create a priority queue to store the possible resource-time slot pairs + available_resource = operation.available_resource + # if operation.required_resource_priority is not None: + # required_resource.sort() # Sort by resource priority + + for i, resource in enumerate(available_resource): + # start_time = max(operation.prev_operation.end_time, earliest_start_time) + # end_time = min(start_time + operation.processing_time, latest_end_time) + + resource_all_available_end_time = [slot.end_time for slot in resource.available_timeslots] + just_in_time_end_time_slot = max([i for i in resource_all_available_end_time if i <= latest_end_time]) + print(latest_end_time, just_in_time_end_time_slot) + + if hasattr(operation.beat_time, "__iter__"): + working_hours = (operation.beat_time[i] * operation.quantity) / 60 + + unoccupied_hours = resource.get_available_time_slots_within_time() + if unoccupied_hours >= working_hours: + # assign + pass + else: + pass + + # required processing time and time slot + + just_in_time_slot = find_time_slots(resource, operation, "just_in_time") + if just_in_time_slot: + evaluation_score = calculate_evaluation_score(operation, just_in_time_slot, resource) + heapq.heappush(resource_timeslots_pq, (evaluation_score, resource, just_in_time_slot)) + + # If no "just in time" time slot is found, search for available time slots in the past + if not resource_timeslots_pq: + past_time_slots = find_time_slots(resource, operation, "history") + for timeslot in past_time_slots: + evaluation_score = calculate_evaluation_score(operation, timeslot, resource) + heapq.heappush(resource_timeslots_pq, (evaluation_score, resource, timeslot)) + + # If still no suitable time slot is found, look for available time slots in the future + # if in future, all the next operation need to be rescheduling forwards + if not resource_timeslots_pq: + future_time_slots = find_time_slots(resource, operation, "future") + for timeslot in future_time_slots: + evaluation_score = calculate_evaluation_score(operation, timeslot, resource) + heapq.heappush(resource_timeslots_pq, (evaluation_score, resource, timeslot)) + + if resource_timeslots_pq: + _, chosen_resource, chosen_timeslot = heapq.heappop(resource_timeslots_pq) + operation.assigned_resource = chosen_resource + operation.assigned_timeslot = chosen_timeslot + resource.available_timeslots -= chosen_timeslot + return True + + +def find_just_in_time_slots(resource, num_time_slots, duration_hours, job_demand_date): + available_slots = resource.get_available_time_slots() + just_in_time_slots = [] + + for slot in available_slots: + slot_end = slot + duration_hours + if slot_end <= job_demand_date: + just_in_time_slots.append(slot) + if len(just_in_time_slots) >= num_time_slots: + break + + return just_in_time_slots + + +def find_time_slots(resource, operation, time_slot_type, latest_end_time, working_hours): + # Find available time slots in the resource for the operation. + # The time_slot_type can be 'just_in_time', 'history', or 'future'. + end_time = [slot.end_time for slot in resource.time_slots] + time_slots = [] + if time_slot_type == "just_in_time": + # Find a "just in time" time slot where the operation can finish just before the job's demand date. + + resource_all_available_end_time = [slot.end_time for slot in resource.available_timeslots] + just_in_time_end_time_slot = max([i for i in resource_all_available_end_time if i <= latest_end_time]) + unoccupied_hours = resource.get_available_time_slots_within_time(just_in_time_end_time_slot) + if unoccupied_hours >= working_hours: + duration_hours = 0 + for slot in unoccupied_hours[::-1]: + time_slots.append(slot) + duration_hours += len(slot.hours) + + if duration_hours >= working_hours: + break + + elif time_slot_type == "history": + # Find available time slots in the past that allow the operation to finish before the demand date. + past_time_slots = [] + for slot in resource.timeslots: + if slot.end_time <= end_time: + past_time_slots.append(slot) + return past_time_slots + elif time_slot_type == "future": + # Find available time slots in the future that allow the operation to finish before the demand date. + future_time_slots = [] + for slot in resource.timeslots: + if slot.start_time >= end_time: + future_time_slots.append(slot) + return future_time_slots + else: + raise ValueError("Invalid time_slot_type. It should be 'just_in_time', 'history', or 'future'.") + + +def calculate_evaluation_score(self, operation, timeslots, resource): + # Implement evaluation function here, consider factors like changeover time, priority, lead time, etc. + # The evaluation function should consider conflicts with other operations and + # prioritize the best resource and timeslot sequence + priority_score = operation.priority + changeover_score = self.calculate_total_changeover_score(operation, timeslots, resource) + + return priority_score + changeover_score + + +def calculate_conflicts_hours(resource, operation): + for op in resource.assigned_operation: + if operation.time_slot.overlaps_with(op.time_slot): + # Handle conflict: Apply resolution strategy + # adjust_start_times(operations[i], operations[j]) + pass + return + + +def calculate_delay_hours(operation): + pass + + +def calculate_total_changeover_score(resource, operation, timeslots): + # Implement your total changeover calculation + # calculate the changeover time between the current operation and the previous one in the resource's task sequence + + # Example: A simple total changeover calculation based on processing time of the previous operation + total_changeover_time = 0 + if resource.assigned_operation: + previous_operation = resource.task_sequence[-1].operation + for timeslot in timeslots: + total_changeover_time += abs(previous_operation.processing_time - operation.processing_time) + + return total_changeover_time + + +class ConflictResolver: + def __init__(self, resources): + self.resources = resources + + def merge_and_resolve(self): + # Merge backward and forward schedules + for resource in self.resources: + resource.merge_schedules() + + # Resolve conflicts + for resource in self.resources: + self.resolve_conflicts(resource) + + def resolve_conflicts(self, resource): + # Find overlapping operations on the same resource at the same time + overlapping_ops = self.find_overlapping_operations(resource) + + # Resolve conflicts + for op1, op2 in overlapping_ops: + # Adjust the start or end time of operations to resolve conflicts + self.adjust_times_for_conflict(op1, op2) + + def find_overlapping_operations(self, resource): + overlapping_ops = [] + for time_slot in resource.schedules: + ops_in_timeslot = resource.schedules[time_slot] + if len(ops_in_timeslot) > 1: + # Multiple operations in the same time slot indicate a conflict + for i in range(len(ops_in_timeslot)): + for j in range(i + 1, len(ops_in_timeslot)): + overlapping_ops.append((ops_in_timeslot[i], ops_in_timeslot[j])) + return overlapping_ops + + def adjust_times_for_conflict(self, op1, op2): + # Implement your logic to adjust the start or end time of operations + # based on your specific requirements and constraints. + # You can consider factors like priority, changeovers, lead times, etc. + # For example, you might want to adjust the end time of op1 or the start time of op2. + # Check if op2 starts before op1 ends + if op2.start_time < op1.end_time: + # Calculate the time difference between op2's start time and op1's end time + time_difference = op1.end_time - op2.start_time + + # Update op2's start time to be after op1's end time + op2.start_time += time_difference + + # Adjust the end time of the job if it exceeds the demand date + if op2.end_time > op2.job.demand_date: + op2.job.demand_date = op2.end_time + + def resolve_conflicts2(self): + # Sort operations by their route requirements and processing time + self.operations.sort(key=lambda op: (op.route.priority, op.processing_time)) + + for operation in self.operations: + # Find a suitable time slot for the operation + suitable_time_slot = self.find_suitable_time_slot(operation) + + if suitable_time_slot is not None: + # Assign the operation to the resource and time slot + operation.resource = suitable_time_slot.resource + operation.time_slot = suitable_time_slot + + # Update resource's assigned operations + suitable_time_slot.resource.assign_operation(operation) + + def find_suitable_time_slot(self, operation): + # Sort available time slots based on priority, earliest start time, and resource availability + available_time_slots = sorted( + self.resources, + key=lambda resource: ( + resource.priority, + resource.get_earliest_start_time(), + resource.get_latest_end_time(), + ), + ) + + for time_slot in available_time_slots: + # Check if the resource's time slot is compatible with the operation's route + if time_slot.resource.is_compatible_route(operation.route): + # Check if there are any conflicts with other operations in the time slot + if not time_slot.has_conflicts(operation.processing_time): + return time_slot + + return None # No suitable time slot found + + +# -- +# +# for start_index in range(len(resource.timeslots) - operation.required_time_slots + 1): +# # Check if consecutive time slots can accommodate the entire operation +# can_accommodate = True +# for i in range(operation.required_time_slots): +# timeslot = resource.timeslots[start_index + i] +# if self.check_timeslot_conflict(resource, timeslot): +# can_accommodate = False +# break +# +# if can_accommodate: +# end_index = start_index + operation.required_time_slots - 1 +# evaluation_score = self.calculate_evaluation_score(operation, +# resource.timeslots[start_index:end_index + 1], +# resource) +# heappush(priority_queue, (evaluation_score, resource, resource.timeslots[start_index:end_index + 1])) +# +# if end_time >= start_time + processing_time: +# delay_time = max(0, operation.job.demand_date - end_time) +# changeover_time = self.calculate_changeover_time(last_operation, resource) +# priority_score = delay_time + changeover_time + resource.priority +# heapq.heappush(resource_timeslots, (priority_score, resource, start_time, end_time)) +# +# while resource_timeslots: +# _, resource, start_time, end_time = heapq.heappop(resource_timeslots) +# +# # Create a temporary timeslot for the operation +# temp_timeslot = Timeslot(start_time, end_time) +# +# # Check if the time slot overlaps with any of the previous task's timeslots +# if not self.check_timeslot_conflict(resource, temp_timeslot): +# # Assign the operation to the current resource and timeslot +# operation.assigned_resource = resource +# operation.assigned_timeslot = temp_timeslot +# return diff --git a/lekin/solver/construction_heuristics/__init__.py b/lekin/solver/construction_heuristics/__init__.py new file mode 100644 index 0000000..e2497db --- /dev/null +++ b/lekin/solver/construction_heuristics/__init__.py @@ -0,0 +1,9 @@ +"""Dispatching rules""" + +from lekin.solver.construction_heuristics.atcs import ATCScheduler +from lekin.solver.construction_heuristics.backward import BackwardScheduler +from lekin.solver.construction_heuristics.forward import ForwardScheduler +from lekin.solver.construction_heuristics.lpst import LPSTScheduler +from lekin.solver.construction_heuristics.spt import SPTScheduler + +__all__ = [ATCScheduler, LPSTScheduler, SPTScheduler, ForwardScheduler, BackwardScheduler] diff --git a/lekin/solver/construction_heuristics/atcs.py b/lekin/solver/construction_heuristics/atcs.py new file mode 100644 index 0000000..171208c --- /dev/null +++ b/lekin/solver/construction_heuristics/atcs.py @@ -0,0 +1,38 @@ +"""Apparent Tardiness Cost""" + +import logging + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class ATCScheduler(object): + def __init__(self, jobs, routes): + self.jobs = jobs + self.routes = routes + + def calculate_tardiness_cost(self, operation): + # Calculate the tardiness cost for an operation based on its finish time and due date + if operation.end_time > operation.due_date: + return operation.end_time - operation.due_date + else: + return 0 + + def schedule_job(self, job): + # Schedule the operations of a job using ATC method + for operation in job.route.operations: + operation.start_time = max(operation.available_time, operation.earliest_start_time) + operation.end_time = operation.start_time + operation.processing_time + + # Sort the operations by their tardiness cost in descending order + sorted_operations = sorted(job.route.operations, key=self.calculate_tardiness_cost, reverse=True) + + # Reschedule the operations based on their tardiness cost + current_time = 0 + for operation in sorted_operations: + operation.start_time = max(current_time, operation.earliest_start_time) + operation.end_time = operation.start_time + operation.processing_time + current_time = operation.end_time + + def run(self): + for job in self.jobs: + self.schedule_job(job) diff --git a/lekin/solver/construction_heuristics/base.py b/lekin/solver/construction_heuristics/base.py new file mode 100644 index 0000000..9dca977 --- /dev/null +++ b/lekin/solver/construction_heuristics/base.py @@ -0,0 +1,19 @@ +import logging + + +class BaseScheduler(object): + def __init__(self, job_collector, resource_collector, **kwargs): + self.job_collector = job_collector + self.resource_collector = resource_collector + + for key, value in kwargs.items(): + setattr(self, key, value) + + def run(self): + raise NotImplementedError + + def scheduling_job(self, job, **kwargs): + raise NotImplementedError + + def find_best_resource_and_timeslot_for_operation(self, operation, **kwargs): + raise NotImplementedError diff --git a/lekin/solver/construction_heuristics/cr.py b/lekin/solver/construction_heuristics/cr.py new file mode 100644 index 0000000..46a735c --- /dev/null +++ b/lekin/solver/construction_heuristics/cr.py @@ -0,0 +1,30 @@ +"""Critical ratio rule""" + +import logging + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class CRScheduler(object): + def __init__(self, jobs, routes): + self.jobs = jobs + self.routes = routes + + def calculate_critical_ratio(self, operation): + time_remaining = operation.job.due_date - operation.start_time + return time_remaining / operation.processing_time + + def schedule_job(self, job): + # Schedule the operations of a job using CR method + current_time = 0 + for operation in job.route.operations: + operation.start_time = max(current_time, operation.available_time) + operation.end_time = operation.start_time + operation.processing_time + current_time = operation.end_time + + # Sort the operations based on critical ratio + job.route.operations.sort(key=self.calculate_critical_ratio, reverse=True) + + def run(self): + for job in self.jobs: + self.schedule_job(job) diff --git a/lekin/solver/construction_heuristics/edd.py b/lekin/solver/construction_heuristics/edd.py new file mode 100644 index 0000000..07e1cfe --- /dev/null +++ b/lekin/solver/construction_heuristics/edd.py @@ -0,0 +1,30 @@ +"""Earliest Due Date""" + +import logging + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class EDDScheduler(object): + def __init__(self, jobs, routes): + self.jobs = jobs + self.routes = routes + + def schedule_job(self, job): + # Schedule the operations of a job using EDD method + current_time = 0 + for operation in job.route.operations: + operation.start_time = max(current_time, operation.available_time) + operation.end_time = operation.start_time + operation.processing_time + current_time = operation.end_time + + def run(self): + for job in self.jobs: + self.schedule_job(job) + + +class MS(object): + """Variation of EDD""" + + def __init__(self): + pass diff --git a/lekin/solver/construction_heuristics/epst.py b/lekin/solver/construction_heuristics/epst.py new file mode 100644 index 0000000..926a109 --- /dev/null +++ b/lekin/solver/construction_heuristics/epst.py @@ -0,0 +1,109 @@ +"""Earliest Possible Start Time +Forward scheduler +正排""" + +import logging +import math + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class EPSTScheduler(BaseScheduler): + def __init__(self, job_collector, resource_collector, route_collector=None, **kwargs): + super().__init__(job_collector, resource_collector, **kwargs) + self.job_collector = job_collector + self.resource_collector = resource_collector + self.route_collector = route_collector + + for key, value in kwargs.items(): + setattr(self, key, value) + + def run(self): + for i, job in enumerate(self.job_collector.job_list): + self.scheduling_job(job, self.resource_collector, self.route_collector) + logging.info("First Scheduling Done") + + for i, job in enumerate(self.job_collector.job_list): + self.rescheduling_job_to_resolve_conflict(job) + logging.info("ReScheduling Done") + return + + def scheduling_job(self, job, resource_collector, route_collector): + logging.info(f"\nAssign Job {job.job_id}") + + if route_collector is not None: + route_id = job.assigned_route_id + route = None + for r in route_collector: + if r.route_id == route_id: + route = r + break + if not route: + print(f"Route with ID '{job.assigned_route_id}' not found for Job ID '{job.job_id}'. Skipping job.") + + job.operations = route.operations_sequence + + op_earliest_start = 0 + for operation in job.operations: + logging.info(f"\tAssign Operation {operation.operation_id} of Job {job.job_id}") + chosen_resource, chosen_timeslot_hour = self.find_best_resource_and_timeslot_for_operation( + operation, op_earliest_start + ) + + if chosen_resource and chosen_timeslot_hour: + logging.info( + f"\tOperation {operation.operation_id} assigned in: resource" + f" {chosen_resource.resource_id}, {min(chosen_timeslot_hour)} -" + f" {max(chosen_timeslot_hour)}" + ) + + # assign + operation.assigned_resource = chosen_resource + operation.assigned_hours = chosen_timeslot_hour + chosen_resource.assigned_operations.append(operation) + chosen_resource.assigned_hours += chosen_timeslot_hour + + op_earliest_start = chosen_timeslot_hour[-1] + 1 + return + + def find_best_resource_and_timeslot_for_operation(self, operation, op_earliest_start, **kwargs): + available_resource = operation.available_resource + + earliest_index = 0 + resource_earliest_time = float("inf") + for i, resource in enumerate(available_resource): + resource_time = resource.get_earliest_available_time(duration=operation.processing_time) + + if resource_time < resource_earliest_time: + earliest_index = i + resource_earliest_time = resource_time + + chosen_resource = available_resource[earliest_index] + earliest_time = int(max(op_earliest_start, resource_earliest_time)) + chosen_hours = list(range(earliest_time, earliest_time + math.ceil(operation.processing_time))) + return chosen_resource, chosen_hours + + def rescheduling_job_to_resolve_conflict(self, job): + op_earliest_start = 0 + for operation in job.operations: + logging.info(f"Rescheduling {job.job_id}/ {operation.operation_id}") + assigned_resource = operation.assigned_resource + if operation.assigned_hours[0] < op_earliest_start: + delta = op_earliest_start - operation.assigned_hours[0] + operation.assigned_hours = [i + delta for i in operation.assigned_hours] + + pivot_assigned_hours = operation.assigned_hours + op_earliest_start = pivot_assigned_hours[-1] + 1 + + ops_in_same_resource = assigned_resource.assigned_operations + ops_in_same_resource.sort(key=lambda x: x.assigned_hours[0], reverse=False) + logging.info([i.parent_job_id for i in ops_in_same_resource]) + for op in ops_in_same_resource: + if op != operation: + op_start = op.assigned_hours[0] + if set(pivot_assigned_hours).intersection(set(op.assigned_hours)): + logging.info( + f"\tRescheduling {job.job_id}/ {op.operation_id} in {assigned_resource.resource_id}" + ) + op.assigned_hours = [i + pivot_assigned_hours[-1] - op_start + 1 for i in op.assigned_hours] + return diff --git a/lekin/solver/construction_heuristics/fifo.py b/lekin/solver/construction_heuristics/fifo.py new file mode 100644 index 0000000..232f840 --- /dev/null +++ b/lekin/solver/construction_heuristics/fifo.py @@ -0,0 +1,31 @@ +"""First In First Out""" + +import logging + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class FCFSScheduler: + """ + - 初始化,记录各个机器前的任务等待序列。模拟时间进度 + """ + + def __init__(self, jobs, routes): + self.jobs = jobs + self.routes = routes + + def init(self): + """ """ + pass + + def schedule_job(self, job): + # Schedule the operations of a job in FCFS order + current_time = 0 + for operation in job.route.operations: + operation.start_time = max(current_time, operation.available_time) + operation.end_time = operation.start_time + operation.processing_time + current_time = operation.end_time + + def run(self): + for job in self.jobs: + self.schedule_job(job) diff --git a/lekin/solver/construction_heuristics/lpst.py b/lekin/solver/construction_heuristics/lpst.py new file mode 100644 index 0000000..c43dd97 --- /dev/null +++ b/lekin/solver/construction_heuristics/lpst.py @@ -0,0 +1,146 @@ +"""Latest Possible Start Time +Backward scheduler +倒排""" + +import logging +import math + +from lekin.lekin_struct.job import Job, JobCollector +from lekin.lekin_struct.operation import Operation +from lekin.lekin_struct.resource import ResourceCollector +from lekin.lekin_struct.route import RouteCollector +from lekin.lekin_struct.timeslot import TimeSlot +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class LPSTScheduler(object): + def __init__( + self, + job_collector: JobCollector, + resource_collector: ResourceCollector, + route_collector: RouteCollector = None, + **kwargs, + ) -> None: + self.job_collector = job_collector + self.resource_collector = resource_collector + self.route_collector = route_collector + + for key, value in kwargs.items(): + setattr(self, key, value) + + def run(self) -> None: + for i, job in enumerate(self.job_collector.job_list): + self.scheduling_job(job, self.resource_collector, self.route_collector) + logging.info("First Scheduling Done") + return + + def scheduling_job(self, job: Job, resource_collector, route_collector: RouteCollector) -> None: + logging.info(f"\nAssign Job {job.job_id}") + + if route_collector is not None: + route_id = job.assigned_route_id + route = None + for r in route_collector: + if r.route_id == route_id: + route = r + break + if not route: + print(f"Route with ID '{job.assigned_route_id}' not found for Job ID '{job.job_id}'. Skipping job.") + + job.operations = route.operations_sequence + + op_earliest_start = 0 # forward constraint + op_latest_end = 150 # backward constraint + for operation in job.operations[::-1]: # inverse + logging.info(f"\tAssign Operation {operation.operation_id} of Job {job.job_id}") + chosen_resource, chosen_timeslot_hour = self.find_best_resource_and_timeslot_for_operation( + operation, op_latest_end, op_earliest_start + ) + + if chosen_resource and chosen_timeslot_hour: + logging.info( + f"\tOperation {operation.operation_id} assigned in: resource" + f" {chosen_resource.resource_id}, {min(chosen_timeslot_hour)} -" + f" {max(chosen_timeslot_hour)}" + ) + + # assign + operation.assigned_resource = chosen_resource + operation.assigned_hours = chosen_timeslot_hour + chosen_resource.assigned_operations.append(operation) + chosen_resource.assigned_hours += chosen_timeslot_hour + + # op_earliest_start = chosen_timeslot_hour[-1] + 1 + op_latest_end = chosen_timeslot_hour[0] - 1 + return + + def find_best_resource_and_timeslot_for_operation( + self, operation: Operation, op_latest_end=None, op_earliest_start=None, **kwargs + ): + available_resource = operation.available_resource + + latest_index = float("inf") + resource_latest_time = 0 + for i, resource in enumerate(available_resource): + resource_time = resource.get_latest_available_time(duration=operation.processing_time, end=op_latest_end) + + if resource_time > resource_latest_time: + latest_index = i + resource_latest_time = resource_time + + chosen_resource = available_resource[latest_index] + latest_time = int(min(op_latest_end, resource_latest_time)) + chosen_hours = list(range(latest_time - math.ceil(operation.processing_time), latest_time + 0)) + return chosen_resource, chosen_hours + + def assign_operation(self, operation: Operation, start_time, end_time, resources): + timeslot = TimeSlot(start_time, end_time) + self.timeslots.append(timeslot) + for resource in resources: + # Add timeslot to resource's schedule + resource.schedule.append(timeslot) + # Link operation to scheduled timeslot + operation.scheduled_timeslot = timeslot + + def select_resources(self, job: Job, operation: Operation): + available_slots = self.find_available_timeslots(job, operation) + + selected_resources = [] + for slot in available_slots: + resources = slot.available_resources() + resource = self.optimize_resource_selection(resources, operation) + selected_resources.append((slot, resource)) + return selected_resources + + def find_available_timeslots(self, job, operation): + # Search timeslots and filter based on: + # - operation duration + # - predecessor timeslots + # - resource requirements + + slots = [] + # for ts in job.schedule.timeslots: + # if ts.end - ts.start >= operation.duration: + # if all(pred in job.predecessors(ts)): + # if ts.meets_resource_needs(operation): + # slots.append(ts) + return slots + + def optimize_resource_selection(self, resources, operation): + # Score and prioritize resources based on: + # - Capacity + # - Changeover time + # - Utilization + + scored = [] + for resource in resources: + score = 0 + if resource.capacity >= operation.required_capacity: + score += 1 + if resource.type in operation.preferred_resources: + score += 1 + # Prioritize resources with less adjacent timeslots + score -= len(resource.adjacent_timeslots(operation)) + scored.append((score, resource)) + best = max(scored, key=lambda x: x[0]) + return best[1] diff --git a/lekin/solver/construction_heuristics/lsf.py b/lekin/solver/construction_heuristics/lsf.py new file mode 100644 index 0000000..ce25ed0 --- /dev/null +++ b/lekin/solver/construction_heuristics/lsf.py @@ -0,0 +1,46 @@ +"""L""" + +import logging +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class LSTScheduler(object): + def __init__(self, jobs, routes): + self.jobs = jobs + self.routes = routes + + def calculate_slack_time(self, operation, current_time): + # Calculate the slack time for an operation based on its due date and current time + return max(0, operation.due_date - current_time) + + def select_next_operation(self, available_operations, current_time): + # Select the operation with the longest slack time from the available operations + selected_operation = None + max_slack_time = float("-inf") + + for operation in available_operations: + slack_time = self.calculate_slack_time(operation, current_time) + if slack_time > max_slack_time: + max_slack_time = slack_time + selected_operation = operation + + return selected_operation + + def schedule_job(self, job, start_time): + # Schedule the operations of a job based on LST + current_time = start_time + + for operation in job.route.operations: + slack_time = self.calculate_slack_time(operation, current_time) + print(slack_time) + operation.start_time = current_time + operation.end_time = current_time + operation.processing_time + current_time += operation.processing_time + + job.completion_time = current_time + + def run(self): + for job in self.jobs: + self.schedule_job(job, 0) diff --git a/lekin/solver/construction_heuristics/mix.py b/lekin/solver/construction_heuristics/mix.py new file mode 100644 index 0000000..a74a1e7 --- /dev/null +++ b/lekin/solver/construction_heuristics/mix.py @@ -0,0 +1,72 @@ +import heapq + + +class JobScheduler: + def __init__(self, available_slots, jobs, routes, operations, resources): + self.available_slots = available_slots + heapq.heapify(self.available_slots) # Convert the list into a min heap + self.jobs = jobs + self.routes = routes + self.operations = operations + self.resources = resources + + def backward_schedule(self): + # Implement your initial backward scheduling pass here + pass + + def assign_resource(self, operation, available_resources): + # Implement resource assignment logic based on availability and scoring + pass + + def analyze_schedule_density(self): + # Implement schedule density analysis + pass + + def identify_bottleneck_resources(self): + # Identify bottleneck resources limiting density + pass + + def push_operations_closer(self, bottleneck_resources): + # Push operations closer on bottleneck resources + pass + + def rescore_operations(self): + # Rescore operations based on priority and slack time + pass + + def reassign_operations(self): + # Reassign operations to reduce gaps + pass + + def reevaluate_routes(self, critical_jobs): + # Re-evaluate routes for critical jobs + pass + + def reschedule_operations(self, critical_jobs): + # Reschedule operations on preferred resources for critical jobs + pass + + def push_dense(self): + # Iteratively push operations closer system-wide + # while True: + # self.backward_schedule() + # density = self.analyze_schedule_density() + # if density is not improved: + # break + pass + + def final_tweaking(self): + # Fine-tune schedules of critical jobs and leverage flexibilities + pass + + def optimize_schedule(self): + self.push_dense() + critical_jobs = self.identify_critical_jobs() + self.reevaluate_routes(critical_jobs) + self.reschedule_operations(critical_jobs) + self.final_tweaking() + return self.schedule + + def identify_critical_jobs(self): + # Identify critical jobs on the schedule + pass diff --git a/lekin/solver/construction_heuristics/spt.py b/lekin/solver/construction_heuristics/spt.py new file mode 100644 index 0000000..bd47e82 --- /dev/null +++ b/lekin/solver/construction_heuristics/spt.py @@ -0,0 +1,102 @@ +"""Shortest Processing Time +""" + +from collections import OrderedDict +import logging +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +from lekin.solver.construction_heuristics.base import BaseScheduler + + +class SPTScheduler(object): + def __init__(self): + self.time = {} # global时间队列 + self.waiting_operations = {} # 记录每个机器的任务等待队列 + self.jobs_list_to_export = [] + + def setup(self, job_list: List, machine_list: List): + for machine in machine_list: + # init for machine start time + self.current_time_on_machines[machine.name] = 0 + + # init for waiting list of machines + self.waiting_operations[machine.name] = 0 + for job in job_list: + if job.operation_id == 1 and machine.name in job.machine: + if len(job.machine) == 1: + self.waiting_operations[machine.name].append(job) + + self.waiting_operations[machine.name].sort(key=lambda j: j.duration) + return + + def solve(self, job_list: List, machine_list: List): + self.setup(job_list, machine_list) + + self.time[0] = self.waiting_operations + + for key_mach, operations in self.waiting_operations.items(): + # for each waiting task in front of machine, set time to 0 + if len(operations): + operations[0].start_time = 0 + operations[0].completed = True + operations[0].assigned_machine = key_mach + + self.jobs_list_to_export.append(operations[0]) + self.current_time_on_machines[key_mach] = operations[0].get_end_time() + self.time[self.current_time_on_machines[key_mach]] = {} + + while len(self.jobs_list_to_export) != len(job_list): + for t, operations in self.time.items(): + operations = self.get_waiting_operations( + job_list, float(t), machine_list, self.current_time_on_machines + ) + + for key_mach, tasks in operations.items(): + if len(tasks): + if float(t) < self.current_time_on_machines[key_mach]: + continue + + tasks[0].start_time = float(t) + tasks[0].completed = True + tasks[0].assigned_machine = key_mach + + self.jobs_list_to_export.append(tasks[0]) + self.current_time_on_machines[key_mach] = tasks[0].get_end_time() + self.time[self.current_time_on_machines[key_mach]] = {} + + del self.time[t] + break + self.time = OrderedDict(self.time) + return self.jobs_list_to_export + + def get_waiting_operations(self, job_list, time, machine_list, current_time_on_machines): + incoming_operations = {} + + for mach in machine_list: + assigned_jobs_for_machine = [] + for job in job_list: + if job.completed is False and mach.name in job.machine: + if len(job.machine) == 1: + assigned_jobs_for_machine.append(job) + + incoming_operations[mach.name] = [] + for j in assigned_jobs_for_machine: + if j.id_operation == 1: + incoming_operations[mach.name].append(j) + else: + previous_task = [ + job + for job in job_list + if job.route_id == j.route_id + and job.id_operation == (j.id_operation - 1) + and job.end_time <= time + ] + if len(previous_task): + if previous_task[0].completed: + incoming_operations[mach.name].append(j) + + incoming_operations[mach.name].sort(key=lambda j: j.duration) + return incoming_operations + + def run(self): + return diff --git a/lekin/solver/meta_heuristics/__init__.py b/lekin/solver/meta_heuristics/__init__.py new file mode 100644 index 0000000..e8ba164 --- /dev/null +++ b/lekin/solver/meta_heuristics/__init__.py @@ -0,0 +1,6 @@ +"""Heuristics""" + + +class Heuristics(object): + def __init__(self, name): + pass diff --git a/lekin/solver/meta_heuristics/branch_and_bound.py b/lekin/solver/meta_heuristics/branch_and_bound.py new file mode 100644 index 0000000..0734e06 --- /dev/null +++ b/lekin/solver/meta_heuristics/branch_and_bound.py @@ -0,0 +1,57 @@ +from datetime import datetime, timedelta +from itertools import permutations + +from lekin.lekin_struct import TimeSlot + + +class BranchAndBoundScheduler: + def __init__(self, job_list, resource_list): + self.job_list = job_list + self.resource_list = resource_list + self.best_schedule = None + self.best_cost = float("inf") + + def find_available_time_slots(self, operation, resource, current_time): + # Implement the logic to find available time slots for a given operation and resource + available_time_slots = [] + for slot in resource.available_time_slots: + if slot.start_time >= current_time + operation.processing_time: + available_time_slots.append(slot) + return available_time_slots + + def assign_operation(self, job, operation, resource, start_time): + # Implement the logic to assign an operation to a resource at a specific start time + end_time = start_time + operation.processing_time + resource.available_time_slots.append(TimeSlot(end_time, float("inf"))) + return end_time + + def calculate_cost(self, schedule): + # Implement the logic to calculate the cost of the current schedule + # For example, you can calculate makespan or any other relevant metric + makespan = max(slot.end_time for slot in schedule) + return makespan + + def branch_and_bound(self, current_job_idx, current_time, schedule): + # Implement the Branch and Bound algorithm for scheduling + if current_job_idx == len(self.job_list): + cost = self.calculate_cost(schedule) + if cost < self.best_cost: + self.best_cost = cost + self.best_schedule = schedule.copy() + return + + job = self.job_list[current_job_idx] + route = self.resource_list[job.assigned_route_id] + + for resource_id in route.operations[0].resource_ids: + resource = self.resource_list[resource_id] + time_slots = self.find_available_time_slots(route.operations[0], resource, current_time) + for slot in time_slots: + new_schedule = schedule + [slot] + new_time = self.assign_operation(job, route.operations[0], resource, slot.start_time) + self.branch_and_bound(current_job_idx + 1, new_time, new_schedule) + + def get_schedule(self): + # Implement the main function to get the final schedule using Branch and Bound + self.branch_and_bound(0, datetime(2023, 1, 1), []) + return self.best_schedule diff --git a/lekin/solver/meta_heuristics/critical_path.py b/lekin/solver/meta_heuristics/critical_path.py new file mode 100644 index 0000000..4f88e1a --- /dev/null +++ b/lekin/solver/meta_heuristics/critical_path.py @@ -0,0 +1,70 @@ +"""Critical path""" + +import copy +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + + +class CriticalPathScheduler: + def __init__(self, job_collector): + self.job_collector = job_collector + + def schedule(self): + # Generate the forward and backward pass times + forward_pass_times = self.forward_pass() + backward_pass_times = self.backward_pass() + + # Find the critical path + critical_path = self.find_critical_path(forward_pass_times, backward_pass_times) + + # Assign start times for each operation in the critical path + critical_path_schedule = {} + current_time = 0 + for job_id, operation_id in critical_path: + operation = self.job_collector.get_operation_by_id(job_id, operation_id) + critical_path_schedule[operation] = current_time + current_time += operation.processing_time + + return critical_path_schedule + + def forward_pass(self): + forward_pass_times: Dict[str, Dict[str, float]] = {} + for job in self.job_collector.jobs: + forward_pass_times[job.id] = {} + for operation in job.route.operations: + if operation.id == 0: + forward_pass_times[job.id][operation.id] = 0 + else: + predecessors = operation.get_predecessors() + max_predecessor_time = max( + [forward_pass_times[job.id][pred.id] + pred.processing_time for pred in predecessors] + ) + forward_pass_times[job.id][operation.id] = max_predecessor_time + + return forward_pass_times + + def backward_pass(self): + backward_pass_times: Dict[str, Dict[str, float]] = {} + for job in self.job_collector.jobs: + backward_pass_times[job.id] = {} + for operation in reversed(job.route.operations): + if operation.id == len(job.route.operations) - 1: + backward_pass_times[job.id][operation.id] = operation.processing_time + else: + successors = operation.get_successors() + min_successor_time = min( + [backward_pass_times[job.id][succ.id] + succ.processing_time for succ in successors] + ) + backward_pass_times[job.id][operation.id] = min_successor_time + + return backward_pass_times + + def find_critical_path(self, forward_pass_times, backward_pass_times): + critical_path = [] + for job in self.job_collector.jobs: + for operation in job.route.operations: + start_time = forward_pass_times[job.id][operation.id] + end_time = backward_pass_times[job.id][operation.id] + if start_time + operation.processing_time == end_time: + critical_path.append((job.id, operation.id)) + + return critical_path diff --git a/lekin/solver/meta_heuristics/genetic.py b/lekin/solver/meta_heuristics/genetic.py new file mode 100644 index 0000000..0dba9ba --- /dev/null +++ b/lekin/solver/meta_heuristics/genetic.py @@ -0,0 +1,98 @@ +"""Genetic scheduler""" + +import copy +import random + +from lekin.lekin_struct import JobCollector, ResourceCollector, RouteCollector + + +class GeneticScheduler: + def __init__( + self, + job_collector: JobCollector, + resource_collector: ResourceCollector, + route_collector: RouteCollector = None, + initial_schedule=None, + population_size=50, + generations=1000, + crossover_rate=0.8, + mutation_rate=0.2, + **kwargs, + ): + self.job_collector = job_collector + self.initial_schedule = initial_schedule + self.population_size = population_size + self.generations = generations + self.crossover_rate = crossover_rate + self.mutation_rate = mutation_rate + + def run(self): + population = self.initialize_population() + + for generation in range(self.generations): + selected_individuals = self.selection(population) + new_population = [] + + while len(new_population) < self.population_size: + parent1 = random.choice(selected_individuals) + parent2 = random.choice(selected_individuals) + + if random.random() < self.crossover_rate: + offspring1, offspring2 = self.crossover(parent1, parent2) + else: + offspring1, offspring2 = parent1, parent2 + + if random.random() < self.mutation_rate: + offspring1 = self.mutation(offspring1) + if random.random() < self.mutation_rate: + offspring2 = self.mutation(offspring2) + + new_population.append(offspring1) + new_population.append(offspring2) + + population = new_population + + # Find the best solution in the final population + best_solution = min(population, key=lambda chromosome: self.fitness(chromosome)[0]) + + # Return the best schedule + return self.job_collector.create_schedule_from_operations(best_solution) + + def initialize_population(self): + population = [] + for _ in range(self.population_size): + # Shuffle the operations for each job to create a random chromosome + chromosome = copy.deepcopy(self.job_collector.get_operations()) + for job_operations in chromosome.values(): + random.shuffle(job_operations) + population.append(chromosome) + return population + + def fitness(self, chromosome): + # Calculate the fitness of a chromosome based on the scheduling criteria (e.g., makespan, tardiness) + # The lower the fitness value, the better the solution + # Return a tuple with the fitness value and the schedule + schedule = self.job_collector.create_schedule_from_operations(chromosome) + fitness_value = 0 + return fitness_value, schedule + + def selection(self, population): + # Select the best individuals based on their fitness values + # You can use tournament selection, rank-based selection, or other methods + # Return the selected individuals + selected_individuals = 0 + return selected_individuals + + def crossover(self, parent1, parent2): + # Perform crossover (recombination) between two parents to create two offspring + # You can use one-point crossover, two-point crossover, or other methods + # Return the two offspring + offspring1, offspring2 = 0, 0 + return offspring1, offspring2 + + def mutation(self, chromosome): + # Introduce random changes in the chromosome to add diversity + # You can swap two operations for a randomly selected job + # Return the mutated chromosome + mutated_chromosome = 0 + return mutated_chromosome diff --git a/lekin/solver/meta_heuristics/hill_climbing.py b/lekin/solver/meta_heuristics/hill_climbing.py new file mode 100644 index 0000000..dac23ae --- /dev/null +++ b/lekin/solver/meta_heuristics/hill_climbing.py @@ -0,0 +1,60 @@ +"""Hill climbing""" + +import random + + +class HillClimbingScheduler: + def __init__(self, job_collector): + self.job_collector = job_collector + + def schedule(self, max_iterations=1000): + current_solution = self.random_solution() + current_score = self.evaluate_solution(current_solution) + + for _ in range(max_iterations): + neighbors = self.get_neighbors(current_solution) + if not neighbors: + break + + next_solution = max(neighbors, key=lambda neighbor: self.evaluate_solution(neighbor)) + next_score = self.evaluate_solution(next_solution) + + if next_score <= current_score: + current_solution = next_solution + current_score = next_score + else: + break + + return current_solution + + def random_solution(self): + return { + operation: random.randint(0, operation.get_latest_start_time()) + for job in self.job_collector.jobs + for operation in job.route.operations + } + + def get_neighbors(self, solution): + neighbors = [] + for operation, start_time in solution.items(): + for t in range(start_time - 1, operation.get_latest_start_time() + 1): + neighbor = solution.copy() + neighbor[operation] = t + neighbors.append(neighbor) + return neighbors + + def evaluate_solution(self, solution): + end_times = {} + for job in self.job_collector.jobs: + for operation in job.route.operations: + if operation in solution: + start_time = solution[operation] + else: + start_time = operation.get_latest_start_time() + + end_time = start_time + operation.processing_time + if operation.id not in end_times or end_time > end_times[operation.id]: + end_times[operation.id] = end_time + + makespan = max(end_times.values()) + return makespan diff --git a/lekin/solver/meta_heuristics/nsga3.py b/lekin/solver/meta_heuristics/nsga3.py new file mode 100644 index 0000000..e25a2ee --- /dev/null +++ b/lekin/solver/meta_heuristics/nsga3.py @@ -0,0 +1,87 @@ +import random +from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, Union + + +class NSGA3Scheduler: + def __init__(self, jobs, routes, num_generations, population_size): + self.jobs = jobs + self.routes = routes + self.num_generations = num_generations + self.population_size = population_size + self.population = [] + self.fronts = [] + + def initialize_population(self): + # Generate an initial random population + for _ in range(self.population_size): + solution = self.generate_random_solution() + self.population.append(solution) + + def generate_random_solution(self): + # Generate a random solution representing start times for operations + solution: Dict = {} + for job in self.jobs: + solution[job] = {} + for operation in job.route.operations: + solution[job][operation] = random.randint(0, 100) # Random start time + return solution + + def evaluate_objectives(self, solution): + # Evaluate the objectives for a given solution + makespan = self.calculate_makespan(solution) + total_tardiness = self.calculate_total_tardiness(solution) + resource_utilization = self.calculate_resource_utilization(solution) + return makespan, total_tardiness, resource_utilization + + def calculate_makespan(self, solution): + # Calculate the makespan for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def calculate_total_tardiness(self, solution): + # Calculate the total tardiness for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def calculate_resource_utilization(self, solution): + # Calculate the resource utilization for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def run(self): + self.initialize_population() + for generation in range(self.num_generations): + self.fast_nondominated_sort() + self.crowding_distance() + self.selection() + self.crossover() + self.mutation() + + def fast_nondominated_sort(self): + # Implement NSGA-III's fast non-dominated sorting + # Categorize solutions into different fronts based on their dominance relationships + pass + + def crowding_distance(self): + # Implement NSGA-III's crowding distance calculation + # Calculate the crowding distance for each solution in each front + pass + + def selection(self): + # Implement NSGA-III's selection mechanism + # Select the best solutions based on their non-dominated ranks and crowding distances + pass + + def crossover(self): + # Implement NSGA-III's crossover operator + # Perform crossover to create offspring solutions + pass + + def mutation(self): + # Implement NSGA-III's mutation operator + # Perform mutation to introduce diversity in the population + pass + + def get_pareto_front(self): + # Get the Pareto front solutions after the algorithm has run + return self.fronts[0] diff --git a/lekin/solver/meta_heuristics/pso.py b/lekin/solver/meta_heuristics/pso.py new file mode 100644 index 0000000..e0ce0ce --- /dev/null +++ b/lekin/solver/meta_heuristics/pso.py @@ -0,0 +1 @@ +"""particle swarm optimization""" diff --git a/lekin/solver/meta_heuristics/shifting_bottle_neck.py b/lekin/solver/meta_heuristics/shifting_bottle_neck.py new file mode 100644 index 0000000..8dd031d --- /dev/null +++ b/lekin/solver/meta_heuristics/shifting_bottle_neck.py @@ -0,0 +1,63 @@ +"""Shifting bottle neck meta_heuristics""" + +import copy + + +class ShiftingBottleneckScheduler: + def __init__(self, job_collector): + self.job_collector = job_collector + + def schedule(self): + best_solution = self.generate_initial_solution() + best_cost = self.calculate_cost(best_solution) + + for _ in range(1000): # Number of iterations + current_solution = copy.deepcopy(best_solution) + bottleneck_job, bottleneck_op = self.find_bottleneck_operation(current_solution) + + # Move bottleneck operation to different time slots + for time_slot in range(self.job_collector.max_time): + current_solution[bottleneck_job.id][bottleneck_op.id] = time_slot + current_cost = self.calculate_cost(current_solution) + + if current_cost < best_cost: + best_solution = current_solution + best_cost = current_cost + + return best_solution + + def generate_initial_solution(self): + # Randomly assign operations to time slots + solution = {} + for job in self.job_collector.jobs: + solution[job.id] = {op.id: 0 for op in job.route.operations} + return solution + + def find_bottleneck_operation(self, solution): + # Find the operation with the longest processing time in the schedule + max_processing_time = 0 + bottleneck_job = None + bottleneck_op = None + + for job in self.job_collector.jobs: + for operation in job.route.operations: + processing_time = operation.processing_time + start_time = solution[job.id][operation.id] + end_time = start_time + processing_time + + if end_time > max_processing_time: + max_processing_time = end_time + bottleneck_job = job + bottleneck_op = operation + + return bottleneck_job, bottleneck_op + + def calculate_cost(self, solution): + # Calculate the makespan of the schedule + max_end_time = 0 + for job in self.job_collector.jobs: + end_time = max( + [solution[job.id][operation.id] + operation.processing_time for operation in job.route.operations] + ) + max_end_time = max(max_end_time, end_time) + return max_end_time diff --git a/lekin/solver/meta_heuristics/tabu_search.py b/lekin/solver/meta_heuristics/tabu_search.py new file mode 100644 index 0000000..025d936 --- /dev/null +++ b/lekin/solver/meta_heuristics/tabu_search.py @@ -0,0 +1,74 @@ +""" +Local search +https://towardsdatascience.com/optimization-techniques-tabu-search-36f197ef8e25 +""" + + +import math +import random + + +class TabuSearchScheduler: + def __init__(self, job_collector, max_iterations=1000, tabu_size=20): + self.job_collector = job_collector + self.max_iterations = max_iterations + self.tabu_size = tabu_size + + def schedule(self): + best_solution = self.generate_initial_solution() + best_cost = self.calculate_cost(best_solution) + + tabu_list = [best_solution] + current_solution = best_solution + + for iteration in range(self.max_iterations): + neighbors = self.generate_neighbors(current_solution) + non_tabu_neighbors = [neighbor for neighbor in neighbors if neighbor not in tabu_list] + + if not non_tabu_neighbors: + break + + current_solution = random.choice(non_tabu_neighbors) + current_cost = self.calculate_cost(current_solution) + + if current_cost < best_cost: + best_solution = current_solution + best_cost = current_cost + + tabu_list.append(current_solution) + if len(tabu_list) > self.tabu_size: + tabu_list.pop(0) + + return best_solution + + def generate_initial_solution(self): + # Randomly assign operations to time slots + solution = {} + for job in self.job_collector.jobs: + for operation in job.route.operations: + solution[(job.id, operation.id)] = random.randint( + 0, self.job_collector.max_time - operation.processing_time + ) + return solution + + def generate_neighbors(self, solution): + neighbors = [] + for i in range(len(solution)): + neighbor = solution.copy() + job_id, operation_id = list(neighbor.keys())[i] + operation = self.job_collector.get_operation_by_id(job_id, operation_id) + neighbor[(job_id, operation_id)] = random.randint( + 0, self.job_collector.max_time - operation.processing_time + ) + neighbors.append(neighbor) + return neighbors + + def calculate_cost(self, solution): + # Calculate the makespan of the schedule + max_end_time = 0 + for job in self.job_collector.jobs: + end_time = max( + [solution[(job.id, operation.id)] + operation.processing_time for operation in job.route.operations] + ) + max_end_time = max(max_end_time, end_time) + return max_end_time diff --git a/lekin/solver/meta_heuristics/variable_neighborhood_search.py b/lekin/solver/meta_heuristics/variable_neighborhood_search.py new file mode 100644 index 0000000..57ae2d8 --- /dev/null +++ b/lekin/solver/meta_heuristics/variable_neighborhood_search.py @@ -0,0 +1,89 @@ +"""Variable neighborhood search""" + +import random + + +class VNSScheduler: + def __init__(self, jobs, routes, max_iterations, neighborhood_size): + self.jobs = jobs + self.routes = routes + self.max_iterations = max_iterations + self.neighborhood_size = neighborhood_size + + def initialize_solution(self): + # Generate an initial random solution representing start times for operations + solution = {} + for job in self.jobs: + solution[job] = {} + for operation in job.route.operations: + solution[job][operation] = random.randint(0, 100) # Random start time + return solution + + def evaluate_objectives(self, solution): + # Evaluate the objectives for a given solution + makespan = self.calculate_makespan(solution) + total_tardiness = self.calculate_total_tardiness(solution) + resource_utilization = self.calculate_resource_utilization(solution) + return makespan, total_tardiness, resource_utilization + + def calculate_makespan(self, solution): + # Calculate the makespan for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def calculate_total_tardiness(self, solution): + # Calculate the total tardiness for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def calculate_resource_utilization(self, solution): + # Calculate the resource utilization for a given solution + # Implementation specific to your job shop scheduling problem + pass + + def generate_neighbor(self, current_solution): + # Generate a neighbor solution by perturbing the current solution + # You can use different perturbation techniques like swap, insert, etc. + # based on your specific problem requirements + neighbor_solution = current_solution.copy() + # Implement your perturbation here + return neighbor_solution + + def accept_neighbor(self, current_solution, neighbor_solution, temperature): + # Decide whether to accept the neighbor solution based on acceptance criteria + # For VNS, you can use simulated annealing-like acceptance probability + # based on the difference in objective values and the current temperature + current_objectives = self.evaluate_objectives(current_solution) + neighbor_objectives = self.evaluate_objectives(neighbor_solution) + current_cost = self.calculate_cost(current_objectives) + neighbor_cost = self.calculate_cost(neighbor_objectives) + + if neighbor_cost < current_cost: + return True + else: + acceptance_prob = min(1, pow(2.71, -(neighbor_cost - current_cost) / temperature)) + return random.random() < acceptance_prob + + def calculate_cost(self, objectives): + # Calculate the cost for a set of objectives + # You can define a weighted sum, weighted sum of ranks, or other measures + # based on your specific problem requirements and preferences + # For example, you can use a weighted sum of makespan and total tardiness + weight_makespan = 1 + weight_tardiness = 1 + return weight_makespan * objectives[0] + weight_tardiness * objectives[1] + + def run(self): + current_solution = self.initialize_solution() + temperature = 100 # Initial temperature for simulated annealing + iteration = 0 + + while iteration < self.max_iterations: + for _ in range(self.neighborhood_size): + neighbor_solution = self.generate_neighbor(current_solution) + if self.accept_neighbor(current_solution, neighbor_solution, temperature): + current_solution = neighbor_solution + temperature *= 0.95 # Reduce temperature for simulated annealing + iteration += 1 + + return current_solution diff --git a/lekin/solver/operation_research/__init__.py b/lekin/solver/operation_research/__init__.py new file mode 100644 index 0000000..8b878dd --- /dev/null +++ b/lekin/solver/operation_research/__init__.py @@ -0,0 +1 @@ +"""Operation research""" diff --git a/lekin/solver/operation_research/ortool.py b/lekin/solver/operation_research/ortool.py new file mode 100644 index 0000000..5d1c26a --- /dev/null +++ b/lekin/solver/operation_research/ortool.py @@ -0,0 +1,69 @@ +from ortools.sat.python import cp_model + + +class ORToolsScheduler: + def __init__(self, job_collector): + self.job_collector = job_collector + self.model = cp_model.CpModel() + self.vars = {} + + def schedule(self): + self.create_variables() + self.add_constraints() + self.add_objective() + solver = cp_model.CpSolver() + status = solver.Solve(self.model) + if status == cp_model.OPTIMAL: + return self.get_schedule(solver) + else: + return None + + def create_variables(self): + for job in self.job_collector.jobs: + for operation in job.route.operations: + self.vars[operation.id] = self.model.NewIntVar( + 0, self.job_collector.max_time, f"Operation_{operation.id}_Start" + ) + + def add_constraints(self): + for job in self.job_collector.jobs: + for i, operation in enumerate(job.route.operations): + # Constraint: Each operation starts after the end of its parent operation + if i > 0: + parent_operation = job.route.operations[i - 1] + self.model.Add( + self.vars[operation.id] >= self.vars[parent_operation.id] + parent_operation.processing_time + ) + + # Constraint: Each operation must be finished before the job's demand date + self.model.Add(self.vars[operation.id] + operation.processing_time <= job.demand_date) + + for resource in self.job_collector.resources: + for timeslot in resource.timeslots: + for job in self.job_collector.jobs: + for operation in job.route.operations: + # Constraint: The operation must start within the resource's available timeslots + self.model.Add(self.vars[operation.id] >= timeslot.start_time).OnlyEnforceIf(timeslot.is_used) + self.model.Add( + self.vars[operation.id] <= timeslot.end_time - operation.processing_time + ).OnlyEnforceIf(timeslot.is_used) + + def add_objective(self): + objective_var = self.model.NewIntVar(0, self.job_collector.max_time, "Makespan") + self.model.AddMaxEquality( + objective_var, + [ + self.vars[operation.id] + operation.processing_time + for job in self.job_collector.jobs + for operation in job.route.operations + ], + ) + self.model.Minimize(objective_var) + + def get_schedule(self, solver): + schedule = {} + for job in self.job_collector.jobs: + for operation in job.route.operations: + start_time = solver.Value(self.vars[operation.id]) + schedule[(job.id, operation.id)] = start_time + return schedule diff --git a/lekin/solver/reinforcement_learning/__init__.py b/lekin/solver/reinforcement_learning/__init__.py new file mode 100644 index 0000000..47e826e --- /dev/null +++ b/lekin/solver/reinforcement_learning/__init__.py @@ -0,0 +1 @@ +"""Reinforcement learning""" diff --git a/lekin/solver/reinforcement_learning/dqn.py b/lekin/solver/reinforcement_learning/dqn.py new file mode 100644 index 0000000..751fc42 --- /dev/null +++ b/lekin/solver/reinforcement_learning/dqn.py @@ -0,0 +1,99 @@ +import random + +import numpy as np +import tensorflow as tf + + +class DeepQLearningScheduler: + def __init__( + self, + job_collector, + state_shape, + action_shape, + learning_rate=0.001, + gamma=0.99, + epsilon=1.0, + epsilon_decay=0.995, + epsilon_min=0.01, + ): + self.job_collector = job_collector + self.state_shape = state_shape + self.action_shape = action_shape + self.learning_rate = learning_rate + self.gamma = gamma + self.epsilon = epsilon + self.epsilon_decay = epsilon_decay + self.epsilon_min = epsilon_min + + self.memory = [] + self.q_network = self.build_q_network() + self.target_q_network = self.build_q_network() + self.update_target_network() + + def build_q_network(self): + model = tf.keras.Sequential( + [ + tf.keras.layers.Dense(64, activation="relu", input_shape=self.state_shape), + tf.keras.layers.Dense(64, activation="relu"), + tf.keras.layers.Dense(np.prod(self.action_shape), activation="linear"), + ] + ) + model.compile(optimizer=tf.keras.optimizers.Adam(lr=self.learning_rate), loss="mse") + return model + + def update_target_network(self): + self.target_q_network.set_weights(self.q_network.get_weights()) + + def get_action(self, state): + if np.random.rand() <= self.epsilon: + return np.random.randint(0, np.prod(self.action_shape)) + return np.argmax(self.q_network.predict(np.array([state]))) + + def remember(self, state, action, reward, next_state, done): + self.memory.append((state, action, reward, next_state, done)) + + def replay(self, batch_size): + if len(self.memory) < batch_size: + return + + batch = random.sample(self.memory, batch_size) + + states = np.array([experience[0] for experience in batch]) + actions = np.array([experience[1] for experience in batch]) + rewards = np.array([experience[2] for experience in batch]) + next_states = np.array([experience[3] for experience in batch]) + dones = np.array([experience[4] for experience in batch]) + + targets = rewards + self.gamma * (1 - dones) * np.amax(self.target_q_network.predict(next_states), axis=1) + target_f = self.q_network.predict(states) + target_f[np.arange(batch_size), actions] = targets + + self.q_network.fit(states, target_f, epochs=1, verbose=0) + + if self.epsilon > self.epsilon_min: + self.epsilon *= self.epsilon_decay + + def train(self, episodes, batch_size): + for episode in range(episodes): + state = self.job_collector.get_state() + done = False + + while not done: + action = self.get_action(state) + next_state, reward, done = self.job_collector.step(action) + self.remember(state, action, reward, next_state, done) + state = next_state + + if len(self.memory) > batch_size: + self.replay(batch_size) + + if episode % 10 == 0: + self.update_target_network() + + def get_schedule(self): + state = self.job_collector.get_state() + done = False + while not done: + action = np.argmax(self.q_network.predict(np.array([state]))) + state, _, done = self.job_collector.step(action) + return self.job_collector.get_schedule() diff --git a/lekin/solver/utils/__init__.py b/lekin/solver/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/solver/utils/push_dense.py b/lekin/solver/utils/push_dense.py new file mode 100644 index 0000000..425528e --- /dev/null +++ b/lekin/solver/utils/push_dense.py @@ -0,0 +1,28 @@ +def push_dense(schedule): + changed = True + + while changed: + changed = False + + for resource in schedule.resources: + slots = sorted(resource.slots, key=lambda x: x.start_time) + + for i in range(len(slots) - 1): + curr_slot = slots[i] + next_slot = slots[i + 1] + + if curr_slot.end_time < next_slot.start_time: + # Gap exists between operations + + gap = next_slot.start_time - curr_slot.end_time + + if next_slot.operation.can_start_early(gap): + # Update slots + next_slot.start_time -= gap + next_slot.end_time -= gap + + curr_slot.end_time = next_slot.start_time + + changed = True + + return schedule diff --git a/lekin/utils/__init__.py b/lekin/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lekin/utils/group_op_ds.py b/lekin/utils/group_op_ds.py new file mode 100644 index 0000000..0f27e13 --- /dev/null +++ b/lekin/utils/group_op_ds.py @@ -0,0 +1,67 @@ +""" +新加入的时候可以快速找到该节点,因此之前用了dict[id, MaterialOP] + +移动的时候, 先标记一些candidate + +找到一个candidate, 往前找之前的插入位置 + +重新整理 + +""" + + +class DictNode: + def __init__(self, key, value): + self.key = key + self.value = value + self.prev = None + self.next = None + + +from collections import OrderedDict + +class IndexedList: + def __init__(self): + self.ordered_dict = OrderedDict() + self.order_list = [] + + def insert_at_index(self, index, key, value): + if index < 0 or index > len(self.order_list): + raise IndexError("Index out of bounds") + + self.ordered_dict[key] = value + self.order_list.insert(index, key) + + def insert_after(self, key, new_key, new_value): + if key is None: + # Insert at the beginning + self.ordered_dict[new_key] = new_value + elif key in self.ordered_dict: + # Insert after the specified key + items = list(self.ordered_dict.items()) + index = next((i for i, (k, v) in enumerate(items) if k == key), -1) + if index != -1: + items.insert(index + 1, (new_key, new_value)) + self.ordered_dict = OrderedDict(items) + else: + raise KeyError(f"Key '{key}' not found in the indexed list") + else: + raise KeyError(f"Key '{key}' not found in the indexed list") + + def display(self): + for key, value in self.ordered_dict.items(): + print(f"({key}: {value})", end=" ") + print() + + +# Example Usage: +indexed_list = IndexedList() +indexed_list.insert_at_index(0, 'a', 1) +indexed_list.insert_at_index(1, 'b', 2) +indexed_list.insert_at_index(1, 'c', 3) +indexed_list.display() # Output: (a: 1) (c: 3) (b: 2) + +indexed_list.insert_at_index(2, 'd', 4) +indexed_list.display() # Output: (a: 1) (c: 3) (d: 4) (b: 2) + + diff --git a/lekin/utils/push_dense.py b/lekin/utils/push_dense.py new file mode 100644 index 0000000..a3de2b8 --- /dev/null +++ b/lekin/utils/push_dense.py @@ -0,0 +1,17 @@ +# push dense +# for i, (id, resource) in enumerate(resource_collector.resources_dict.items()): +# logging.info( +# f'Start to push {i + 1}/{len(resource_collector.resources_dict)} resources' +# ) +# assigned_ops = resource.assigned_operations +# assigned_ops.sort() +# for op in assigned_ops: +# buffer_push = op.buffer +# if buffer_push > resource.available_capacity: +# buffer_push = resource.available_capacity + +# op.start_time -= buffer_push + +# # last chance for remaining jobs +# for job in self.unassigned_jobs: +# logging.warning(f'Abnornal! No scheduling for job {job.job_id}') diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..d0daceb --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2669 @@ +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. + +[[package]] +name = "accessible-pygments" +version = "0.0.4" +description = "A collection of accessible pygments styles" +optional = false +python-versions = "*" +files = [ + {file = "accessible-pygments-0.0.4.tar.gz", hash = "sha256:e7b57a9b15958e9601c7e9eb07a440c813283545a20973f2574a5f453d0e953e"}, + {file = "accessible_pygments-0.0.4-py2.py3-none-any.whl", hash = "sha256:416c6d8c1ea1c5ad8701903a20fcedf953c6e720d64f33dc47bfb2d3f2fa4e8d"}, +] + +[package.dependencies] +pygments = ">=1.5" + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = "*" +files = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] + +[[package]] +name = "astroid" +version = "2.11.7" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "astroid-2.11.7-py3-none-any.whl", hash = "sha256:86b0a340a512c65abf4368b80252754cda17c02cdbbd3f587dddf98112233e7b"}, + {file = "astroid-2.11.7.tar.gz", hash = "sha256:bb24615c77f4837c707669d16907331374ae8a964650a66999da3f5ca68dc946"}, +] + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +setuptools = ">=20.0" +typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = ">=1.11,<2" + +[[package]] +name = "attrs" +version = "23.1.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, + {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] +dev = ["attrs[docs,tests]", "pre-commit"] +docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] +tests = ["attrs[tests-no-zope]", "zope-interface"] +tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] + +[[package]] +name = "babel" +version = "2.12.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"}, + {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.12.2" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, + {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "23.3.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.7" +files = [ + {file = "black-23.3.0-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9"}, + {file = "black-23.3.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2"}, + {file = "black-23.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c"}, + {file = "black-23.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b"}, + {file = "black-23.3.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d"}, + {file = "black-23.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70"}, + {file = "black-23.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326"}, + {file = "black-23.3.0-cp37-cp37m-macosx_10_16_x86_64.whl", hash = "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b"}, + {file = "black-23.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2"}, + {file = "black-23.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331"}, + {file = "black-23.3.0-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5"}, + {file = "black-23.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961"}, + {file = "black-23.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"}, + {file = "black-23.3.0-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266"}, + {file = "black-23.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab"}, + {file = "black-23.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb"}, + {file = "black-23.3.0-py3-none-any.whl", hash = "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4"}, + {file = "black-23.3.0.tar.gz", hash = "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940"}, +] + +[package.dependencies] +click = ">=8.0.0" +ipython = {version = ">=7.8.0", optional = true, markers = "extra == \"jupyter\""} +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tokenize-rt = {version = ">=3.2.0", optional = true, markers = "extra == \"jupyter\""} +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "bleach" +version = "6.0.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.7" +files = [ + {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, + {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, +] + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +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 = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.1.4" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.6" +files = [ + {file = "comm-0.1.4-py3-none-any.whl", hash = "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a"}, + {file = "comm-0.1.4.tar.gz", hash = "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] +test = ["pytest"] +typing = ["mypy (>=0.990)"] + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +optional = false +python-versions = "*" +files = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "coverage" +version = "7.2.7" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8"}, + {file = "coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7d9405291c6928619403db1d10bd07888888ec1abcbd9748fdaa971d7d661b2"}, + {file = "coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ebba1cd308ef115925421d3e6a586e655ca5a77b5bf41e02eb0e4562a111f2d1"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cb017fd1b2603ef59e374ba2063f593abe0fc45f2ad9abdde5b4d83bd922a353"}, + {file = "coverage-7.2.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62a5c7dad11015c66fbb9d881bc4caa5b12f16292f857842d9d1871595f4495"}, + {file = "coverage-7.2.7-cp310-cp310-win32.whl", hash = "sha256:ee57190f24fba796e36bb6d3aa8a8783c643d8fa9760c89f7a98ab5455fbf818"}, + {file = "coverage-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:f75f7168ab25dd93110c8a8117a22450c19976afbc44234cbf71481094c1b850"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f"}, + {file = "coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52edc1a60c0d34afa421c9c37078817b2e67a392cab17d97283b64c5833f427f"}, + {file = "coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:afb17f84d56068a7c29f5fa37bfd38d5aba69e3304af08ee94da8ed5b0865833"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:48c19d2159d433ccc99e729ceae7d5293fbffa0bdb94952d3579983d1c8c9d97"}, + {file = "coverage-7.2.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e1f928eaf5469c11e886fe0885ad2bf1ec606434e79842a879277895a50942a"}, + {file = "coverage-7.2.7-cp311-cp311-win32.whl", hash = "sha256:33d6d3ea29d5b3a1a632b3c4e4f4ecae24ef170b0b9ee493883f2df10039959a"}, + {file = "coverage-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:5b7540161790b2f28143191f5f8ec02fb132660ff175b7747b95dcb77ac26562"}, + {file = "coverage-7.2.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2f67fe12b22cd130d34d0ef79206061bfb5eda52feb6ce0dba0644e20a03cf4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a342242fe22407f3c17f4b499276a02b01e80f861f1682ad1d95b04018e0c0d4"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:171717c7cb6b453aebac9a2ef603699da237f341b38eebfee9be75d27dc38e01"}, + {file = "coverage-7.2.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49969a9f7ffa086d973d91cec8d2e31080436ef0fb4a359cae927e742abfaaa6"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b46517c02ccd08092f4fa99f24c3b83d8f92f739b4657b0f146246a0ca6a831d"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a3d33a6b3eae87ceaefa91ffdc130b5e8536182cd6dfdbfc1aa56b46ff8c86de"}, + {file = "coverage-7.2.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:976b9c42fb2a43ebf304fa7d4a310e5f16cc99992f33eced91ef6f908bd8f33d"}, + {file = "coverage-7.2.7-cp312-cp312-win32.whl", hash = "sha256:8de8bb0e5ad103888d65abef8bca41ab93721647590a3f740100cd65c3b00511"}, + {file = "coverage-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9e31cb64d7de6b6f09702bb27c02d1904b3aebfca610c12772452c4e6c21a0d3"}, + {file = "coverage-7.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58c2ccc2f00ecb51253cbe5d8d7122a34590fac9646a960d1430d5b15321d95f"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d22656368f0e6189e24722214ed8d66b8022db19d182927b9a248a2a8a2f67eb"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a895fcc7b15c3fc72beb43cdcbdf0ddb7d2ebc959edac9cef390b0d14f39f8a9"}, + {file = "coverage-7.2.7-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84606b74eb7de6ff581a7915e2dab7a28a0517fbe1c9239eb227e1354064dcd"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0a5f9e1dbd7fbe30196578ca36f3fba75376fb99888c395c5880b355e2875f8a"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:419bfd2caae268623dd469eff96d510a920c90928b60f2073d79f8fe2bbc5959"}, + {file = "coverage-7.2.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2aee274c46590717f38ae5e4650988d1af340fe06167546cc32fe2f58ed05b02"}, + {file = "coverage-7.2.7-cp37-cp37m-win32.whl", hash = "sha256:61b9a528fb348373c433e8966535074b802c7a5d7f23c4f421e6c6e2f1697a6f"}, + {file = "coverage-7.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b1c546aca0ca4d028901d825015dc8e4d56aac4b541877690eb76490f1dc8ed0"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5"}, + {file = "coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e9d683426464e4a252bf70c3498756055016f99ddaec3774bf368e76bbe02b6"}, + {file = "coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7aa5f8a41217360e600da646004f878250a0d6738bcdc11a0a39928d7dc2050"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fa03bce9bfbeeef9f3b160a8bed39a221d82308b4152b27d82d8daa7041fee5"}, + {file = "coverage-7.2.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:245167dd26180ab4c91d5e1496a30be4cd721a5cf2abf52974f965f10f11419f"}, + {file = "coverage-7.2.7-cp38-cp38-win32.whl", hash = "sha256:d2c2db7fd82e9b72937969bceac4d6ca89660db0a0967614ce2481e81a0b771e"}, + {file = "coverage-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:2e07b54284e381531c87f785f613b833569c14ecacdcb85d56b25c4622c16c3c"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9"}, + {file = "coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6951407391b639504e3b3be51b7ba5f3528adbf1a8ac3302b687ecababf929e"}, + {file = "coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b29019c76039dc3c0fd815c41392a044ce555d9bcdd38b0fb60fb4cd8e475ba9"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:81c13a1fc7468c40f13420732805a4c38a105d89848b7c10af65a90beff25250"}, + {file = "coverage-7.2.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:975d70ab7e3c80a3fe86001d8751f6778905ec723f5b110aed1e450da9d4b7f2"}, + {file = "coverage-7.2.7-cp39-cp39-win32.whl", hash = "sha256:7ee7d9d4822c8acc74a5e26c50604dff824710bc8de424904c0982e25c39c6cb"}, + {file = "coverage-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:eb393e5ebc85245347950143969b241d08b52b88a3dc39479822e073a1a8eb27"}, + {file = "coverage-7.2.7-pp37.pp38.pp39-none-any.whl", hash = "sha256:b7b4c971f05e6ae490fef852c218b0e79d4e52f79ef0c8475566584a8fb3e01d"}, + {file = "coverage-7.2.7.tar.gz", hash = "sha256:924d94291ca674905fe9481f12294eb11f2d3d3fd1adb20314ba89e94f44ed59"}, +] + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +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 = "debugpy" +version = "1.7.0" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "debugpy-1.7.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:17ad9a681aca1704c55b9a5edcb495fa8f599e4655c9872b7f9cf3dc25890d48"}, + {file = "debugpy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1285920a3f9a75f5d1acf59ab1b9da9ae6eb9a05884cd7674f95170c9cafa4de"}, + {file = "debugpy-1.7.0-cp310-cp310-win32.whl", hash = "sha256:a6f43a681c5025db1f1c0568069d1d1bad306a02e7c36144912b26d9c90e4724"}, + {file = "debugpy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e9571d831ad3c75b5fb6f3efcb71c471cf2a74ba84af6ac1c79ce00683bed4b"}, + {file = "debugpy-1.7.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:538765a41198aa88cc089295b39c7322dd598f9ef1d52eaae12145c63bf9430a"}, + {file = "debugpy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e8cf91f8f3f9b5fad844dd88427b85d398bda1e2a0cd65d5a21312fcbc0c6f"}, + {file = "debugpy-1.7.0-cp311-cp311-win32.whl", hash = "sha256:18a69f8e142a716310dd0af6d7db08992aed99e2606108732efde101e7c65e2a"}, + {file = "debugpy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7515a5ba5ee9bfe956685909c5f28734c1cecd4ee813523363acfe3ca824883a"}, + {file = "debugpy-1.7.0-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:bc8da67ade39d9e75608cdb8601d07e63a4e85966e0572c981f14e2cf42bcdef"}, + {file = "debugpy-1.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5036e918c6ba8fc4c4f1fd0207d81db634431a02f0dc2ba51b12fd793c8c9de"}, + {file = "debugpy-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:d5be95b3946a4d7b388e45068c7b75036ac5a610f41014aee6cafcd5506423ad"}, + {file = "debugpy-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0e90314a078d4e3f009520c8387aba8f74c3034645daa7a332a3d1bb81335756"}, + {file = "debugpy-1.7.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:1565fd904f9571c430adca597771255cff4f92171486fced6f765dcbdfc8ec8d"}, + {file = "debugpy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6516f36a2e95b3be27f171f12b641e443863f4ad5255d0fdcea6ae0be29bb912"}, + {file = "debugpy-1.7.0-cp38-cp38-win32.whl", hash = "sha256:2b0e489613bc066051439df04c56777ec184b957d6810cb65f235083aef7a0dc"}, + {file = "debugpy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:7bf0b4bbd841b2397b6a8de15da9227f1164f6d43ceee971c50194eaed930a9d"}, + {file = "debugpy-1.7.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:ad22e1095b9977af432465c1e09132ba176e18df3834b1efcab1a449346b350b"}, + {file = "debugpy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f625e427f21423e5874139db529e18cb2966bdfcc1cb87a195538c5b34d163d1"}, + {file = "debugpy-1.7.0-cp39-cp39-win32.whl", hash = "sha256:18bca8429d6632e2d3435055416d2d88f0309cc39709f4f6355c8d412cc61f24"}, + {file = "debugpy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:dc8a12ac8b97ef3d6973c6679a093138c7c9b03eb685f0e253269a195f651559"}, + {file = "debugpy-1.7.0-py2.py3-none-any.whl", hash = "sha256:f6de2e6f24f62969e0f0ef682d78c98161c4dca29e9fb05df4d2989005005502"}, + {file = "debugpy-1.7.0.zip", hash = "sha256:676911c710e85567b17172db934a71319ed9d995104610ce23fd74a07f66e6f6"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "dill" +version = "0.3.7" +description = "serialize all of Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"}, + {file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distlib" +version = "0.3.7" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, + {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, +] + +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + +[[package]] +name = "entrypoints" +version = "0.4" +description = "Discover and load entry points from installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, + {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, +] + +[[package]] +name = "fastjsonschema" +version = "2.18.0" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.18.0-py3-none-any.whl", hash = "sha256:128039912a11a807068a7c87d0da36660afbfd7202780db26c4aa7153cfdc799"}, + {file = "fastjsonschema-2.18.0.tar.gz", hash = "sha256:e820349dd16f806e4bd1467a138dced9def4bc7d6213a34295272a6cac95b5bd"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.12.2" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.7" +files = [ + {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, + {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, +] + +[package.extras] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "fonttools" +version = "4.38.0" +description = "Tools to manipulate font files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fonttools-4.38.0-py3-none-any.whl", hash = "sha256:820466f43c8be8c3009aef8b87e785014133508f0de64ec469e4efb643ae54fb"}, + {file = "fonttools-4.38.0.zip", hash = "sha256:2bb244009f9bf3fa100fc3ead6aeb99febe5985fa20afbfbaa2f8946c2fbdaf1"}, +] + +[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 (>=14.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 (>=14.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "identify" +version = "2.5.24" +description = "File identification library for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "identify-2.5.24-py2.py3-none-any.whl", hash = "sha256:986dbfb38b1140e763e413e6feb44cd731faf72d1909543178aa79b0e258265d"}, + {file = "identify-2.5.24.tar.gz", hash = "sha256:0aac67d5b4812498056d28a9a512a483f5085cc28640b02b258a59dac34301d4"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +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 = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, + {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +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 = ["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-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "importlib-resources" +version = "5.12.0" +description = "Read resources from Python packages" +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 = "invoke" +version = "2.2.0" +description = "Pythonic task execution" +optional = false +python-versions = ">=3.6" +files = [ + {file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, + {file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, +] + +[[package]] +name = "ipykernel" +version = "6.16.2" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipykernel-6.16.2-py3-none-any.whl", hash = "sha256:67daf93e5b52456cd8eea87a8b59405d2bb80ae411864a1ea206c3631d8179af"}, + {file = "ipykernel-6.16.2.tar.gz", hash = "sha256:463f3d87a92e99969b1605cb7a5b4d7b36b7145a0e72d06e65918a6ddefbe630"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +debugpy = ">=1.0" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=17" +tornado = ">=6.1" +traitlets = ">=5.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "7.34.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, + {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" +setuptools = ">=18.5" +traitlets = ">=4.2" + +[package.extras] +all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] + +[[package]] +name = "ipywidgets" +version = "8.1.1" +description = "Jupyter interactive widgets" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ipywidgets-8.1.1-py3-none-any.whl", hash = "sha256:2b88d728656aea3bbfd05d32c747cfd0078f9d7e159cf982433b58ad717eed7f"}, + {file = "ipywidgets-8.1.1.tar.gz", hash = "sha256:40211efb556adec6fa450ccc2a77d59ca44a060f4f9f136833df59c9f538e6e8"}, +] + +[package.dependencies] +comm = ">=0.1.3" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0.9,<3.1.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0.9,<4.1.0" + +[package.extras] +test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isort" +version = "5.11.5" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "isort-5.11.5-py3-none-any.whl", hash = "sha256:ba1d72fb2595a01c7895a5128f9585a5cc4b6d395f1c8d514989b9a7eb2a8746"}, + {file = "isort-5.11.5.tar.gz", hash = "sha256:6be1f76a507cb2ecf16c7cf14a37e41609ca082330be4e3436a18ef74add55db"}, +] + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "jedi" +version = "0.19.0" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.0-py2.py3-none-any.whl", hash = "sha256:cb8ce23fbccff0025e9386b5cf85e892f94c9b822378f8da49970471335ac64e"}, + {file = "jedi-0.19.0.tar.gz", hash = "sha256:bcf9894f1753969cbac8022a8c2eaee06bfa3724e4192470aaffe7eb6272b0c4"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django (<3.1)", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +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 = "jsonschema" +version = "4.17.3" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, + {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jupyter-client" +version = "7.4.9" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_client-7.4.9-py3-none-any.whl", hash = "sha256:214668aaea208195f4c13d28eb272ba79f945fc0cf3f11c7092c20b2ca1980e7"}, + {file = "jupyter_client-7.4.9.tar.gz", hash = "sha256:52be28e04171f07aed8f20e1616a5a552ab9fee9cbbe6c1896ae170c3880d392"}, +] + +[package.dependencies] +entrypoints = "*" +jupyter-core = ">=4.9.2" +nest-asyncio = ">=1.5.4" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = "*" + +[package.extras] +doc = ["ipykernel", "myst-parser", "sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-commit", "pytest", "pytest-asyncio (>=0.18)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "4.12.0" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyter_core-4.12.0-py3-none-any.whl", hash = "sha256:a54672c539333258495579f6964144924e0aa7b07f7069947bef76d7ea5cb4c1"}, + {file = "jupyter_core-4.12.0.tar.gz", hash = "sha256:87f39d7642412ae8a52291cc68e71ac01dfa2c735df2701f8108251d51b4f460"}, +] + +[package.dependencies] +pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = "*" + +[package.extras] +test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.2.2" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.9" +description = "Jupyter interactive widgets for JupyterLab" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jupyterlab_widgets-3.0.9-py3-none-any.whl", hash = "sha256:3cf5bdf5b897bf3bccf1c11873aa4afd776d7430200f765e0686bd352487b58d"}, + {file = "jupyterlab_widgets-3.0.9.tar.gz", hash = "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c"}, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +description = "A fast implementation of the Cassowary constraint solver" +optional = false +python-versions = ">=3.7" +files = [ + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, + {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, + {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, + {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, + {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, + {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, + {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, + {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, + {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, + {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, + {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, + {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, + {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, + {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, + {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, + {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, + {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, + {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, + {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, + {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, + {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, + {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, + {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, + {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "lazy-object-proxy" +version = "1.9.0" +description = "A fast and thorough lazy object proxy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"}, + {file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"}, + {file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"}, + {file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"}, + {file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"}, + {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, +] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "matplotlib" +version = "3.5.3" +description = "Python plotting package" +optional = false +python-versions = ">=3.7" +files = [ + {file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a206a1b762b39398efea838f528b3a6d60cdb26fe9d58b48265787e29cd1d693"}, + {file = "matplotlib-3.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd45a6f3e93a780185f70f05cf2a383daed13c3489233faad83e81720f7ede24"}, + {file = "matplotlib-3.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d62880e1f60e5a30a2a8484432bcb3a5056969dc97258d7326ad465feb7ae069"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ab29589cef03bc88acfa3a1490359000c18186fc30374d8aa77d33cc4a51a4a"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2886cc009f40e2984c083687251821f305d811d38e3df8ded414265e4583f0c5"}, + {file = "matplotlib-3.5.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c995f7d9568f18b5db131ab124c64e51b6820a92d10246d4f2b3f3a66698a15b"}, + {file = "matplotlib-3.5.3-cp310-cp310-win32.whl", hash = "sha256:6bb93a0492d68461bd458eba878f52fdc8ac7bdb6c4acdfe43dba684787838c2"}, + {file = "matplotlib-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:2e6d184ebe291b9e8f7e78bbab7987d269c38ea3e062eace1fe7d898042ef804"}, + {file = "matplotlib-3.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6ea6aef5c4338e58d8d376068e28f80a24f54e69f09479d1c90b7172bad9f25b"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:839d47b8ead7ad9669aaacdbc03f29656dc21f0d41a6fea2d473d856c39c8b1c"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3b4fa56159dc3c7f9250df88f653f085068bcd32dcd38e479bba58909254af7f"}, + {file = "matplotlib-3.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:94ff86af56a3869a4ae26a9637a849effd7643858a1a04dd5ee50e9ab75069a7"}, + {file = "matplotlib-3.5.3-cp37-cp37m-win32.whl", hash = "sha256:35a8ad4dddebd51f94c5d24bec689ec0ec66173bf614374a1244c6241c1595e0"}, + {file = "matplotlib-3.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:43e9d3fa077bf0cc95ded13d331d2156f9973dce17c6f0c8b49ccd57af94dbd9"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:22227c976ad4dc8c5a5057540421f0d8708c6560744ad2ad638d48e2984e1dbc"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf618a825deb6205f015df6dfe6167a5d9b351203b03fab82043ae1d30f16511"}, + {file = "matplotlib-3.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9befa5954cdbc085e37d974ff6053da269474177921dd61facdad8023c4aeb51"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3840c280ebc87a48488a46f760ea1c0c0c83fcf7abbe2e6baf99d033fd35fd8"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dacddf5bfcec60e3f26ec5c0ae3d0274853a258b6c3fc5ef2f06a8eb23e042be"}, + {file = "matplotlib-3.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:b428076a55fb1c084c76cb93e68006f27d247169f056412607c5c88828d08f88"}, + {file = "matplotlib-3.5.3-cp38-cp38-win32.whl", hash = "sha256:874df7505ba820e0400e7091199decf3ff1fde0583652120c50cd60d5820ca9a"}, + {file = "matplotlib-3.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:b28de401d928890187c589036857a270a032961411934bdac4cf12dde3d43094"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3211ba82b9f1518d346f6309df137b50c3dc4421b4ed4815d1d7eadc617f45a1"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fe807e8a22620b4cd95cfbc795ba310dc80151d43b037257250faf0bfcd82bc"}, + {file = "matplotlib-3.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5c096363b206a3caf43773abebdbb5a23ea13faef71d701b21a9c27fdcef72f4"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcdfcb0f976e1bac6721d7d457c17be23cf7501f977b6a38f9d38a3762841f7"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e64ac9be9da6bfff0a732e62116484b93b02a0b4d4b19934fb4f8e7ad26ad6a"}, + {file = "matplotlib-3.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:73dd93dc35c85dece610cca8358003bf0760d7986f70b223e2306b4ea6d1406b"}, + {file = "matplotlib-3.5.3-cp39-cp39-win32.whl", hash = "sha256:879c7e5fce4939c6aa04581dfe08d57eb6102a71f2e202e3314d5fbc072fd5a0"}, + {file = "matplotlib-3.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:ab8d26f07fe64f6f6736d635cce7bfd7f625320490ed5bfc347f2cdb4fae0e56"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:99482b83ebf4eb6d5fc6813d7aacdefdd480f0d9c0b52dcf9f1cc3b2c4b3361a"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f814504e459c68118bf2246a530ed953ebd18213dc20e3da524174d84ed010b2"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57f1b4e69f438a99bb64d7f2c340db1b096b41ebaa515cf61ea72624279220ce"}, + {file = "matplotlib-3.5.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:d2484b350bf3d32cae43f85dcfc89b3ed7bd2bcd781ef351f93eb6fb2cc483f9"}, + {file = "matplotlib-3.5.3.tar.gz", hash = "sha256:339cac48b80ddbc8bfd05daae0a3a73414651a8596904c2a881cfd1edb65f26c"}, +] + +[package.dependencies] +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.17" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.5" +files = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mistune" +version = "3.0.1" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mistune-3.0.1-py3-none-any.whl", hash = "sha256:b9b3e438efbb57c62b5beb5e134dab664800bdf1284a7ee09e8b12b13eb1aac6"}, + {file = "mistune-3.0.1.tar.gz", hash = "sha256:e912116c13aa0944f9dc530db38eb88f6a77087ab128f49f84a48f4c05ea163c"}, +] + +[[package]] +name = "mypy" +version = "1.4.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8"}, + {file = "mypy-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878"}, + {file = "mypy-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd"}, + {file = "mypy-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc"}, + {file = "mypy-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462"}, + {file = "mypy-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258"}, + {file = "mypy-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2"}, + {file = "mypy-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7"}, + {file = "mypy-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01"}, + {file = "mypy-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b"}, + {file = "mypy-1.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b"}, + {file = "mypy-1.4.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7"}, + {file = "mypy-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042"}, + {file = "mypy-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3"}, + {file = "mypy-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6"}, + {file = "mypy-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f"}, + {file = "mypy-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828"}, + {file = "mypy-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3"}, + {file = "mypy-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816"}, + {file = "mypy-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c"}, + {file = "mypy-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f"}, + {file = "mypy-1.4.1-py3-none-any.whl", hash = "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4"}, + {file = "mypy-1.4.1.tar.gz", hash = "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} +typing-extensions = ">=4.1.0" + +[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." +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 = "nbclient" +version = "0.7.4" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "nbclient-0.7.4-py3-none-any.whl", hash = "sha256:c817c0768c5ff0d60e468e017613e6eae27b6fa31e43f905addd2d24df60c125"}, + {file = "nbclient-0.7.4.tar.gz", hash = "sha256:d447f0e5a4cfe79d462459aec1b3dc5c2e9152597262be8ee27f7d4c02566a0d"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.3" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] +test = ["flaky", "ipykernel", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.6.0" +description = "Converting Jupyter Notebooks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nbconvert-7.6.0-py3-none-any.whl", hash = "sha256:5a445c6794b0791984bc5436608fe2c066cb43c83920c7bc91bde3b765e9a264"}, + {file = "nbconvert-7.6.0.tar.gz", hash = "sha256:24fcf27efdef2b51d7f090cc5ce5a9b178766a55be513c4ebab08c91899ab550"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "!=5.0.0" +defusedxml = "*" +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.1" + +[package.extras] +all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["nbconvert[qtpng]"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["ipykernel", "ipywidgets (>=7)", "pre-commit", "pytest", "pytest-dependency"] +webpdf = ["pyppeteer (>=1,<1.1)"] + +[[package]] +name = "nbformat" +version = "5.8.0" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.7" +files = [ + {file = "nbformat-5.8.0-py3-none-any.whl", hash = "sha256:d910082bd3e0bffcf07eabf3683ed7dda0727a326c446eeb2922abe102e65162"}, + {file = "nbformat-5.8.0.tar.gz", hash = "sha256:46dac64c781f1c34dfd8acba16547024110348f9fc7eab0f31981c2a3dc48d1f"}, +] + +[package.dependencies] +fastjsonschema = "*" +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.8\""} +jsonschema = ">=2.6" +jupyter-core = "*" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nbsphinx" +version = "0.9.3" +description = "Jupyter Notebook Tools for Sphinx" +optional = false +python-versions = ">=3.6" +files = [ + {file = "nbsphinx-0.9.3-py3-none-any.whl", hash = "sha256:6e805e9627f4a358bd5720d5cbf8bf48853989c79af557afd91a5f22e163029f"}, + {file = "nbsphinx-0.9.3.tar.gz", hash = "sha256:ec339c8691b688f8676104a367a4b8cf3ea01fd089dc28d24dec22d563b11562"}, +] + +[package.dependencies] +docutils = "*" +jinja2 = "*" +nbconvert = "!=5.4" +nbformat = "*" +sphinx = ">=1.8" +traitlets = ">=5" + +[[package]] +name = "nest-asyncio" +version = "1.5.7" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.5.7-py3-none-any.whl", hash = "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657"}, + {file = "nest_asyncio-1.5.7.tar.gz", hash = "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "numpy" +version = "1.21.6" +description = "NumPy is the fundamental package for array computing with Python." +optional = false +python-versions = ">=3.7,<3.11" +files = [ + {file = "numpy-1.21.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8737609c3bbdd48e380d463134a35ffad3b22dc56295eff6f79fd85bd0eeeb25"}, + {file = "numpy-1.21.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fdffbfb6832cd0b300995a2b08b8f6fa9f6e856d562800fea9182316d99c4e8e"}, + {file = "numpy-1.21.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3820724272f9913b597ccd13a467cc492a0da6b05df26ea09e78b171a0bb9da6"}, + {file = "numpy-1.21.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f17e562de9edf691a42ddb1eb4a5541c20dd3f9e65b09ded2beb0799c0cf29bb"}, + {file = "numpy-1.21.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f30427731561ce75d7048ac254dbe47a2ba576229250fb60f0fb74db96501a1"}, + {file = "numpy-1.21.6-cp310-cp310-win32.whl", hash = "sha256:d4bf4d43077db55589ffc9009c0ba0a94fa4908b9586d6ccce2e0b164c86303c"}, + {file = "numpy-1.21.6-cp310-cp310-win_amd64.whl", hash = "sha256:d136337ae3cc69aa5e447e78d8e1514be8c3ec9b54264e680cf0b4bd9011574f"}, + {file = "numpy-1.21.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6aaf96c7f8cebc220cdfc03f1d5a31952f027dda050e5a703a0d1c396075e3e7"}, + {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:67c261d6c0a9981820c3a149d255a76918278a6b03b6a036800359aba1256d46"}, + {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a6be4cb0ef3b8c9250c19cc122267263093eee7edd4e3fa75395dfda8c17a8e2"}, + {file = "numpy-1.21.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c4068a8c44014b2d55f3c3f574c376b2494ca9cc73d2f1bd692382b6dffe3db"}, + {file = "numpy-1.21.6-cp37-cp37m-win32.whl", hash = "sha256:7c7e5fa88d9ff656e067876e4736379cc962d185d5cd808014a8a928d529ef4e"}, + {file = "numpy-1.21.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bcb238c9c96c00d3085b264e5c1a1207672577b93fa666c3b14a45240b14123a"}, + {file = "numpy-1.21.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:82691fda7c3f77c90e62da69ae60b5ac08e87e775b09813559f8901a88266552"}, + {file = "numpy-1.21.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:643843bcc1c50526b3a71cd2ee561cf0d8773f062c8cbaf9ffac9fdf573f83ab"}, + {file = "numpy-1.21.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:357768c2e4451ac241465157a3e929b265dfac85d9214074985b1786244f2ef3"}, + {file = "numpy-1.21.6-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9f411b2c3f3d76bba0865b35a425157c5dcf54937f82bbeb3d3c180789dd66a6"}, + {file = "numpy-1.21.6-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4aa48afdce4660b0076a00d80afa54e8a97cd49f457d68a4342d188a09451c1a"}, + {file = "numpy-1.21.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a96eef20f639e6a97d23e57dd0c1b1069a7b4fd7027482a4c5c451cd7732f4"}, + {file = "numpy-1.21.6-cp38-cp38-win32.whl", hash = "sha256:5c3c8def4230e1b959671eb959083661b4a0d2e9af93ee339c7dada6759a9470"}, + {file = "numpy-1.21.6-cp38-cp38-win_amd64.whl", hash = "sha256:bf2ec4b75d0e9356edea834d1de42b31fe11f726a81dfb2c2112bc1eaa508fcf"}, + {file = "numpy-1.21.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4391bd07606be175aafd267ef9bea87cf1b8210c787666ce82073b05f202add1"}, + {file = "numpy-1.21.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:67f21981ba2f9d7ba9ade60c9e8cbaa8cf8e9ae51673934480e45cf55e953673"}, + {file = "numpy-1.21.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee5ec40fdd06d62fe5d4084bef4fd50fd4bb6bfd2bf519365f569dc470163ab0"}, + {file = "numpy-1.21.6-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1dbe1c91269f880e364526649a52eff93ac30035507ae980d2fed33aaee633ac"}, + {file = "numpy-1.21.6-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d9caa9d5e682102453d96a0ee10c7241b72859b01a941a397fd965f23b3e016b"}, + {file = "numpy-1.21.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58459d3bad03343ac4b1b42ed14d571b8743dc80ccbf27444f266729df1d6f5b"}, + {file = "numpy-1.21.6-cp39-cp39-win32.whl", hash = "sha256:7f5ae4f304257569ef3b948810816bc87c9146e8c446053539947eedeaa32786"}, + {file = "numpy-1.21.6-cp39-cp39-win_amd64.whl", hash = "sha256:e31f0bb5928b793169b87e3d1e070f2342b22d5245c755e2b81caa29756246c3"}, + {file = "numpy-1.21.6-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dd1c8f6bd65d07d3810b90d02eba7997e32abbdf1277a481d698969e921a3be0"}, + {file = "numpy-1.21.6.zip", hash = "sha256:ecb55251139706669fdec2ff073c98ef8e9a84473e51e716211b41aa0f18e656"}, +] + +[[package]] +name = "packaging" +version = "23.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"}, + {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.0" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pathspec" +version = "0.11.2" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, + {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, +] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "pillow" +version = "9.5.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"}, + {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"}, + {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"}, + {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"}, + {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"}, + {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"}, + {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"}, + {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"}, + {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"}, + {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"}, + {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"}, + {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"}, + {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"}, + {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"}, + {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"}, + {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"}, + {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"}, + {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"}, + {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"}, + {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"}, + {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"}, + {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"}, + {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"}, + {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"}, + {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"}, + {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"}, + {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"}, + {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"}, + {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"}, + {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"}, + {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + +[[package]] +name = "platformdirs" +version = "3.10.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.7" +files = [ + {file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"}, + {file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.7.1", markers = "python_version < \"3.8\""} + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] + +[[package]] +name = "pre-commit" +version = "2.21.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, + {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "prompt-toolkit" +version = "3.0.39" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.39-py3-none-any.whl", hash = "sha256:9dffbe1d8acf91e3de75f3b544e4842382fc06c6babe903ac9acb74dc6e08d88"}, + {file = "prompt_toolkit-3.0.39.tar.gz", hash = "sha256:04505ade687dc26dc4284b1ad19a83be2f2afe83e7a828ace0c72f3a1df72aac"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "5.9.5" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, + {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, + {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, + {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, + {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, + {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, + {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, + {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, + {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, + {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, + {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.13.3" +description = "Bootstrap-based Sphinx theme from the PyData community" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pydata_sphinx_theme-0.13.3-py3-none-any.whl", hash = "sha256:bf41ca6c1c6216e929e28834e404bfc90e080b51915bbe7563b5e6fda70354f0"}, + {file = "pydata_sphinx_theme-0.13.3.tar.gz", hash = "sha256:827f16b065c4fd97e847c11c108bf632b7f2ff53a3bca3272f63f3f3ff782ecc"}, +] + +[package.dependencies] +accessible-pygments = "*" +Babel = "*" +beautifulsoup4 = "*" +docutils = "!=0.17.0" +packaging = "*" +pygments = ">=2.7" +sphinx = ">=4.2" +typing-extensions = "*" + +[package.extras] +dev = ["nox", "pre-commit", "pydata-sphinx-theme[doc,test]", "pyyaml"] +doc = ["ablog (>=0.11.0rc2)", "colorama", "ipyleaflet", "jupyter_sphinx", "linkify-it-py", "matplotlib", "myst-nb", "nbsphinx", "numpy", "numpydoc", "pandas", "plotly", "rich", "sphinx-copybutton", "sphinx-design", "sphinx-favicon (>=1.0.1)", "sphinx-sitemap", "sphinx-togglebutton", "sphinxcontrib-youtube", "sphinxext-rediraffe", "xarray"] +test = ["codecov", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] + +[[package]] +name = "pygments" +version = "2.16.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, + {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pylint" +version = "2.13.9" +description = "python code static checker" +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "pylint-2.13.9-py3-none-any.whl", hash = "sha256:705c620d388035bdd9ff8b44c5bcdd235bfb49d276d488dd2c8ff1736aa42526"}, + {file = "pylint-2.13.9.tar.gz", hash = "sha256:095567c96e19e6f57b5b907e67d265ff535e588fe26b12b5ebe1fc5645b2c731"}, +] + +[package.dependencies] +astroid = ">=2.11.5,<=2.12.0-dev0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +dill = ">=0.2" +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +testutil = ["gitpython (>3)"] + +[[package]] +name = "pyparsing" +version = "3.1.1" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb"}, + {file = "pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.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 = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "pyzmq" +version = "25.1.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76"}, + {file = "pyzmq-25.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849"}, + {file = "pyzmq-25.1.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7"}, + {file = "pyzmq-25.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9"}, + {file = "pyzmq-25.1.1-cp310-cp310-win32.whl", hash = "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790"}, + {file = "pyzmq-25.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83"}, + {file = "pyzmq-25.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9"}, + {file = "pyzmq-25.1.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752"}, + {file = "pyzmq-25.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca"}, + {file = "pyzmq-25.1.1-cp311-cp311-win32.whl", hash = "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329"}, + {file = "pyzmq-25.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762"}, + {file = "pyzmq-25.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a"}, + {file = "pyzmq-25.1.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e"}, + {file = "pyzmq-25.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb"}, + {file = "pyzmq-25.1.1-cp312-cp312-win32.whl", hash = "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075"}, + {file = "pyzmq-25.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787"}, + {file = "pyzmq-25.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062"}, + {file = "pyzmq-25.1.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e"}, + {file = "pyzmq-25.1.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win32.whl", hash = "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71"}, + {file = "pyzmq-25.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3"}, + {file = "pyzmq-25.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0"}, + {file = "pyzmq-25.1.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7"}, + {file = "pyzmq-25.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win32.whl", hash = "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e"}, + {file = "pyzmq-25.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a"}, + {file = "pyzmq-25.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f"}, + {file = "pyzmq-25.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2"}, + {file = "pyzmq-25.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0"}, + {file = "pyzmq-25.1.1-cp38-cp38-win32.whl", hash = "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c"}, + {file = "pyzmq-25.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45"}, + {file = "pyzmq-25.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae"}, + {file = "pyzmq-25.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8"}, + {file = "pyzmq-25.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win32.whl", hash = "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2"}, + {file = "pyzmq-25.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996"}, + {file = "pyzmq-25.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a"}, + {file = "pyzmq-25.1.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728"}, + {file = "pyzmq-25.1.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180"}, + {file = "pyzmq-25.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304"}, + {file = "pyzmq-25.1.1.tar.gz", hash = "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "recommonmark" +version = "0.7.1" +description = "A docutils-compatibility bridge to CommonMark, enabling you to write CommonMark inside of Docutils & Sphinx projects." +optional = false +python-versions = "*" +files = [ + {file = "recommonmark-0.7.1-py2.py3-none-any.whl", hash = "sha256:1b1db69af0231efce3fa21b94ff627ea33dee7079a01dd0a7f8482c3da148b3f"}, + {file = "recommonmark-0.7.1.tar.gz", hash = "sha256:bdb4db649f2222dcd8d2d844f0006b958d627f732415d399791ee436a3686d67"}, +] + +[package.dependencies] +commonmark = ">=0.8.1" +docutils = ">=0.11" +sphinx = ">=1.3.1" + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[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-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-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "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" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.4.1" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, + {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "tokenize-rt" +version = "5.0.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +optional = false +python-versions = ">=3.7" +files = [ + {file = "tokenize_rt-5.0.0-py2.py3-none-any.whl", hash = "sha256:c67772c662c6b3dc65edf66808577968fb10badfc2042e3027196bed4daf9e5a"}, + {file = "tokenize_rt-5.0.0.tar.gz", hash = "sha256:3160bc0c3e8491312d0485171dea861fc160a240f5f5766b72a1165408d10740"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +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 = "tornado" +version = "6.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] + +[[package]] +name = "traitlets" +version = "5.9.0" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.7" +files = [ + {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, + {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] + +[[package]] +name = "typed-ast" +version = "1.5.5" +description = "a fork of Python 2 and 3 ast modules with type comment support" +optional = false +python-versions = ">=3.6" +files = [ + {file = "typed_ast-1.5.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc1efe0ce3ffb74784e06460f01a223ac1f6ab31c6bc0376a21184bf5aabe3b"}, + {file = "typed_ast-1.5.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f7a8c46a8b333f71abd61d7ab9255440d4a588f34a21f126bbfc95f6049e686"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:597fc66b4162f959ee6a96b978c0435bd63791e31e4f410622d19f1686d5e769"}, + {file = "typed_ast-1.5.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d41b7a686ce653e06c2609075d397ebd5b969d821b9797d029fccd71fdec8e04"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5fe83a9a44c4ce67c796a1b466c270c1272e176603d5e06f6afbc101a572859d"}, + {file = "typed_ast-1.5.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d5c0c112a74c0e5db2c75882a0adf3133adedcdbfd8cf7c9d6ed77365ab90a1d"}, + {file = "typed_ast-1.5.5-cp310-cp310-win_amd64.whl", hash = "sha256:e1a976ed4cc2d71bb073e1b2a250892a6e968ff02aa14c1f40eba4f365ffec02"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c631da9710271cb67b08bd3f3813b7af7f4c69c319b75475436fcab8c3d21bee"}, + {file = "typed_ast-1.5.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b445c2abfecab89a932b20bd8261488d574591173d07827c1eda32c457358b18"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc95ffaaab2be3b25eb938779e43f513e0e538a84dd14a5d844b8f2932593d88"}, + {file = "typed_ast-1.5.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61443214d9b4c660dcf4b5307f15c12cb30bdfe9588ce6158f4a005baeb167b2"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6eb936d107e4d474940469e8ec5b380c9b329b5f08b78282d46baeebd3692dc9"}, + {file = "typed_ast-1.5.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e48bf27022897577d8479eaed64701ecaf0467182448bd95759883300ca818c8"}, + {file = "typed_ast-1.5.5-cp311-cp311-win_amd64.whl", hash = "sha256:83509f9324011c9a39faaef0922c6f720f9623afe3fe220b6d0b15638247206b"}, + {file = "typed_ast-1.5.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:44f214394fc1af23ca6d4e9e744804d890045d1643dd7e8229951e0ef39429b5"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118c1ce46ce58fda78503eae14b7664163aa735b620b64b5b725453696f2a35c"}, + {file = "typed_ast-1.5.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4919b808efa61101456e87f2d4c75b228f4e52618621c77f1ddcaae15904fa"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fc2b8c4e1bc5cd96c1a823a885e6b158f8451cf6f5530e1829390b4d27d0807f"}, + {file = "typed_ast-1.5.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:16f7313e0a08c7de57f2998c85e2a69a642e97cb32f87eb65fbfe88381a5e44d"}, + {file = "typed_ast-1.5.5-cp36-cp36m-win_amd64.whl", hash = "sha256:2b946ef8c04f77230489f75b4b5a4a6f24c078be4aed241cfabe9cbf4156e7e5"}, + {file = "typed_ast-1.5.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2188bc33d85951ea4ddad55d2b35598b2709d122c11c75cffd529fbc9965508e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0635900d16ae133cab3b26c607586131269f88266954eb04ec31535c9a12ef1e"}, + {file = "typed_ast-1.5.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57bfc3cf35a0f2fdf0a88a3044aafaec1d2f24d8ae8cd87c4f58d615fb5b6311"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fe58ef6a764de7b4b36edfc8592641f56e69b7163bba9f9c8089838ee596bfb2"}, + {file = "typed_ast-1.5.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d09d930c2d1d621f717bb217bf1fe2584616febb5138d9b3e8cdd26506c3f6d4"}, + {file = "typed_ast-1.5.5-cp37-cp37m-win_amd64.whl", hash = "sha256:d40c10326893ecab8a80a53039164a224984339b2c32a6baf55ecbd5b1df6431"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fd946abf3c31fb50eee07451a6aedbfff912fcd13cf357363f5b4e834cc5e71a"}, + {file = "typed_ast-1.5.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ed4a1a42df8a3dfb6b40c3d2de109e935949f2f66b19703eafade03173f8f437"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:045f9930a1550d9352464e5149710d56a2aed23a2ffe78946478f7b5416f1ede"}, + {file = "typed_ast-1.5.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:381eed9c95484ceef5ced626355fdc0765ab51d8553fec08661dce654a935db4"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bfd39a41c0ef6f31684daff53befddae608f9daf6957140228a08e51f312d7e6"}, + {file = "typed_ast-1.5.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8c524eb3024edcc04e288db9541fe1f438f82d281e591c548903d5b77ad1ddd4"}, + {file = "typed_ast-1.5.5-cp38-cp38-win_amd64.whl", hash = "sha256:7f58fabdde8dcbe764cef5e1a7fcb440f2463c1bbbec1cf2a86ca7bc1f95184b"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:042eb665ff6bf020dd2243307d11ed626306b82812aba21836096d229fdc6a10"}, + {file = "typed_ast-1.5.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:622e4a006472b05cf6ef7f9f2636edc51bda670b7bbffa18d26b255269d3d814"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1efebbbf4604ad1283e963e8915daa240cb4bf5067053cf2f0baadc4d4fb51b8"}, + {file = "typed_ast-1.5.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0aefdd66f1784c58f65b502b6cf8b121544680456d1cebbd300c2c813899274"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:48074261a842acf825af1968cd912f6f21357316080ebaca5f19abbb11690c8a"}, + {file = "typed_ast-1.5.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:429ae404f69dc94b9361bb62291885894b7c6fb4640d561179548c849f8492ba"}, + {file = "typed_ast-1.5.5-cp39-cp39-win_amd64.whl", hash = "sha256:335f22ccb244da2b5c296e6f96b06ee9bed46526db0de38d2f0e5a6597b81155"}, + {file = "typed_ast-1.5.5.tar.gz", hash = "sha256:94282f7a354f36ef5dbce0ef3467ebf6a258e370ab33d5b40c249fa996e590dd"}, +] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.24.5" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""} +platformdirs = ">=3.9.1,<4" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.9" +description = "Jupyter interactive widgets for Jupyter Notebook" +optional = false +python-versions = ">=3.7" +files = [ + {file = "widgetsnbextension-4.0.9-py3-none-any.whl", hash = "sha256:91452ca8445beb805792f206e560c1769284267a30ceb1cec9f5bcc887d15175"}, + {file = "widgetsnbextension-4.0.9.tar.gz", hash = "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385"}, +] + +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + +[[package]] +name = "zipp" +version = "3.15.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +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.7,<3.11" +content-hash = "c5b23e5b5be0d2103faf86cd3e0380fb6bfcd5e5b56e90dda009eb15e84fb619" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..cb6dae0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +[tool.black] +line-length = 120 +include = '\.pyi?$' +exclude = ''' +( + /( + \.eggs # exclude a few common directories in the + | \.git # root of the project + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + )/ + | docs/build/ + | node_modules/ + | venve/ + | .venv/ +) +''' + +[tool.nbqa.mutate] +isort = 1 +black = 1 + +[tool.poetry] +name = "lekin" +readme = "README.md" # Markdown files are supported +version = "0.0.2" # is being replaced automatically + +authors = ["Longxing Tan"] +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "License :: OSI Approved :: MIT License"] +description = "Flexible job shop scheduler in Python" +repository = "https://github.com/LongxingTan/python-lekin" +documentation = "https://python-lekin.readthedocs.io" +homepage = "https://python-lekin.readthedocs.io" + +[tool.poetry.dependencies] +python = ">=3.7,<3.11" +#pandas = "^1.1.0" +matplotlib = "*" + +[tool.poetry.dev-dependencies] +# checks and make tools +pre-commit = "^2.20.0" + +invoke = "*" +flake8 = "*" +mypy = "*" +pylint = "*" +isort = "*" +coverage = "*" + +# jupyter notebook +ipykernel = "*" +black = { version = "*", allow-prereleases = true, extras = ["jupyter"] } + +# documentatation +sphinx = "*" +pydata-sphinx-theme = "*" +nbsphinx = "*" +# pandoc = "*" +recommonmark = "*" +ipywidgets = "^8.0.1" + +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +dirty = false +style = "semver" # semantic versioning + +[build-system] # make the package pip installable +requires = ["poetry-core>=1.0.7", "poetry-dynamic-versioning>=0.13.1"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f0b8151 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,69 @@ +[flake8] +max-line-length = 120 +show-source = true +ignore = + # space before : (needed for how black formats slicing) + E203, + # line break before binary operator + W503, + # line break after binary operator + W504, + # module level import not at top of file + E402, + # do not assign a lambda expression, use a def + E731, + # ignore not easy to read variables like i l I etc. + E741, + # Unnecessary list literal - rewrite as a dict literal. + C406, + # Unnecessary dict call - rewrite as a literal. + C408, + # Unnecessary list passed to tuple() - rewrite as a tuple literal. + C409, + # found modulo formatter (incorrect picks up mod operations) + S001, + # unused imports + F401 +exclude = docs/build/*.py, + node_modules/*.py, + .eggs/*.py, + versioneer.py, + venv/*, + .venv/*, + .git/* + .history/* + +[isort] +profile = black +honor_noqa = true +line_length = 120 +combine_as_imports = true +force_sort_within_sections = true +known_first_party = pytorch_forecasting + +[tool:pytest] +addopts = + -rsxX + -vv + --last-failed + --cov=pytorch_forecasting + --cov-report=html + --cov-config=setup.cfg + --cov-report=term-missing:skip-covered + --no-cov-on-fail + -n0 +testpaths = tests/ +log_cli_level = ERROR +markers = + +[coverage:report] +ignore_errors = False +show_missing = true + + +[mypy] +ignore_missing_imports = true +no_implicit_optional = true +check_untyped_defs = true + +cache_dir = .cache/mypy/ diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_dashboard/__init__.py b/tests/test_dashboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_dashboard/test_gantt.py b/tests/test_dashboard/test_gantt.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_datasets/__init__.py b/tests/test_datasets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/__init__.py b/tests/test_lekin_struct/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_job.py b/tests/test_lekin_struct/test_job.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_operation.py b/tests/test_lekin_struct/test_operation.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_resource.py b/tests/test_lekin_struct/test_resource.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_route.py b/tests/test_lekin_struct/test_route.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lekin_struct/test_timeslot.py b/tests/test_lekin_struct/test_timeslot.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_objective/__init__.py b/tests/test_objective/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/__init__.py b/tests/test_solver/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_construction_heuristics/__init__.py b/tests/test_solver/test_construction_heuristics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_construction_heuristics/test_lpst.py b/tests/test_solver/test_construction_heuristics/test_lpst.py new file mode 100644 index 0000000..2435285 --- /dev/null +++ b/tests/test_solver/test_construction_heuristics/test_lpst.py @@ -0,0 +1,3 @@ +import unittest + +from lekin.solver.construction_heuristics.lpst import LPSTScheduler diff --git a/tests/test_solver/test_construction_heuristics/test_rule.py b/tests/test_solver/test_construction_heuristics/test_rule.py new file mode 100644 index 0000000..f6df27e --- /dev/null +++ b/tests/test_solver/test_construction_heuristics/test_rule.py @@ -0,0 +1,3 @@ +import pandas as pd + +from lekin.lekin_struct import Job, JobCollector diff --git a/tests/test_solver/test_construction_heuristics/test_spt.py b/tests/test_solver/test_construction_heuristics/test_spt.py new file mode 100644 index 0000000..ccb7ab4 --- /dev/null +++ b/tests/test_solver/test_construction_heuristics/test_spt.py @@ -0,0 +1,3 @@ +import unittest + +from lekin.solver.construction_heuristics.spt import SPTScheduler diff --git a/tests/test_solver/test_meta_heuristics/__init__.py b/tests/test_solver/test_meta_heuristics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_branch_and_bound.py b/tests/test_solver/test_meta_heuristics/test_branch_and_bound.py new file mode 100644 index 0000000..3a87ac3 --- /dev/null +++ b/tests/test_solver/test_meta_heuristics/test_branch_and_bound.py @@ -0,0 +1,26 @@ +from datetime import datetime, timedelta +import unittest + +from lekin.lekin_struct import Job, Operation, Resource, Route, TimeSlot +from lekin.solver.meta_heuristics.branch_and_bound import BranchAndBoundScheduler + +# class BranchAndBoundSchedulerTest(unittest.TestCase): +# def test_schedule(self): +# job1 = Job(1, datetime(2023, 1, 10), 2, 1) +# job2 = Job(2, datetime(2023, 1, 20), 1, 1) +# +# op1 = Operation(1, timedelta(hours=2), 2, None, [1]) +# op2 = Operation(2, timedelta(hours=3), None, 1, [1]) +# +# route1 = Route(1, [op1, op2]) +# print(route1) +# +# resource1 = Resource(1, [TimeSlot(datetime(2023, 1, 1), datetime(2023, 1, 3))]) +# +# job_list = [job1, job2] +# resource_list = [resource1] +# +# scheduler = BranchAndBoundScheduler(job_list, resource_list) +# schedule = scheduler.get_schedule() +# for idx, slot in enumerate(schedule): +# print(f"Job {idx + 1} will start at {slot.start_time} and end at {slot.end_time}") diff --git a/tests/test_solver/test_meta_heuristics/test_genetic.py b/tests/test_solver/test_meta_heuristics/test_genetic.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_nsga3.py b/tests/test_solver/test_meta_heuristics/test_nsga3.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_meta_heuristics/test_tabu_search.py b/tests/test_solver/test_meta_heuristics/test_tabu_search.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_reinforcement_learning/__init__.py b/tests/test_solver/test_reinforcement_learning/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_solver/test_reinforcement_learning/test_dqn.py b/tests/test_solver/test_reinforcement_learning/test_dqn.py new file mode 100644 index 0000000..e69de29