From 0bcdfbdb34e03e24e2f5da90a7236226b721981d Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Thu, 5 Dec 2024 05:34:48 +0100
Subject: [PATCH 001/135] Use Astral uv (#12402)

* Use Astral uv

* uvx vs uv run

* uv sync --group=euler-validate,test

* uv sync --group=euler-validate --group=test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* --group=test

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .github/workflows/build.yml         |   15 +-
 .github/workflows/project_euler.yml |   16 +-
 .github/workflows/ruff.yml          |    4 +-
 .github/workflows/sphinx.yml        |    6 +-
 pyproject.toml                      |   48 +-
 requirements.txt                    |    6 -
 uv.lock                             | 1246 +++++++++++++++++++++++++++
 7 files changed, 1301 insertions(+), 40 deletions(-)
 create mode 100644 uv.lock

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b5703e2f1ab6..a6f308715cc2 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,21 +10,18 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: astral-sh/setup-uv@v4
+        with:
+          enable-cache: true
+          cache-dependency-glob: uv.lock
       - uses: actions/setup-python@v5
         with:
           python-version: 3.13
           allow-prereleases: true
-      - uses: actions/cache@v4
-        with:
-          path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip setuptools wheel
-          python -m pip install pytest-cov -r requirements.txt
+      - run: uv sync --group=test
       - name: Run tests
         # TODO: #8818 Re-enable quantum tests
-        run: pytest
+        run: uv run pytest
           --ignore=computer_vision/cnn_classification.py
           --ignore=docs/conf.py
           --ignore=dynamic_programming/k_means_clustering_tensorflow.py
diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml
index 59e1208a650d..84c55335451e 100644
--- a/.github/workflows/project_euler.yml
+++ b/.github/workflows/project_euler.yml
@@ -15,25 +15,21 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: astral-sh/setup-uv@v4
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
-      - name: Install pytest and pytest-cov
-        run: |
-          python -m pip install --upgrade pip
-          python -m pip install --upgrade numpy pytest pytest-cov
-      - run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
+      - run: uv sync --group=euler-validate --group=test
+      - run: uv run pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
   validate-solutions:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: astral-sh/setup-uv@v4
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
-      - name: Install pytest and requests
-        run: |
-          python -m pip install --upgrade pip
-          python -m pip install --upgrade numpy pytest requests
-      - run: pytest scripts/validate_solutions.py
+      - run: uv sync --group=euler-validate --group=test
+      - run: uv run pytest scripts/validate_solutions.py
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
index d354eba672ae..2c6f92fcf7bf 100644
--- a/.github/workflows/ruff.yml
+++ b/.github/workflows/ruff.yml
@@ -12,5 +12,5 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - run: pip install --user ruff
-      - run: ruff check --output-format=github .
+      - uses: astral-sh/setup-uv@v4
+      - run: uvx ruff check --output-format=github .
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index 9dfe344f9743..e3e2ce81a95d 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -26,14 +26,14 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: astral-sh/setup-uv@v4
       - uses: actions/setup-python@v5
         with:
           python-version: 3.13
           allow-prereleases: true
-      - run: pip install --upgrade pip
-      - run: pip install myst-parser sphinx-autoapi sphinx-pyproject
+      - run: uv sync --group=docs
       - uses: actions/configure-pages@v5
-      - run: sphinx-build -c docs . docs/_build/html
+      - run: uv run sphinx-build -c docs . docs/_build/html
       - uses: actions/upload-pages-artifact@v3
         with:
           path: docs/_build/html
diff --git a/pyproject.toml b/pyproject.toml
index c60ec246144e..7b7176705c44 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -7,12 +7,43 @@ requires-python = ">=3.13"
 classifiers = [
   "Programming Language :: Python :: 3 :: Only",
   "Programming Language :: Python :: 3.13",
+]
+dependencies = [
+  "beautifulsoup4>=4.12.3",
+  "fake-useragent>=1.5.1",
+  "imageio>=2.36.1",
+  "keras>=3.7",
+  "lxml>=5.3",
+  "matplotlib>=3.9.3",
+  "numpy>=2.1.3",
+  "opencv-python>=4.10.0.84",
+  "pandas>=2.2.3",
+  "pillow>=11",
+  "requests>=2.32.3",
+  "rich>=13.9.4",
+  "scikit-learn>=1.5.2",
+  "sphinx-pyproject>=0.3",
+  "statsmodels>=0.14.4",
+  "sympy>=1.13.3",
+  "tweepy>=4.14",
+  "typing-extensions>=4.12.2",
+  "xgboost>=2.1.3",
+]
 
+[dependency-groups]
+test = [
+  "pytest>=8.3.4",
+  "pytest-cov>=6",
 ]
-optional-dependencies.docs = [
-  "myst-parser",
-  "sphinx-autoapi",
-  "sphinx-pyproject",
+
+docs = [
+  "myst-parser>=4",
+  "sphinx-autoapi>=3.4",
+  "sphinx-pyproject>=0.3",
+]
+euler-validate = [
+  "numpy>=2.1.3",
+  "requests>=2.32.3",
 ]
 
 [tool.ruff]
@@ -61,8 +92,8 @@ lint.select = [
   "UP",    # pyupgrade
   "W",     # pycodestyle
   "YTT",   # flake8-2020
-  # "ANN",  # flake8-annotations  # FIX ME?
-  # "COM",  # flake8-commas
+  # "ANN",  # flake8-annotations -- FIX ME?
+  # "COM",  # flake8-commas -- DO NOT FIX
   # "D",    # pydocstyle -- FIX ME?
   # "ERA",  # eradicate -- DO NOT FIX
   # "FBT",  # flake8-boolean-trap  # FIX ME
@@ -129,10 +160,7 @@ lint.pylint.max-statements = 88 # default: 50
 
 [tool.codespell]
 ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar"
-skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt"
-
-[tool.pyproject-fmt]
-max_supported_python = "3.13"
+skip = "./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt"
 
 [tool.pytest.ini_options]
 markers = [
diff --git a/requirements.txt b/requirements.txt
index 6754363332c4..4cc83f44987d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,18 +8,12 @@ numpy
 opencv-python
 pandas
 pillow
-# projectq  # uncomment once quantum/quantum_random.py is fixed
-qiskit ; python_version < '3.12'
-qiskit-aer ; python_version < '3.12'
 requests
 rich
-# scikit-fuzzy  # uncomment once fuzzy_logic/fuzzy_operations.py is fixed
 scikit-learn
 sphinx_pyproject
 statsmodels
 sympy
-tensorflow ; python_version < '3.13'
 tweepy
-# yulewalker  # uncomment once audio_filters/equal_loudness_filter.py is fixed
 typing_extensions
 xgboost
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 000000000000..077288f041a1
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,1246 @@
+version = 1
+requires-python = ">=3.13"
+resolution-markers = [
+    "platform_system == 'Darwin'",
+    "platform_machine == 'aarch64' and platform_system == 'Linux'",
+    "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')",
+]
+
+[[package]]
+name = "absl-py"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 },
+]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 },
+]
+
+[[package]]
+name = "astroid"
+version = "3.3.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/38/1e/326fb1d3d83a3bb77c9f9be29d31f2901e35acb94b0605c3f2e5085047f9/astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d", size = 397229 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/41/30/624365383fa4a40329c0f0bbbc151abc4a64e30dfc110fc8f6e2afcd02bb/astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8", size = 274586 },
+]
+
+[[package]]
+name = "babel"
+version = "2.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "soupsieve" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 },
+]
+
+[[package]]
+name = "certifi"
+version = "2024.8.30"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 },
+    { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 },
+    { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 },
+    { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 },
+    { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 },
+    { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 },
+    { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 },
+    { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 },
+    { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 },
+    { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 },
+    { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 },
+    { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 },
+    { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 },
+    { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 },
+    { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 },
+    { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 },
+]
+
+[[package]]
+name = "codespell"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a0/a9/98353dfc7afcdf18cffd2dd3e959a25eaaf2728cf450caa59af89648a8e4/codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f", size = 329791 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0e/20/b6019add11e84f821184234cea0ad91442373489ef7ccfa3d73a71b908fa/codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1", size = 329167 },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9a/e7/de62050dce687c5e96f946a93546910bc67e483fe05324439e329ff36105/contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2", size = 271548 },
+    { url = "https://files.pythonhosted.org/packages/78/4d/c2a09ae014ae984c6bdd29c11e74d3121b25eaa117eca0bb76340efd7e1c/contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5", size = 255576 },
+    { url = "https://files.pythonhosted.org/packages/ab/8a/915380ee96a5638bda80cd061ccb8e666bfdccea38d5741cb69e6dbd61fc/contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81", size = 306635 },
+    { url = "https://files.pythonhosted.org/packages/29/5c/c83ce09375428298acd4e6582aeb68b1e0d1447f877fa993d9bf6cd3b0a0/contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2", size = 345925 },
+    { url = "https://files.pythonhosted.org/packages/29/63/5b52f4a15e80c66c8078a641a3bfacd6e07106835682454647aca1afc852/contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7", size = 318000 },
+    { url = "https://files.pythonhosted.org/packages/9a/e2/30ca086c692691129849198659bf0556d72a757fe2769eb9620a27169296/contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c", size = 322689 },
+    { url = "https://files.pythonhosted.org/packages/6b/77/f37812ef700f1f185d348394debf33f22d531e714cf6a35d13d68a7003c7/contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3", size = 1268413 },
+    { url = "https://files.pythonhosted.org/packages/3f/6d/ce84e79cdd128542ebeb268f84abb4b093af78e7f8ec504676673d2675bc/contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1", size = 1326530 },
+    { url = "https://files.pythonhosted.org/packages/72/22/8282f4eae20c73c89bee7a82a19c4e27af9b57bb602ecaa00713d5bdb54d/contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82", size = 175315 },
+    { url = "https://files.pythonhosted.org/packages/e3/d5/28bca491f65312b438fbf076589dcde7f6f966b196d900777f5811b9c4e2/contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd", size = 220987 },
+    { url = "https://files.pythonhosted.org/packages/2f/24/a4b285d6adaaf9746e4700932f579f1a7b6f9681109f694cfa233ae75c4e/contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30", size = 285001 },
+    { url = "https://files.pythonhosted.org/packages/48/1d/fb49a401b5ca4f06ccf467cd6c4f1fd65767e63c21322b29b04ec40b40b9/contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751", size = 268553 },
+    { url = "https://files.pythonhosted.org/packages/79/1e/4aef9470d13fd029087388fae750dccb49a50c012a6c8d1d634295caa644/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342", size = 310386 },
+    { url = "https://files.pythonhosted.org/packages/b0/34/910dc706ed70153b60392b5305c708c9810d425bde12499c9184a1100888/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c", size = 349806 },
+    { url = "https://files.pythonhosted.org/packages/31/3c/faee6a40d66d7f2a87f7102236bf4780c57990dd7f98e5ff29881b1b1344/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f", size = 321108 },
+    { url = "https://files.pythonhosted.org/packages/17/69/390dc9b20dd4bb20585651d7316cc3054b7d4a7b4f8b710b2b698e08968d/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda", size = 327291 },
+    { url = "https://files.pythonhosted.org/packages/ef/74/7030b67c4e941fe1e5424a3d988080e83568030ce0355f7c9fc556455b01/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242", size = 1263752 },
+    { url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 },
+    { url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 },
+    { url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 },
+]
+
+[[package]]
+name = "coverage"
+version = "7.6.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ab/75/aecfd0a3adbec6e45753976bc2a9fed62b42cea9a206d10fd29244a77953/coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", size = 801425 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/9a/84/6f0ccf94a098ac3d6d6f236bd3905eeac049a9e0efcd9a63d4feca37ac4b/coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", size = 207313 },
+    { url = "https://files.pythonhosted.org/packages/db/2b/e3b3a3a12ebec738c545897ac9f314620470fcbc368cdac88cf14974ba20/coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", size = 207574 },
+    { url = "https://files.pythonhosted.org/packages/db/c0/5bf95d42b6a8d21dfce5025ce187f15db57d6460a59b67a95fe8728162f1/coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", size = 240090 },
+    { url = "https://files.pythonhosted.org/packages/57/b8/d6fd17d1a8e2b0e1a4e8b9cb1f0f261afd422570735899759c0584236916/coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", size = 237237 },
+    { url = "https://files.pythonhosted.org/packages/d4/e4/a91e9bb46809c8b63e68fc5db5c4d567d3423b6691d049a4f950e38fbe9d/coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", size = 239225 },
+    { url = "https://files.pythonhosted.org/packages/31/9c/9b99b0591ec4555b7292d271e005f27b465388ce166056c435b288db6a69/coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", size = 238888 },
+    { url = "https://files.pythonhosted.org/packages/a6/85/285c2df9a04bc7c31f21fd9d4a24d19e040ec5e2ff06e572af1f6514c9e7/coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", size = 236974 },
+    { url = "https://files.pythonhosted.org/packages/cb/a1/95ec8522206f76cdca033bf8bb61fff56429fb414835fc4d34651dfd29fc/coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", size = 238815 },
+    { url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957 },
+    { url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711 },
+    { url = "https://files.pythonhosted.org/packages/ee/4b/891c8b9acf1b62c85e4a71dac142ab9284e8347409b7355de02e3f38306f/coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", size = 208053 },
+    { url = "https://files.pythonhosted.org/packages/18/a9/9e330409b291cc002723d339346452800e78df1ce50774ca439ade1d374f/coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", size = 208329 },
+    { url = "https://files.pythonhosted.org/packages/9c/0d/33635fd429f6589c6e1cdfc7bf581aefe4c1792fbff06383f9d37f59db60/coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", size = 251052 },
+    { url = "https://files.pythonhosted.org/packages/23/32/8a08da0e46f3830bbb9a5b40614241b2e700f27a9c2889f53122486443ed/coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", size = 246765 },
+    { url = "https://files.pythonhosted.org/packages/56/3f/3b86303d2c14350fdb1c6c4dbf9bc76000af2382f42ca1d4d99c6317666e/coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", size = 249125 },
+    { url = "https://files.pythonhosted.org/packages/36/cb/c4f081b9023f9fd8646dbc4ef77be0df090263e8f66f4ea47681e0dc2cff/coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", size = 248615 },
+    { url = "https://files.pythonhosted.org/packages/32/ee/53bdbf67760928c44b57b2c28a8c0a4bf544f85a9ee129a63ba5c78fdee4/coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", size = 246507 },
+    { url = "https://files.pythonhosted.org/packages/57/49/5a57910bd0af6d8e802b4ca65292576d19b54b49f81577fd898505dee075/coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", size = 247785 },
+    { url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605 },
+    { url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777 },
+]
+
+[[package]]
+name = "cycler"
+version = "0.12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
+]
+
+[[package]]
+name = "docutils"
+version = "0.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 },
+]
+
+[[package]]
+name = "dom-toml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "domdf-python-tools" },
+    { name = "tomli" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/34/f7690cf288eaa86b55c8f1b890d0834e6df44a026a88eca12274fcd624ab/dom_toml-2.0.0.tar.gz", hash = "sha256:3c07e8436538994974127b1ae037661d1a779ac915c44fd06b3ab5fe140ff589", size = 11133 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/22/99/b6fc87dff3138491d81676bdcbf1531080925ba41486ec1dafd86e33fdbc/dom_toml-2.0.0-py3-none-any.whl", hash = "sha256:0b6d02a72bcbc6be8175c61afc30623bbb6b74c4650f2a806fbc3fb7fe86935d", size = 13376 },
+]
+
+[[package]]
+name = "domdf-python-tools"
+version = "3.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "natsort" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6b/78/974e10c583ba9d2302e748c9585313a7f2c7ba00e4f600324f432e38fe68/domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18", size = 103792 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/de/e9/7447a88b217650a74927d3444a89507986479a69b83741900eddd34167fe/domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e", size = 127106 },
+]
+
+[[package]]
+name = "fake-useragent"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/24/a1/1f662631ab153975fa8dbf09296324ecbaf53370dce922054e8de6b57370/fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49", size = 22631 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190 },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.55.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/4e/053fe1b5c0ce346c0a9d0557492c654362bafb14f026eae0d3ee98009152/fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71", size = 3490431 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c3/87/a669ac26c6077e37ffb06abf29c5571789eefe518d06c52df392181ee694/fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9", size = 2752519 },
+    { url = "https://files.pythonhosted.org/packages/0c/e9/4822ad238fe215133c7df20f1cdb1a58cfb634a31523e77ff0fb2033970a/fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c", size = 2286819 },
+    { url = "https://files.pythonhosted.org/packages/3e/a4/d7941c3897129e60fe68d20e4819fda4d0c4858d77badae0e80ca6440b36/fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c", size = 4770382 },
+    { url = "https://files.pythonhosted.org/packages/31/cf/c51ea1348f9fba9c627439afad9dee0090040809ab431f4422b5bfdda34c/fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd", size = 4858336 },
+    { url = "https://files.pythonhosted.org/packages/73/be/36c1fe0e5c9a96b068ddd7e82001243bbe7fe12549c8d14e1bd025bf40c9/fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4", size = 4756072 },
+    { url = "https://files.pythonhosted.org/packages/5c/18/6dd381c29f215a017f79aa9fea0722424a0046b47991c4390a78ff87ce0c/fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18", size = 5008668 },
+    { url = "https://files.pythonhosted.org/packages/b8/95/316f20092b389b927dba1d1dccd3f541853f96e707e210f1b9f4e7bacdd5/fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b", size = 2155841 },
+    { url = "https://files.pythonhosted.org/packages/35/ca/b4638aa3e446184892e2f9cc8ef44bb506f47fea04580df7fb84f5a4363d/fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998", size = 2200587 },
+    { url = "https://files.pythonhosted.org/packages/b4/4a/786589606d4989cb34d8bc766cd687d955aaf3039c367fe7104bcf82dc98/fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f", size = 1100249 },
+]
+
+[[package]]
+name = "h5py"
+version = "3.12.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 },
+    { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 },
+    { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 },
+    { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 },
+    { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 },
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
+]
+
+[[package]]
+name = "imageio"
+version = "2.36.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "pillow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/aa/2e7a49259339e691ff2b477ae0696b1784a09313c5872700bbbdd00a3030/imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62", size = 389522 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 },
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
+]
+
+[[package]]
+name = "joblib"
+version = "1.4.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 },
+]
+
+[[package]]
+name = "keras"
+version = "3.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "absl-py" },
+    { name = "h5py" },
+    { name = "ml-dtypes" },
+    { name = "namex" },
+    { name = "numpy" },
+    { name = "optree" },
+    { name = "packaging" },
+    { name = "rich" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c9/c3/56fc6800c5eab94bd0f5e930751bd4c0fa1ee0aee272fad4a72723ffae87/keras-3.7.0.tar.gz", hash = "sha256:a4451a5591e75dfb414d0b84a3fd2fb9c0240cc87ebe7e397f547ce10b0e67b7", size = 924719 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/8a/bf/9e3f10e55df30b0fb4bf6c2ee7d50bda2e070599b86f62ea3f9954af172b/keras-3.7.0-py3-none-any.whl", hash = "sha256:546a64f302e4779c129c06d9826fa586de752cdfd43d7dc4010c31b282587969", size = 1228365 },
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913 },
+    { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627 },
+    { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888 },
+    { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145 },
+    { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448 },
+    { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750 },
+    { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175 },
+    { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963 },
+    { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220 },
+    { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463 },
+    { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842 },
+    { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635 },
+    { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556 },
+    { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 },
+    { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 },
+    { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 },
+]
+
+[[package]]
+name = "lxml"
+version = "5.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 },
+    { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 },
+    { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 },
+    { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 },
+    { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 },
+    { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 },
+    { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 },
+    { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 },
+    { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 },
+    { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 },
+    { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 },
+    { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 },
+    { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 },
+    { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 },
+    { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 },
+    { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 },
+    { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
+    { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
+    { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
+    { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
+    { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
+    { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
+    { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
+    { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
+    { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
+    { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
+    { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
+    { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
+    { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
+    { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
+    { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
+    { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
+    { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
+    { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
+    { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
+    { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.9.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "contourpy" },
+    { name = "cycler" },
+    { name = "fonttools" },
+    { name = "kiwisolver" },
+    { name = "numpy" },
+    { name = "packaging" },
+    { name = "pillow" },
+    { name = "pyparsing" },
+    { name = "python-dateutil" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/75/9f/562ed484b11ac9f4bb4f9d2d7546954ec106a8c0f06cc755d6f63e519274/matplotlib-3.9.3.tar.gz", hash = "sha256:cd5dbbc8e25cad5f706845c4d100e2c8b34691b412b93717ce38d8ae803bcfa5", size = 36113438 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/60/04/949640040982822416c471d9ebe4e9e6c69ca9f9bb6ba82ed30808863c02/matplotlib-3.9.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:203d18df84f5288973b2d56de63d4678cc748250026ca9e1ad8f8a0fd8a75d83", size = 7883417 },
+    { url = "https://files.pythonhosted.org/packages/9f/90/ebd37143cd3150b6c650ee1580024df3dd649d176e68d346f826b8d24e37/matplotlib-3.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b651b0d3642991259109dc0351fc33ad44c624801367bb8307be9bfc35e427ad", size = 7768720 },
+    { url = "https://files.pythonhosted.org/packages/dc/84/6591e6b55d755d16dacdc113205067031867c1f5e3c08b32c01aad831420/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66d7b171fecf96940ce069923a08ba3df33ef542de82c2ff4fe8caa8346fa95a", size = 8192723 },
+    { url = "https://files.pythonhosted.org/packages/29/09/146a17d37e32313507f11ac984e65311f2d5805d731eb981d4f70eb928dc/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be0ba61f6ff2e6b68e4270fb63b6813c9e7dec3d15fc3a93f47480444fd72f0", size = 8305801 },
+    { url = "https://files.pythonhosted.org/packages/85/cb/d2690572c08f19ca7c0f44b1fb4d11c121d63467a57b508cc3656ff80b43/matplotlib-3.9.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d6b2e8856dec3a6db1ae51aec85c82223e834b228c1d3228aede87eee2b34f9", size = 9086564 },
+    { url = "https://files.pythonhosted.org/packages/28/dd/0a5176027c1cb94fe75f69f76cb274180c8abf740df6fc0e6a1e4cbaec3f/matplotlib-3.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:90a85a004fefed9e583597478420bf904bb1a065b0b0ee5b9d8d31b04b0f3f70", size = 7833257 },
+    { url = "https://files.pythonhosted.org/packages/42/d4/e477d50a8e4b437c2afbb5c665cb8e5d79b06abe6fe3c6915d6f7f0c2ef2/matplotlib-3.9.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3119b2f16de7f7b9212ba76d8fe6a0e9f90b27a1e04683cd89833a991682f639", size = 7911906 },
+    { url = "https://files.pythonhosted.org/packages/ae/a1/ba5ab89666c42ace8e31b4ff5a2c76a17e4d6f91aefce476b064c56ff61d/matplotlib-3.9.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87ad73763d93add1b6c1f9fcd33af662fd62ed70e620c52fcb79f3ac427cf3a6", size = 7801336 },
+    { url = "https://files.pythonhosted.org/packages/77/59/4dcdb3a6695af6c698a95aec13016a550ef2f85144d22f61f81d1e064148/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026bdf3137ab6022c866efa4813b6bbeddc2ed4c9e7e02f0e323a7bca380dfa0", size = 8218178 },
+    { url = "https://files.pythonhosted.org/packages/4f/27/7c72db0d0ee35d9237572565ffa3c0eb25fc46a3f47e0f16412a587bc9d8/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760a5e89ebbb172989e8273024a1024b0f084510b9105261b3b00c15e9c9f006", size = 8327768 },
+    { url = "https://files.pythonhosted.org/packages/de/ad/213eee624feadba7b77e881c9d2c04c1e036efe69d19031e3fa927fdb5dc/matplotlib-3.9.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a42b9dc42de2cfe357efa27d9c50c7833fc5ab9b2eb7252ccd5d5f836a84e1e4", size = 9094075 },
+    { url = "https://files.pythonhosted.org/packages/19/1b/cb8e99a5fe2e2b14e3b8234cb1649a675be63f74a5224a648ae4ab61f60c/matplotlib-3.9.3-cp313-cp313t-win_amd64.whl", hash = "sha256:e0fcb7da73fbf67b5f4bdaa57d85bb585a4e913d4a10f3e15b32baea56a67f0a", size = 7888937 },
+]
+
+[[package]]
+name = "mdit-py-plugins"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markdown-it-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
+]
+
+[[package]]
+name = "ml-dtypes"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy", marker = "python_full_version >= '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/79/717c5e22ad25d63ce3acdfe8ff8d64bdedec18914256c59b838218708b16/ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128", size = 699367 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f", size = 753345 },
+    { url = "https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599", size = 4424962 },
+    { url = "https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab", size = 4475201 },
+    { url = "https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07", size = 213195 },
+]
+
+[[package]]
+name = "mpmath"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
+]
+
+[[package]]
+name = "myst-parser"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "docutils" },
+    { name = "jinja2" },
+    { name = "markdown-it-py" },
+    { name = "mdit-py-plugins" },
+    { name = "pyyaml" },
+    { name = "sphinx" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/85/55/6d1741a1780e5e65038b74bce6689da15f620261c490c3511eb4c12bac4b/myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531", size = 93858 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ca/b4/b036f8fdb667587bb37df29dc6644681dd78b7a2a6321a34684b79412b28/myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d", size = 84563 },
+]
+
+[[package]]
+name = "namex"
+version = "0.0.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9d/48/d275cdb6216c6bb4f9351675795a0b48974e138f16b1ffe0252c1f8faa28/namex-0.0.8.tar.gz", hash = "sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b", size = 6623 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 },
+]
+
+[[package]]
+name = "natsort"
+version = "8.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 },
+]
+
+[[package]]
+name = "numpy"
+version = "2.1.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 },
+    { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 },
+    { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 },
+    { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 },
+    { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 },
+    { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 },
+    { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 },
+    { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 },
+    { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 },
+    { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 },
+    { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 },
+    { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 },
+    { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 },
+    { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 },
+    { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 },
+    { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 },
+    { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 },
+    { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 },
+    { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 },
+    { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 },
+]
+
+[[package]]
+name = "nvidia-nccl-cu12"
+version = "2.23.4"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c8/3a/0112397396dec37ffc8edd7836d48261b4d14ca60ec8ed7bc857cce1d916/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa946c8327e22ced28e7cef508a334673abc42064ec85f02d005ba1785ea4cec", size = 198953892 },
+    { url = "https://files.pythonhosted.org/packages/ed/1f/6482380ec8dcec4894e7503490fc536d846b0d59694acad9cf99f27d0e7d/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b097258d9aab2fa9f686e33c6fe40ae57b27df60cedbd15d139701bb5509e0c1", size = 198954603 },
+]
+
+[[package]]
+name = "oauthlib"
+version = "3.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 },
+]
+
+[[package]]
+name = "opencv-python"
+version = "4.10.0.84"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy", marker = "python_full_version >= '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 },
+    { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 },
+    { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 },
+    { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 },
+    { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 },
+    { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 },
+]
+
+[[package]]
+name = "optree"
+version = "0.13.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/f2/56afdaeaae36b076659be7db8e72be0924dd64ebd1c131675c77f7e704a6/optree-0.13.1.tar.gz", hash = "sha256:af67856aa8073d237fe67313d84f8aeafac32c1cef7239c628a2768d02679c43", size = 155738 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/3f/53/f3727cad24f16a06666f328f1212476988cadac9b9e7919ddfb2c22eb662/optree-0.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f788b2ad120deb73b4908a74473cd6de79cfb9f33bbe9dcb59cea2e2477d4e28", size = 608270 },
+    { url = "https://files.pythonhosted.org/packages/64/f2/68beb9da2dd52baa50e7a589ed2bd8434fdd70cdba06754aa5910263da06/optree-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2909cb42add6bb1a5a2b0243bdd8c4b861bf072f3741e26239481907ac8ad4e6", size = 325703 },
+    { url = "https://files.pythonhosted.org/packages/45/db/08921e56f3425bf649eb593eb28775263c935d029985d35572dc5690cc1a/optree-0.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc5fa2ff5090389f3a906567446f01d692bd6fe5cfcc5ae2d5861f24e8e0e4d", size = 355813 },
+    { url = "https://files.pythonhosted.org/packages/e5/e3/587e0d28dc2cee064902adfebca97db124e12b275dbe9c2b05a70a22345f/optree-0.13.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4711f5cac5a2a49c3d6c9f0eca7b77c22b452170bb33ea01c3214ebb17931db9", size = 402566 },
+    { url = "https://files.pythonhosted.org/packages/8a/1d/0d5bbab8c99580b732b89ef2c5fcdd6ef410478295949fdf2984fa1bfc28/optree-0.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c4ab1d391b89cb88eb3c63383d5eb0930bc21141de9d5acd277feed9e38eb65", size = 397005 },
+    { url = "https://files.pythonhosted.org/packages/16/fa/fc2a8183e14f0d195d25824bf65095ff32b34bd469614a6c30d0a596a30f/optree-0.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5e5f09c85ae558a6bdaea57e63168082e728e777391393e9e2792f0d15b7b59", size = 369400 },
+    { url = "https://files.pythonhosted.org/packages/9f/42/8c08ce4ebb3d9a6e4415f1a97830c84879e2d1a43710a7c8a18b2c3e169d/optree-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8ee1e988c634a451146b87d9ebdbf650a75dc1f52a9cffcd89fabb7289321c", size = 390179 },
+    { url = "https://files.pythonhosted.org/packages/06/02/3a701d6307fdfefe4fcecbac644803e2a4314ab2406ff465e03129cc85f6/optree-0.13.1-cp313-cp313-win32.whl", hash = "sha256:5b6531cd4eb23fadbbf77faf834e1119da06d7af3154f55786b59953cd87bb8a", size = 264264 },
+    { url = "https://files.pythonhosted.org/packages/ef/f9/8a1421181c5eb0c0f81d1423a900baeb3faba68a48747bbdffb7581239ac/optree-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:27d81dc43b522ba47ba7d2e7d91dbb486940348b1bf85caeb0afc2815c0aa492", size = 293682 },
+    { url = "https://files.pythonhosted.org/packages/80/34/d1b1849a6240385c4a3af5da9425b11912204d0b1cf142d802815319b73a/optree-0.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:f39c7174a3f3cdc3f5fe6fb4b832f608c40ac174d7567ed6734b2ee952094631", size = 293670 },
+    { url = "https://files.pythonhosted.org/packages/0d/d6/f81e6748bcc3f35a2f570a814014e3418b0ed425d7cbc2b42d88d12863d5/optree-0.13.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3010ae24e994f6e00071098d34e98e78eb995b7454a2ef629a0bf7df17441b24", size = 702861 },
+    { url = "https://files.pythonhosted.org/packages/08/7f/70a2d02110ccb245bc57bd9ad57668acfea0ff364c27d7dfe1735ede79ed/optree-0.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b5626c38d4a18a144063db5c1dbb558431d83ca10682324f74665a12214801f", size = 370740 },
+    { url = "https://files.pythonhosted.org/packages/63/37/4ddf05267467809236203e2007e9443519c4d55e0744ce7eea1aa74dffee/optree-0.13.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1935639dd498a42367633e3877797e1330e39d44d48bbca1a136bb4dbe4c1bc9", size = 374695 },
+    { url = "https://files.pythonhosted.org/packages/19/f2/51a63a799f6dce31813d7e02a7547394aebcb39f407e62038ecbd999d490/optree-0.13.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01819c3df950696f32c91faf8d376ae6b695ffdba18f330f1cab6b8e314e4612", size = 418671 },
+    { url = "https://files.pythonhosted.org/packages/f0/7c/a08191e0c9202f2be9c415057eea3cf3a5af18e9a6d81f4c7b0e6faf0a1f/optree-0.13.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48c29d9c6c64c8dc48c8ee97f7c1d5cdb83e37320f0be0857c06ce4b97994aea", size = 414966 },
+    { url = "https://files.pythonhosted.org/packages/8f/37/7bf815f4da7234e387863228b17246b42b8c02553882581a4013a64a88d0/optree-0.13.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:025d23400b8b579462a251420f0a9ae77d3d3593f84276f3465985731d79d722", size = 389219 },
+    { url = "https://files.pythonhosted.org/packages/3d/84/bb521a66d3a84fe2f1500ef67d245c2cc1a26277fcaaf4bc70b22c06e99b/optree-0.13.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55e82426bef151149cfa41d68ac957730fcd420996c0db8324fca81aa6a810ba", size = 405377 },
+    { url = "https://files.pythonhosted.org/packages/06/99/3eb53829c4c0b6dc20115d957d2d8e945630ddf40c656dc4e39c5a6e51f2/optree-0.13.1-cp313-cp313t-win32.whl", hash = "sha256:e40f018f522fcfd244688d1b3a360518e636ba7f636385aae0566eae3e7d29bc", size = 292734 },
+    { url = "https://files.pythonhosted.org/packages/2f/59/d7601959ad0b90d309794c0975a256304488b4c5671f24e3e12101ade7ef/optree-0.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d580f1bf23bb352c4db6b3544f282f1ac08dcb0d9ab537d25e56220353438cf7", size = 331457 },
+    { url = "https://files.pythonhosted.org/packages/8b/36/c01a5bc34660d46c6a3b1fe090bbdc8c76af7b5c1a6613cc671aa6df8349/optree-0.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c4d13f55dbd509d27be3af54d53b4ca0751bc518244ced6d0567e518e51452a2", size = 331470 },
+]
+
+[[package]]
+name = "packaging"
+version = "24.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
+]
+
+[[package]]
+name = "pandas"
+version = "2.2.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy", marker = "python_full_version >= '3.13'" },
+    { name = "python-dateutil" },
+    { name = "pytz" },
+    { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
+    { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
+    { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
+    { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 },
+    { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 },
+    { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 },
+    { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 },
+    { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 },
+    { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 },
+    { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 },
+    { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 },
+    { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 },
+    { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
+]
+
+[[package]]
+name = "patsy"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923 },
+]
+
+[[package]]
+name = "pillow"
+version = "11.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 },
+    { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 },
+    { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 },
+    { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 },
+    { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 },
+    { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 },
+    { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 },
+    { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 },
+    { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 },
+    { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 },
+    { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 },
+    { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 },
+    { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 },
+    { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 },
+    { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 },
+    { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 },
+    { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 },
+    { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 },
+    { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
+]
+
+[[package]]
+name = "pygments"
+version = "2.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 },
+]
+
+[[package]]
+name = "pytest"
+version = "8.3.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "colorama", marker = "sys_platform == 'win32'" },
+    { name = "iniconfig" },
+    { name = "packaging" },
+    { name = "pluggy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
+]
+
+[[package]]
+name = "pytest-cov"
+version = "6.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "coverage" },
+    { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
+]
+
+[[package]]
+name = "pytz"
+version = "2024.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
+    { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
+    { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
+    { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
+    { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
+    { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
+    { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
+    { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
+    { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
+]
+
+[[package]]
+name = "requests"
+version = "2.32.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "charset-normalizer" },
+    { name = "idna" },
+    { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
+]
+
+[[package]]
+name = "requests-oauthlib"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "oauthlib" },
+    { name = "requests" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/52/531ef197b426646f26b53815a7d2a67cb7a331ef098bb276db26a68ac49f/requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", size = 52027 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/6f/bb/5deac77a9af870143c684ab46a7934038a53eb4aa975bc0687ed6ca2c610/requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", size = 23892 },
+]
+
+[[package]]
+name = "rich"
+version = "13.9.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "markdown-it-py" },
+    { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
+]
+
+[[package]]
+name = "ruff"
+version = "0.8.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 },
+    { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 },
+    { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 },
+    { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 },
+    { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 },
+    { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 },
+    { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 },
+    { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 },
+    { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 },
+    { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 },
+    { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 },
+    { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 },
+    { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 },
+    { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 },
+    { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 },
+    { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 },
+    { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 },
+]
+
+[[package]]
+name = "scikit-learn"
+version = "1.5.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "joblib" },
+    { name = "numpy" },
+    { name = "scipy" },
+    { name = "threadpoolctl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/37/59/44985a2bdc95c74e34fef3d10cb5d93ce13b0e2a7baefffe1b53853b502d/scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d", size = 7001680 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a4/50/8891028437858cc510e13578fe7046574a60c2aaaa92b02d64aac5b1b412/scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5", size = 12025584 },
+    { url = "https://files.pythonhosted.org/packages/d2/79/17feef8a1c14149436083bec0e61d7befb4812e272d5b20f9d79ea3e9ab1/scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908", size = 10959795 },
+    { url = "https://files.pythonhosted.org/packages/b1/c8/f08313f9e2e656bd0905930ae8bf99a573ea21c34666a813b749c338202f/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3", size = 12077302 },
+    { url = "https://files.pythonhosted.org/packages/a7/48/fbfb4dc72bed0fe31fe045fb30e924909ad03f717c36694351612973b1a9/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12", size = 13002811 },
+    { url = "https://files.pythonhosted.org/packages/a5/e7/0c869f9e60d225a77af90d2aefa7a4a4c0e745b149325d1450f0f0ce5399/scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f", size = 10951354 },
+]
+
+[[package]]
+name = "scipy"
+version = "1.14.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 },
+    { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 },
+    { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 },
+    { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 },
+    { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 },
+    { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 },
+    { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 },
+    { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 },
+]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
+]
+
+[[package]]
+name = "sphinx"
+version = "8.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "alabaster" },
+    { name = "babel" },
+    { name = "colorama", marker = "sys_platform == 'win32'" },
+    { name = "docutils" },
+    { name = "imagesize" },
+    { name = "jinja2" },
+    { name = "packaging" },
+    { name = "pygments" },
+    { name = "requests" },
+    { name = "snowballstemmer" },
+    { name = "sphinxcontrib-applehelp" },
+    { name = "sphinxcontrib-devhelp" },
+    { name = "sphinxcontrib-htmlhelp" },
+    { name = "sphinxcontrib-jsmath" },
+    { name = "sphinxcontrib-qthelp" },
+    { name = "sphinxcontrib-serializinghtml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 },
+]
+
+[[package]]
+name = "sphinx-autoapi"
+version = "3.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "astroid", marker = "python_full_version >= '3.13'" },
+    { name = "jinja2" },
+    { name = "pyyaml" },
+    { name = "sphinx" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4a/eb/cc243583bb1d518ca3b10998c203d919a8ed90affd4831f2b61ad09043d2/sphinx_autoapi-3.4.0.tar.gz", hash = "sha256:e6d5371f9411bbb9fca358c00a9e57aef3ac94cbfc5df4bab285946462f69e0c", size = 29292 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/de/d6/f2acdc2567337fd5f5dc091a4e58d8a0fb14927b9779fc1e5ecee96d9824/sphinx_autoapi-3.4.0-py3-none-any.whl", hash = "sha256:4027fef2875a22c5f2a57107c71641d82f6166bf55beb407a47aaf3ef14e7b92", size = 34095 },
+]
+
+[[package]]
+name = "sphinx-pyproject"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "dom-toml" },
+    { name = "domdf-python-tools" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076 },
+]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 },
+]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 },
+]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 },
+]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 },
+]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 },
+]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 },
+]
+
+[[package]]
+name = "statsmodels"
+version = "0.14.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "packaging" },
+    { name = "pandas" },
+    { name = "patsy" },
+    { name = "scipy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1f/3b/963a015dd8ea17e10c7b0e2f14d7c4daec903baf60a017e756b57953a4bf/statsmodels-0.14.4.tar.gz", hash = "sha256:5d69e0f39060dc72c067f9bb6e8033b6dccdb0bae101d76a7ef0bcc94e898b67", size = 20354802 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/31/f8/2662e6a101315ad336f75168fa9bac71f913ebcb92a6be84031d84a0f21f/statsmodels-0.14.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5a24f5d2c22852d807d2b42daf3a61740820b28d8381daaf59dcb7055bf1a79", size = 10186886 },
+    { url = "https://files.pythonhosted.org/packages/fa/c0/ee6e8ed35fc1ca9c7538c592f4974547bf72274bc98db1ae4a6e87481a83/statsmodels-0.14.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df4f7864606fa843d7e7c0e6af288f034a2160dba14e6ccc09020a3cf67cb092", size = 9880066 },
+    { url = "https://files.pythonhosted.org/packages/d1/97/3380ca6d8fd66cfb3d12941e472642f26e781a311c355a4e97aab2ed0216/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91341cbde9e8bea5fb419a76e09114e221567d03f34ca26e6d67ae2c27d8fe3c", size = 10283521 },
+    { url = "https://files.pythonhosted.org/packages/fe/2a/55c5b5c5e5124a202ea3fe0bcdbdeceaf91b4ec6164b8434acb9dd97409c/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1322286a7bfdde2790bf72d29698a1b76c20b8423a55bdcd0d457969d0041f72", size = 10723228 },
+    { url = "https://files.pythonhosted.org/packages/4f/76/67747e49dc758daae06f33aad8247b718cd7d224f091d2cd552681215bb2/statsmodels-0.14.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e31b95ac603415887c9f0d344cb523889cf779bc52d68e27e2d23c358958fec7", size = 10859503 },
+    { url = "https://files.pythonhosted.org/packages/1d/eb/cb8b01f5edf8f135eb3d0553d159db113a35b2948d0e51eeb735e7ae09ea/statsmodels-0.14.4-cp313-cp313-win_amd64.whl", hash = "sha256:81030108d27aecc7995cac05aa280cf8c6025f6a6119894eef648997936c2dd0", size = 9817574 },
+]
+
+[[package]]
+name = "sympy"
+version = "1.13.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "mpmath" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 },
+]
+
+[[package]]
+name = "thealgorithms-python"
+version = "0.0.1"
+source = { virtual = "." }
+dependencies = [
+    { name = "beautifulsoup4" },
+    { name = "fake-useragent" },
+    { name = "imageio" },
+    { name = "keras" },
+    { name = "lxml" },
+    { name = "matplotlib" },
+    { name = "numpy" },
+    { name = "opencv-python" },
+    { name = "pandas" },
+    { name = "pillow" },
+    { name = "requests" },
+    { name = "rich" },
+    { name = "scikit-learn" },
+    { name = "sphinx-pyproject" },
+    { name = "statsmodels" },
+    { name = "sympy" },
+    { name = "tweepy" },
+    { name = "typing-extensions" },
+    { name = "xgboost" },
+]
+
+[package.dev-dependencies]
+dev = [
+    { name = "pytest" },
+    { name = "pytest-cov" },
+]
+docs = [
+    { name = "myst-parser" },
+    { name = "sphinx-autoapi" },
+    { name = "sphinx-pyproject" },
+]
+euler-validate = [
+    { name = "numpy" },
+    { name = "pytest" },
+    { name = "pytest-cov" },
+    { name = "requests" },
+]
+lint = [
+    { name = "codespell" },
+    { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+    { name = "beautifulsoup4", specifier = ">=4.12.3" },
+    { name = "fake-useragent", specifier = ">=1.5.1" },
+    { name = "imageio", specifier = ">=2.36.1" },
+    { name = "keras", specifier = ">=3.7" },
+    { name = "lxml", specifier = ">=5.3" },
+    { name = "matplotlib", specifier = ">=3.9.3" },
+    { name = "numpy", specifier = ">=2.1.3" },
+    { name = "opencv-python", specifier = ">=4.10.0.84" },
+    { name = "pandas", specifier = ">=2.2.3" },
+    { name = "pillow", specifier = ">=11" },
+    { name = "requests", specifier = ">=2.32.3" },
+    { name = "rich", specifier = ">=13.9.4" },
+    { name = "scikit-learn", specifier = ">=1.5.2" },
+    { name = "sphinx-pyproject", specifier = ">=0.3" },
+    { name = "statsmodels", specifier = ">=0.14.4" },
+    { name = "sympy", specifier = ">=1.13.3" },
+    { name = "tweepy", specifier = ">=4.14" },
+    { name = "typing-extensions", specifier = ">=4.12.2" },
+    { name = "xgboost", specifier = ">=2.1.3" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+    { name = "pytest", specifier = ">=8.3.4" },
+    { name = "pytest-cov", specifier = ">=6" },
+]
+docs = [
+    { name = "myst-parser", specifier = ">=4.0.0" },
+    { name = "sphinx-autoapi", specifier = ">=3.4.0" },
+    { name = "sphinx-pyproject", specifier = ">=0.3.0" },
+]
+euler-validate = [
+    { name = "numpy", specifier = ">=2.1.3" },
+    { name = "pytest", specifier = ">=8.3.4" },
+    { name = "pytest-cov", specifier = ">=6.0.0" },
+    { name = "requests", specifier = ">=2.32.3" },
+]
+lint = [
+    { name = "codespell", specifier = ">=2.3" },
+    { name = "ruff", specifier = ">=0.8.1" },
+]
+
+[[package]]
+name = "threadpoolctl"
+version = "3.5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 },
+]
+
+[[package]]
+name = "tomli"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
+    { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
+    { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
+    { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
+    { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
+    { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
+    { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
+    { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
+    { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
+    { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
+    { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
+]
+
+[[package]]
+name = "tweepy"
+version = "4.14.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "oauthlib" },
+    { name = "requests" },
+    { name = "requests-oauthlib" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/75/1c/0db8c3cf9d31bf63853ff612d201060ae78e6db03468a70e063bef0eda62/tweepy-4.14.0.tar.gz", hash = "sha256:1f9f1707d6972de6cff6c5fd90dfe6a449cd2e0d70bd40043ffab01e07a06c8c", size = 88623 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/4d/78/ba0065d5636bbf4a35b78c4f81b74e7858b609cdf69e629d6da5c91b9d92/tweepy-4.14.0-py3-none-any.whl", hash = "sha256:db6d3844ccc0c6d27f339f12ba8acc89912a961da513c1ae50fa2be502a56afb", size = 98520 },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.12.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
+]
+
+[[package]]
+name = "tzdata"
+version = "2024.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
+]
+
+[[package]]
+name = "xgboost"
+version = "2.1.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "numpy" },
+    { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and platform_system == 'Linux'" },
+    { name = "scipy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/48/b0/131ffc4a15fd3acee9be3a7baa6b2fa6faa479799c51b880de9fc3ddf550/xgboost-2.1.3.tar.gz", hash = "sha256:7699ec4226156887d3afc665c63ab87469db9d46e361c702ba9fccd22535730c", size = 1090326 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/cd/c6/773ebd84414879bd0566788868ae46a6574f6efaf81e694f01ea1fed3277/xgboost-2.1.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:c9b0c92f13e3650e1e1cf92ff9ecef3efc6f5dc3d10ce17858df2081a89976ef", size = 2139909 },
+    { url = "https://files.pythonhosted.org/packages/28/3c/ddf5d9eb742cdb7fbcd5c854bce07471bad01194ac37de91db64fbef0c58/xgboost-2.1.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:fcbf1912a852bd07a7007be350c8dc3a484c5e775b612f2b3cd082fc76240eb3", size = 1938631 },
+    { url = "https://files.pythonhosted.org/packages/4a/3a/8cd69a216993fd9d54ceb079d1b357b7ef50678b3c2695d8a71962b8d0aa/xgboost-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:27af88df1162cee016c67f267a0a16c3db1c48f256e12f64c45c8f8edf9571cd", size = 4441261 },
+    { url = "https://files.pythonhosted.org/packages/48/bc/05d7db90d421c5e3d681a12fd1eb087e37bf2e9bbe2b105422d6319ecc92/xgboost-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32a43526208fe676527c698cb852e0e9515e6d7294143780e476d335290a131b", size = 4532380 },
+    { url = "https://files.pythonhosted.org/packages/0f/c8/f679a816c06a4a6d23da3f4b448d5f0615b51de2886ad3e3e695d17121b3/xgboost-2.1.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5d33090880f3d474f8cf5dda557c7bf8dbceefb62f2fd655c77efcabb9cac222", size = 4207000 },
+    { url = "https://files.pythonhosted.org/packages/32/93/66826e2f50cefecbb0a44bd1e667316bf0a3c8e78cd1f0cdf52f5b2c5c6f/xgboost-2.1.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8d85d38553855a1f8c40b8fbccca86af19202f91b244e2c7f77afbb2a6d9d785", size = 153894508 },
+    { url = "https://files.pythonhosted.org/packages/70/58/2f94976df39470fb00eec2cb4f914dde44cd0df8d96483208bf7db4bc97e/xgboost-2.1.3-py3-none-win_amd64.whl", hash = "sha256:25c0ffcbd62aac5bc22c79e08b5b2edad1d5e37f16610ebefa5f06f3e2ea3d96", size = 124909665 },
+]

From 98391e33ea2a87375a7f744eba3d57918237b4e7 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Thu, 12 Dec 2024 18:40:43 +0100
Subject: [PATCH 002/135] [pre-commit.ci] pre-commit autoupdate (#12428)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.1 → v0.8.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.1...v0.8.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index bef251749c19..884b10661a49 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.1
+    rev: v0.8.2
     hooks:
       - id: ruff
       - id: ruff-format

From f8e595e048f1cbd763e0a1f8c0ffb4dff335b841 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 16 Dec 2024 20:53:36 +0100
Subject: [PATCH 003/135] [pre-commit.ci] pre-commit autoupdate (#12439)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.2 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.2...v0.8.3)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 884b10661a49..0c8108ac55be 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.2
+    rev: v0.8.3
     hooks:
       - id: ruff
       - id: ruff-format

From 4abfce2791c081f65580bc1fefdf5a4d8ee7b5fc Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 23 Dec 2024 06:55:22 +0300
Subject: [PATCH 004/135] Fix sphinx/build_docs warnings for audio_filters
 (#12449)

* updating DIRECTORY.md

* Fix sphinx/build_docs warnings for audio_filters

* Improve

* Fix

* Fix

* Fix

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
---
 audio_filters/iir_filter.py | 32 +++++++++++++++++++-------------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/audio_filters/iir_filter.py b/audio_filters/iir_filter.py
index f3c1ad43b001..fa3e6c54b33f 100644
--- a/audio_filters/iir_filter.py
+++ b/audio_filters/iir_filter.py
@@ -10,13 +10,17 @@ class IIRFilter:
 
     Implementation details:
     Based on the 2nd-order function from
-     https://en.wikipedia.org/wiki/Digital_biquad_filter,
+    https://en.wikipedia.org/wiki/Digital_biquad_filter,
     this generalized N-order function was made.
 
     Using the following transfer function
-    H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
+        .. math:: H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}
+                  {a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
+
     we can rewrite this to
-    y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
+        .. math:: y[n]={\frac{1}{a_{0}}}
+                  \left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-
+                  \left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
     """
 
     def __init__(self, order: int) -> None:
@@ -34,17 +38,19 @@ def __init__(self, order: int) -> None:
 
     def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
         """
-        Set the coefficients for the IIR filter. These should both be of size order + 1.
-        a_0 may be left out, and it will use 1.0 as default value.
+        Set the coefficients for the IIR filter.
+        These should both be of size `order` + 1.
+        :math:`a_0` may be left out, and it will use 1.0 as default value.
 
         This method works well with scipy's filter design functions
-            >>> # Make a 2nd-order 1000Hz butterworth lowpass filter
-            >>> import scipy.signal
-            >>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
-            ...                                          btype='lowpass',
-            ...                                          fs=48000)
-            >>> filt = IIRFilter(2)
-            >>> filt.set_coefficients(a_coeffs, b_coeffs)
+
+        >>> # Make a 2nd-order 1000Hz butterworth lowpass filter
+        >>> import scipy.signal
+        >>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
+        ...                                          btype='lowpass',
+        ...                                          fs=48000)
+        >>> filt = IIRFilter(2)
+        >>> filt.set_coefficients(a_coeffs, b_coeffs)
         """
         if len(a_coeffs) < self.order:
             a_coeffs = [1.0, *a_coeffs]
@@ -68,7 +74,7 @@ def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None
 
     def process(self, sample: float) -> float:
         """
-        Calculate y[n]
+        Calculate :math:`y[n]`
 
         >>> filt = IIRFilter(2)
         >>> filt.process(0)

From 47cd21a110d8e2fc038414bc7f3c7ca8e91d6653 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 23 Dec 2024 14:56:42 +0300
Subject: [PATCH 005/135] Fix sphinx/build_docs warnings for cellular_automata
 (#12454)

* updating DIRECTORY.md

* Fix sphinx/build_docs warnings for cellular_automata

* Fix

* Improve

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
---
 cellular_automata/wa_tor.py | 54 ++++++++++++++++++-------------------
 1 file changed, 26 insertions(+), 28 deletions(-)

diff --git a/cellular_automata/wa_tor.py b/cellular_automata/wa_tor.py
index e423d1595bdb..29f7ea510bfe 100644
--- a/cellular_automata/wa_tor.py
+++ b/cellular_automata/wa_tor.py
@@ -1,9 +1,9 @@
 """
 Wa-Tor algorithm (1984)
 
-@ https://en.wikipedia.org/wiki/Wa-Tor
-@ https://beltoforion.de/en/wator/
-@ https://beltoforion.de/en/wator/images/wator_medium.webm
+| @ https://en.wikipedia.org/wiki/Wa-Tor
+| @ https://beltoforion.de/en/wator/
+| @ https://beltoforion.de/en/wator/images/wator_medium.webm
 
 This solution aims to completely remove any systematic approach
 to the Wa-Tor planet, and utilise fully random methods.
@@ -97,8 +97,8 @@ class WaTor:
 
     :attr time_passed: A function that is called every time
         time passes (a chronon) in order to visually display
-        the new Wa-Tor planet. The time_passed function can block
-        using time.sleep to slow the algorithm progression.
+        the new Wa-Tor planet. The `time_passed` function can block
+        using ``time.sleep`` to slow the algorithm progression.
 
     >>> wt = WaTor(10, 15)
     >>> wt.width
@@ -216,7 +216,7 @@ def get_surrounding_prey(self, entity: Entity) -> list[Entity]:
         """
         Returns all the prey entities around (N, S, E, W) a predator entity.
 
-        Subtly different to the try_to_move_to_unoccupied square.
+        Subtly different to the `move_and_reproduce`.
 
         >>> wt = WaTor(WIDTH, HEIGHT)
         >>> wt.set_planet([
@@ -260,7 +260,7 @@ def move_and_reproduce(
         """
         Attempts to move to an unoccupied neighbouring square
         in either of the four directions (North, South, East, West).
-        If the move was successful and the remaining_reproduction time is
+        If the move was successful and the `remaining_reproduction_time` is
         equal to 0, then a new prey or predator can also be created
         in the previous square.
 
@@ -351,12 +351,12 @@ def perform_prey_actions(
         Performs the actions for a prey entity
 
         For prey the rules are:
-          1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
-            squares. If there are no free squares, no movement takes place.
-          2. Once a prey has survived a certain number of chronons it may reproduce.
-            This is done as it moves to a neighbouring square,
-            leaving behind a new prey in its old position.
-            Its reproduction time is also reset to zero.
+            1. At each chronon, a prey moves randomly to one of the adjacent unoccupied
+               squares. If there are no free squares, no movement takes place.
+            2. Once a prey has survived a certain number of chronons it may reproduce.
+               This is done as it moves to a neighbouring square,
+               leaving behind a new prey in its old position.
+               Its reproduction time is also reset to zero.
 
         >>> wt = WaTor(WIDTH, HEIGHT)
         >>> reproducable_entity = Entity(True, coords=(0, 1))
@@ -382,15 +382,15 @@ def perform_predator_actions(
         :param occupied_by_prey_coords: Move to this location if there is prey there
 
         For predators the rules are:
-          1. At each chronon, a predator moves randomly to an adjacent square occupied
-            by a prey. If there is none, the predator moves to a random adjacent
-            unoccupied square. If there are no free squares, no movement takes place.
-          2. At each chronon, each predator is deprived of a unit of energy.
-          3. Upon reaching zero energy, a predator dies.
-          4. If a predator moves to a square occupied by a prey,
-            it eats the prey and earns a certain amount of energy.
-          5. Once a predator has survived a certain number of chronons
-          it may reproduce in exactly the same way as the prey.
+            1. At each chronon, a predator moves randomly to an adjacent square occupied
+               by a prey. If there is none, the predator moves to a random adjacent
+               unoccupied square. If there are no free squares, no movement takes place.
+            2. At each chronon, each predator is deprived of a unit of energy.
+            3. Upon reaching zero energy, a predator dies.
+            4. If a predator moves to a square occupied by a prey,
+               it eats the prey and earns a certain amount of energy.
+            5. Once a predator has survived a certain number of chronons
+               it may reproduce in exactly the same way as the prey.
 
         >>> wt = WaTor(WIDTH, HEIGHT)
         >>> wt.set_planet([[Entity(True, coords=(0, 0)), Entity(False, coords=(0, 1))]])
@@ -430,7 +430,7 @@ def perform_predator_actions(
 
     def run(self, *, iteration_count: int) -> None:
         """
-        Emulate time passing by looping iteration_count times
+        Emulate time passing by looping `iteration_count` times
 
         >>> wt = WaTor(WIDTH, HEIGHT)
         >>> wt.run(iteration_count=PREDATOR_INITIAL_ENERGY_VALUE - 1)
@@ -484,11 +484,9 @@ def visualise(wt: WaTor, iter_number: int, *, colour: bool = True) -> None:
     an ascii code in terminal to clear and re-print
     the Wa-Tor planet at intervals.
 
-    Uses ascii colour codes to colourfully display
-    the predators and prey.
-
-    (0x60f197) Prey = #
-    (0xfffff) Predator = x
+    Uses ascii colour codes to colourfully display the predators and prey:
+        * (0x60f197) Prey = ``#``
+        * (0xfffff) Predator = ``x``
 
     >>> wt = WaTor(30, 30)
     >>> wt.set_planet([

From c5e603ae4234e5d516d700b01d47f78d42c18008 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 23 Dec 2024 15:43:16 +0300
Subject: [PATCH 006/135] Fix sphinx/build_docs warnings for geodesy (#12462)

* updating DIRECTORY.md

* Fix sphinx/build_docs warnings for geodesy/haversine_distance.py

* Improve

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
---
 geodesy/haversine_distance.py | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/geodesy/haversine_distance.py b/geodesy/haversine_distance.py
index 93e625770f9d..39cd250af965 100644
--- a/geodesy/haversine_distance.py
+++ b/geodesy/haversine_distance.py
@@ -21,10 +21,11 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl
     computation like Haversine can be handy for shorter range distances.
 
     Args:
-        lat1, lon1: latitude and longitude of coordinate 1
-        lat2, lon2: latitude and longitude of coordinate 2
+        * `lat1`, `lon1`: latitude and longitude of coordinate 1
+        * `lat2`, `lon2`: latitude and longitude of coordinate 2
     Returns:
         geographical distance between two points in metres
+
     >>> from collections import namedtuple
     >>> point_2d = namedtuple("point_2d", "lat lon")
     >>> SAN_FRANCISCO = point_2d(37.774856, -122.424227)

From b0cb13fea54854b3a60eced27026db9a9c5dc5ab Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 23 Dec 2024 16:11:58 +0300
Subject: [PATCH 007/135] Fix sphinx/build_docs warnings for greedy_methods
 (#12463)

* updating DIRECTORY.md

* Fix sphinx/build_docs warnings for greedy_methods

* Improve

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
---
 greedy_methods/smallest_range.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/greedy_methods/smallest_range.py b/greedy_methods/smallest_range.py
index e2b7f8d7e96a..9adb12bf9029 100644
--- a/greedy_methods/smallest_range.py
+++ b/greedy_methods/smallest_range.py
@@ -14,12 +14,13 @@ def smallest_range(nums: list[list[int]]) -> list[int]:
     Uses min heap for efficiency. The range includes at least one number from each list.
 
     Args:
-        nums: List of k sorted integer lists.
+        `nums`: List of k sorted integer lists.
 
     Returns:
         list: Smallest range as a two-element list.
 
     Examples:
+
     >>> smallest_range([[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]])
     [20, 24]
     >>> smallest_range([[1, 2, 3], [1, 2, 3], [1, 2, 3]])

From 04fbfd6eae38b9897c1b8ff6aee487dd2523665b Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Tue, 24 Dec 2024 03:14:11 +0300
Subject: [PATCH 008/135] Fix sphinx/build_docs warnings for maths/volume
 (#12464)

* Fix sphinx/build_docs warnings for maths/volume

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix
---
 maths/volume.py | 149 ++++++++++++++++++++++++++++++------------------
 1 file changed, 95 insertions(+), 54 deletions(-)

diff --git a/maths/volume.py b/maths/volume.py
index 23fcf6be6ef1..08bdf72b013b 100644
--- a/maths/volume.py
+++ b/maths/volume.py
@@ -1,5 +1,6 @@
 """
 Find the volume of various shapes.
+
 * https://en.wikipedia.org/wiki/Volume
 * https://en.wikipedia.org/wiki/Spherical_cap
 """
@@ -12,6 +13,7 @@
 def vol_cube(side_length: float) -> float:
     """
     Calculate the Volume of a Cube.
+
     >>> vol_cube(1)
     1.0
     >>> vol_cube(3)
@@ -33,6 +35,7 @@ def vol_cube(side_length: float) -> float:
 def vol_spherical_cap(height: float, radius: float) -> float:
     """
     Calculate the volume of the spherical cap.
+
     >>> vol_spherical_cap(1, 2)
     5.235987755982988
     >>> vol_spherical_cap(1.6, 2.6)
@@ -57,20 +60,29 @@ def vol_spherical_cap(height: float, radius: float) -> float:
 def vol_spheres_intersect(
     radius_1: float, radius_2: float, centers_distance: float
 ) -> float:
-    """
+    r"""
     Calculate the volume of the intersection of two spheres.
+
     The intersection is composed by two spherical caps and therefore its volume is the
-    sum of the volumes of the spherical caps. First, it calculates the heights (h1, h2)
-    of the spherical caps, then the two volumes and it returns the sum.
+    sum of the volumes of the spherical caps.
+    First, it calculates the heights :math:`(h_1, h_2)` of the spherical caps,
+    then the two volumes and it returns the sum.
     The height formulas are
-    h1 = (radius_1 - radius_2 + centers_distance)
-       * (radius_1 + radius_2 - centers_distance)
-       / (2 * centers_distance)
-    h2 = (radius_2 - radius_1 + centers_distance)
-       * (radius_2 + radius_1 - centers_distance)
-       / (2 * centers_distance)
-    if centers_distance is 0 then it returns the volume of the smallers sphere
-    :return vol_spherical_cap(h1, radius_2) + vol_spherical_cap(h2, radius_1)
+
+    .. math::
+        h_1 = \frac{(radius_1 - radius_2 + centers\_distance)
+                    \cdot (radius_1 + radius_2 - centers\_distance)}
+                   {2 \cdot centers\_distance}
+
+        h_2 = \frac{(radius_2 - radius_1 + centers\_distance)
+                    \cdot (radius_2 + radius_1 - centers\_distance)}
+                   {2 \cdot centers\_distance}
+
+    if `centers_distance` is 0 then it returns the volume of the smallers sphere
+
+    :return: ``vol_spherical_cap`` (:math:`h_1`, :math:`radius_2`)
+             + ``vol_spherical_cap`` (:math:`h_2`, :math:`radius_1`)
+
     >>> vol_spheres_intersect(2, 2, 1)
     21.205750411731103
     >>> vol_spheres_intersect(2.6, 2.6, 1.6)
@@ -112,14 +124,18 @@ def vol_spheres_intersect(
 def vol_spheres_union(
     radius_1: float, radius_2: float, centers_distance: float
 ) -> float:
-    """
+    r"""
     Calculate the volume of the union of two spheres that possibly intersect.
-    It is the sum of sphere A and sphere B minus their intersection.
-    First, it calculates the volumes (v1, v2) of the spheres,
-    then the volume of the intersection (i) and it returns the sum v1+v2-i.
-    If centers_distance is 0 then it returns the volume of the larger sphere
-    :return vol_sphere(radius_1) + vol_sphere(radius_2)
-                - vol_spheres_intersect(radius_1, radius_2, centers_distance)
+
+    It is the sum of sphere :math:`A` and sphere :math:`B` minus their intersection.
+    First, it calculates the volumes :math:`(v_1, v_2)` of the spheres,
+    then the volume of the intersection :math:`i` and
+    it returns the sum :math:`v_1 + v_2 - i`.
+    If `centers_distance` is 0 then it returns the volume of the larger sphere
+
+    :return: ``vol_sphere`` (:math:`radius_1`) + ``vol_sphere`` (:math:`radius_2`)
+             - ``vol_spheres_intersect``
+             (:math:`radius_1`, :math:`radius_2`, :math:`centers\_distance`)
 
     >>> vol_spheres_union(2, 2, 1)
     45.814892864851146
@@ -157,7 +173,9 @@ def vol_spheres_union(
 def vol_cuboid(width: float, height: float, length: float) -> float:
     """
     Calculate the Volume of a Cuboid.
-    :return multiple of width, length and height
+
+    :return: multiple of `width`, `length` and `height`
+
     >>> vol_cuboid(1, 1, 1)
     1.0
     >>> vol_cuboid(1, 2, 3)
@@ -185,10 +203,12 @@ def vol_cuboid(width: float, height: float, length: float) -> float:
 
 
 def vol_cone(area_of_base: float, height: float) -> float:
-    """
-    Calculate the Volume of a Cone.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Cone
-    :return (1/3) * area_of_base * height
+    r"""
+    | Calculate the Volume of a Cone.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Cone
+
+    :return: :math:`\frac{1}{3} \cdot area\_of\_base \cdot height`
+
     >>> vol_cone(10, 3)
     10.0
     >>> vol_cone(1, 1)
@@ -212,10 +232,12 @@ def vol_cone(area_of_base: float, height: float) -> float:
 
 
 def vol_right_circ_cone(radius: float, height: float) -> float:
-    """
-    Calculate the Volume of a Right Circular Cone.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Cone
-    :return (1/3) * pi * radius^2 * height
+    r"""
+    | Calculate the Volume of a Right Circular Cone.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Cone
+
+    :return: :math:`\frac{1}{3} \cdot \pi \cdot radius^2 \cdot height`
+
     >>> vol_right_circ_cone(2, 3)
     12.566370614359172
     >>> vol_right_circ_cone(0, 0)
@@ -237,10 +259,12 @@ def vol_right_circ_cone(radius: float, height: float) -> float:
 
 
 def vol_prism(area_of_base: float, height: float) -> float:
-    """
-    Calculate the Volume of a Prism.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry)
-    :return V = Bh
+    r"""
+    | Calculate the Volume of a Prism.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Prism_(geometry)
+
+    :return: :math:`V = B \cdot h`
+
     >>> vol_prism(10, 2)
     20.0
     >>> vol_prism(11, 1)
@@ -264,10 +288,12 @@ def vol_prism(area_of_base: float, height: float) -> float:
 
 
 def vol_pyramid(area_of_base: float, height: float) -> float:
-    """
-    Calculate the Volume of a Pyramid.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry)
-    :return  (1/3) * Bh
+    r"""
+    | Calculate the Volume of a Pyramid.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Pyramid_(geometry)
+
+    :return: :math:`\frac{1}{3} \cdot B \cdot h`
+
     >>> vol_pyramid(10, 3)
     10.0
     >>> vol_pyramid(1.5, 3)
@@ -291,10 +317,12 @@ def vol_pyramid(area_of_base: float, height: float) -> float:
 
 
 def vol_sphere(radius: float) -> float:
-    """
-    Calculate the Volume of a Sphere.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Sphere
-    :return (4/3) * pi * r^3
+    r"""
+    | Calculate the Volume of a Sphere.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Sphere
+
+    :return: :math:`\frac{4}{3} \cdot \pi \cdot r^3`
+
     >>> vol_sphere(5)
     523.5987755982989
     >>> vol_sphere(1)
@@ -315,10 +343,13 @@ def vol_sphere(radius: float) -> float:
 
 
 def vol_hemisphere(radius: float) -> float:
-    """Calculate the volume of a hemisphere
-    Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere
-    Other references: https://www.cuemath.com/geometry/hemisphere
-    :return 2/3 * pi * radius^3
+    r"""
+    | Calculate the volume of a hemisphere
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Hemisphere
+    | Other references: https://www.cuemath.com/geometry/hemisphere
+
+    :return: :math:`\frac{2}{3} \cdot \pi \cdot radius^3`
+
     >>> vol_hemisphere(1)
     2.0943951023931953
     >>> vol_hemisphere(7)
@@ -339,9 +370,12 @@ def vol_hemisphere(radius: float) -> float:
 
 
 def vol_circular_cylinder(radius: float, height: float) -> float:
-    """Calculate the Volume of a Circular Cylinder.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder
-    :return pi * radius^2 * height
+    r"""
+    | Calculate the Volume of a Circular Cylinder.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Cylinder
+
+    :return: :math:`\pi \cdot radius^2 \cdot height`
+
     >>> vol_circular_cylinder(1, 1)
     3.141592653589793
     >>> vol_circular_cylinder(4, 3)
@@ -368,7 +402,9 @@ def vol_circular_cylinder(radius: float, height: float) -> float:
 def vol_hollow_circular_cylinder(
     inner_radius: float, outer_radius: float, height: float
 ) -> float:
-    """Calculate the Volume of a Hollow Circular Cylinder.
+    """
+    Calculate the Volume of a Hollow Circular Cylinder.
+
     >>> vol_hollow_circular_cylinder(1, 2, 3)
     28.274333882308138
     >>> vol_hollow_circular_cylinder(1.6, 2.6, 3.6)
@@ -405,8 +441,9 @@ def vol_hollow_circular_cylinder(
 
 
 def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> float:
-    """Calculate the Volume of a Conical Frustum.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Frustum
+    """
+    | Calculate the Volume of a Conical Frustum.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Frustum
 
     >>> vol_conical_frustum(45, 7, 28)
     48490.482608158454
@@ -443,9 +480,12 @@ def vol_conical_frustum(height: float, radius_1: float, radius_2: float) -> floa
 
 
 def vol_torus(torus_radius: float, tube_radius: float) -> float:
-    """Calculate the Volume of a Torus.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Torus
-    :return 2pi^2 * torus_radius * tube_radius^2
+    r"""
+    | Calculate the Volume of a Torus.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Torus
+
+    :return: :math:`2 \pi^2 \cdot torus\_radius \cdot tube\_radius^2`
+
     >>> vol_torus(1, 1)
     19.739208802178716
     >>> vol_torus(4, 3)
@@ -471,8 +511,9 @@ def vol_torus(torus_radius: float, tube_radius: float) -> float:
 
 
 def vol_icosahedron(tri_side: float) -> float:
-    """Calculate the Volume of an Icosahedron.
-    Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron
+    """
+    | Calculate the Volume of an Icosahedron.
+    | Wikipedia reference: https://en.wikipedia.org/wiki/Regular_icosahedron
 
     >>> from math import isclose
     >>> isclose(vol_icosahedron(2.5), 34.088984228514256)

From e9721aad59743d01e82582017884db528bad3e21 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Tue, 24 Dec 2024 06:06:59 +0300
Subject: [PATCH 009/135] Fix sphinx/build_docs warnings for
 physics/horizontal_projectile_motion (#12467)

---
 physics/horizontal_projectile_motion.py | 68 +++++++++++++++----------
 1 file changed, 41 insertions(+), 27 deletions(-)

diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py
index 80f85a1b7146..60f21c2b39c4 100644
--- a/physics/horizontal_projectile_motion.py
+++ b/physics/horizontal_projectile_motion.py
@@ -1,15 +1,18 @@
 """
 Horizontal Projectile Motion problem in physics.
+
 This algorithm solves a specific problem in which
-the motion starts from the ground as can be seen below:
-      (v = 0)
-               *  *
-           *          *
-        *                *
-      *                    *
-    *                        *
-   *                          *
-GROUND                      GROUND
+the motion starts from the ground as can be seen below::
+
+          (v = 0)
+                   *  *
+               *          *
+            *                *
+          *                    *
+        *                        *
+       *                          *
+    GROUND                      GROUND
+
 For more info: https://en.wikipedia.org/wiki/Projectile_motion
 """
 
@@ -43,14 +46,17 @@ def check_args(init_velocity: float, angle: float) -> None:
 
 
 def horizontal_distance(init_velocity: float, angle: float) -> float:
-    """
+    r"""
     Returns the horizontal distance that the object cover
+
     Formula:
-            v_0^2 * sin(2 * alpha)
-            ---------------------
-                   g
-    v_0 - initial velocity
-    alpha - angle
+        .. math::
+            \frac{v_0^2 \cdot \sin(2 \alpha)}{g}
+
+            v_0 - \text{initial velocity}
+
+            \alpha - \text{angle}
+
     >>> horizontal_distance(30, 45)
     91.77
     >>> horizontal_distance(100, 78)
@@ -70,14 +76,17 @@ def horizontal_distance(init_velocity: float, angle: float) -> float:
 
 
 def max_height(init_velocity: float, angle: float) -> float:
-    """
+    r"""
     Returns the maximum height that the object reach
+
     Formula:
-            v_0^2 * sin^2(alpha)
-            --------------------
-                   2g
-    v_0 - initial velocity
-    alpha - angle
+        .. math::
+            \frac{v_0^2 \cdot \sin^2 (\alpha)}{2 g}
+
+            v_0 - \text{initial velocity}
+
+            \alpha - \text{angle}
+
     >>> max_height(30, 45)
     22.94
     >>> max_height(100, 78)
@@ -97,14 +106,17 @@ def max_height(init_velocity: float, angle: float) -> float:
 
 
 def total_time(init_velocity: float, angle: float) -> float:
-    """
+    r"""
     Returns total time of the motion
+
     Formula:
-            2 * v_0 * sin(alpha)
-            --------------------
-                   g
-    v_0 - initial velocity
-    alpha - angle
+        .. math::
+            \frac{2 v_0 \cdot \sin (\alpha)}{g}
+
+            v_0 - \text{initial velocity}
+
+            \alpha - \text{angle}
+
     >>> total_time(30, 45)
     4.33
     >>> total_time(100, 78)
@@ -125,6 +137,8 @@ def total_time(init_velocity: float, angle: float) -> float:
 
 def test_motion() -> None:
     """
+    Test motion
+
     >>> test_motion()
     """
     v0, angle = 25, 20

From c36aaf0fbcbc0f1a6c82b689ee87e383104b9e96 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Tue, 24 Dec 2024 11:48:37 +0300
Subject: [PATCH 010/135] Fix sphinx/build_docs warnings for
 graphs/check_bipatrite (#12469)

* Fix sphinx/build_docs warnings for graphs/check_bipatrite

* Fix
---
 graphs/check_bipatrite.py | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/graphs/check_bipatrite.py b/graphs/check_bipatrite.py
index 10b9cc965251..213f3f9480b5 100644
--- a/graphs/check_bipatrite.py
+++ b/graphs/check_bipatrite.py
@@ -6,16 +6,17 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
     Check if a graph is bipartite using depth-first search (DFS).
 
     Args:
-        graph: Adjacency list representing the graph.
+        `graph`: Adjacency list representing the graph.
 
     Returns:
-        True if bipartite, False otherwise.
+        ``True`` if bipartite, ``False`` otherwise.
 
     Checks if the graph can be divided into two sets of vertices, such that no two
     vertices within the same set are connected by an edge.
 
     Examples:
-    # FIXME: This test should pass.
+
+    >>> # FIXME: This test should pass.
     >>> is_bipartite_dfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]}))
     Traceback (most recent call last):
         ...
@@ -37,7 +38,7 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
         ...
     KeyError: 0
 
-    # FIXME: This test should fails with KeyError: 4.
+    >>> # FIXME: This test should fails with KeyError: 4.
     >>> is_bipartite_dfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
     False
     >>> is_bipartite_dfs({0: [-1, 3], 1: [0, -2]})
@@ -51,7 +52,8 @@ def is_bipartite_dfs(graph: defaultdict[int, list[int]]) -> bool:
         ...
     KeyError: 0
 
-    # FIXME: This test should fails with TypeError: list indices must be integers or...
+    >>> # FIXME: This test should fails with
+    >>> # TypeError: list indices must be integers or...
     >>> is_bipartite_dfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
     True
     >>> is_bipartite_dfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})
@@ -95,16 +97,17 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
     Check if a graph is bipartite using a breadth-first search (BFS).
 
     Args:
-        graph: Adjacency list representing the graph.
+        `graph`: Adjacency list representing the graph.
 
     Returns:
-        True if bipartite, False otherwise.
+        ``True`` if bipartite, ``False`` otherwise.
 
     Check if the graph can be divided into two sets of vertices, such that no two
     vertices within the same set are connected by an edge.
 
     Examples:
-    # FIXME: This test should pass.
+
+    >>> # FIXME: This test should pass.
     >>> is_bipartite_bfs(defaultdict(list, {0: [1, 2], 1: [0, 3], 2: [0, 4]}))
     Traceback (most recent call last):
         ...
@@ -126,7 +129,7 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
         ...
     KeyError: 0
 
-    # FIXME: This test should fails with KeyError: 4.
+    >>> # FIXME: This test should fails with KeyError: 4.
     >>> is_bipartite_bfs({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2], 9: [0]})
     False
     >>> is_bipartite_bfs({0: [-1, 3], 1: [0, -2]})
@@ -140,7 +143,8 @@ def is_bipartite_bfs(graph: defaultdict[int, list[int]]) -> bool:
         ...
     KeyError: 0
 
-    # FIXME: This test should fails with TypeError: list indices must be integers or...
+    >>> # FIXME: This test should fails with
+    >>> # TypeError: list indices must be integers or...
     >>> is_bipartite_bfs({0: [1.0, 3.0], 1.0: [0, 2.0], 2.0: [1.0, 3.0], 3.0: [0, 2.0]})
     True
     >>> is_bipartite_bfs({"a": [1, 3], "b": [0, 2], "c": [1, 3], "d": [0, 2]})

From ae28fa7fe362c8cb0238dbb6b237d42179e8beb3 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Tue, 24 Dec 2024 16:17:22 +0300
Subject: [PATCH 011/135] Fix sphinx/build_docs warnings for
 data_structures/binary_tree/mirror_binary_tree (#12470)

---
 .../binary_tree/mirror_binary_tree.py         | 52 +++++++++++--------
 1 file changed, 29 insertions(+), 23 deletions(-)

diff --git a/data_structures/binary_tree/mirror_binary_tree.py b/data_structures/binary_tree/mirror_binary_tree.py
index 62e2f08dd4e0..f6611d66d676 100644
--- a/data_structures/binary_tree/mirror_binary_tree.py
+++ b/data_structures/binary_tree/mirror_binary_tree.py
@@ -56,6 +56,8 @@ def mirror(self) -> Node:
 def make_tree_seven() -> Node:
     r"""
     Return a binary tree with 7 nodes that looks like this:
+    ::
+
            1
          /   \
         2     3
@@ -81,13 +83,15 @@ def make_tree_seven() -> Node:
 def make_tree_nine() -> Node:
     r"""
     Return a binary tree with 9 nodes that looks like this:
-          1
-         /  \
-        2    3
-       / \    \
-      4   5    6
-     / \   \
-    7   8   9
+    ::
+
+            1
+           / \
+          2   3
+         / \   \
+        4   5   6
+       / \   \
+      7   8   9
 
     >>> tree_nine = make_tree_nine()
     >>> len(tree_nine)
@@ -117,23 +121,25 @@ def main() -> None:
     >>> tuple(tree.mirror())
     (6, 3, 1, 9, 5, 2, 8, 4, 7)
 
-    nine_tree:
-          1
-         /  \
-        2    3
-       / \    \
-      4   5    6
-     / \   \
-    7   8   9
-
-    The mirrored tree looks like this:
+    nine_tree::
+
+            1
+           / \
+          2   3
+         / \   \
+        4   5   6
+       / \   \
+      7   8   9
+
+    The mirrored tree looks like this::
+
           1
-         /  \
-        3    2
-       /    / \
-      6    5   4
-          /   / \
-         9   8   7
+         / \
+        3   2
+       /   / \
+      6   5   4
+         /   / \
+        9   8   7
     """
     trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()}
     for name, tree in trees.items():

From eb652cf3d48fbd3b51450e95640ce5aec63a066b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 28 Dec 2024 00:18:08 +0300
Subject: [PATCH 012/135] Bump astral-sh/setup-uv from 4 to 5 (#12445)

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 5.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v5)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/build.yml         | 2 +-
 .github/workflows/project_euler.yml | 4 ++--
 .github/workflows/ruff.yml          | 2 +-
 .github/workflows/sphinx.yml        | 2 +-
 4 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a6f308715cc2..62829b2b45a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v4
+      - uses: astral-sh/setup-uv@v5
         with:
           enable-cache: true
           cache-dependency-glob: uv.lock
diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml
index 84c55335451e..8d51ad8850cf 100644
--- a/.github/workflows/project_euler.yml
+++ b/.github/workflows/project_euler.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v4
+      - uses: astral-sh/setup-uv@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
@@ -25,7 +25,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v4
+      - uses: astral-sh/setup-uv@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
index 2c6f92fcf7bf..cfe127b3521f 100644
--- a/.github/workflows/ruff.yml
+++ b/.github/workflows/ruff.yml
@@ -12,5 +12,5 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v4
+      - uses: astral-sh/setup-uv@v5
       - run: uvx ruff check --output-format=github .
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index e3e2ce81a95d..d02435d98028 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -26,7 +26,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v4
+      - uses: astral-sh/setup-uv@v5
       - uses: actions/setup-python@v5
         with:
           python-version: 3.13

From 5bef6ac9296c20250db7d494bbbc9c8bf4bfccdc Mon Sep 17 00:00:00 2001
From: Scarfinos <158184182+Scarfinos@users.noreply.github.com>
Date: Fri, 27 Dec 2024 23:22:36 +0100
Subject: [PATCH 013/135] Improve coverage special_numbers (#12414)

* Improve coverage bell_numbers

* improve more function

* Update hamming_numbers.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 maths/special_numbers/bell_numbers.py    | 4 ++++
 maths/special_numbers/hamming_numbers.py | 6 +++++-
 maths/special_numbers/harshad_numbers.py | 8 ++++++++
 3 files changed, 17 insertions(+), 1 deletion(-)

diff --git a/maths/special_numbers/bell_numbers.py b/maths/special_numbers/bell_numbers.py
index 5d99334d7add..d573e7a3962d 100644
--- a/maths/special_numbers/bell_numbers.py
+++ b/maths/special_numbers/bell_numbers.py
@@ -21,6 +21,10 @@ def bell_numbers(max_set_length: int) -> list[int]:
         list: A list of Bell numbers for sets of lengths from 0 to max_set_length.
 
     Examples:
+    >>> bell_numbers(-2)
+    Traceback (most recent call last):
+        ...
+    ValueError: max_set_length must be non-negative
     >>> bell_numbers(0)
     [1]
     >>> bell_numbers(1)
diff --git a/maths/special_numbers/hamming_numbers.py b/maths/special_numbers/hamming_numbers.py
index 4575119c8a95..a473cc93883b 100644
--- a/maths/special_numbers/hamming_numbers.py
+++ b/maths/special_numbers/hamming_numbers.py
@@ -13,6 +13,10 @@ def hamming(n_element: int) -> list:
     :param n_element: The number of elements on the list
     :return: The nth element of the list
 
+    >>> hamming(-5)
+    Traceback (most recent call last):
+        ...
+    ValueError: n_element should be a positive number
     >>> hamming(5)
     [1, 2, 3, 4, 5]
     >>> hamming(10)
@@ -22,7 +26,7 @@ def hamming(n_element: int) -> list:
     """
     n_element = int(n_element)
     if n_element < 1:
-        my_error = ValueError("a should be a positive number")
+        my_error = ValueError("n_element should be a positive number")
         raise my_error
 
     hamming_list = [1]
diff --git a/maths/special_numbers/harshad_numbers.py b/maths/special_numbers/harshad_numbers.py
index 61667adfa127..417120bd840e 100644
--- a/maths/special_numbers/harshad_numbers.py
+++ b/maths/special_numbers/harshad_numbers.py
@@ -11,6 +11,8 @@ def int_to_base(number: int, base: int) -> str:
     Where 'base' ranges from 2 to 36.
 
     Examples:
+    >>> int_to_base(0, 21)
+    '0'
     >>> int_to_base(23, 2)
     '10111'
     >>> int_to_base(58, 5)
@@ -26,6 +28,10 @@ def int_to_base(number: int, base: int) -> str:
     Traceback (most recent call last):
         ...
     ValueError: 'base' must be between 2 and 36 inclusive
+    >>> int_to_base(-99, 16)
+    Traceback (most recent call last):
+        ...
+    ValueError: number must be a positive integer
     """
 
     if base < 2 or base > 36:
@@ -101,6 +107,8 @@ def harshad_numbers_in_base(limit: int, base: int) -> list[str]:
     Traceback (most recent call last):
         ...
     ValueError: 'base' must be between 2 and 36 inclusive
+    >>> harshad_numbers_in_base(-12, 6)
+    []
     """
 
     if base < 2 or base > 36:

From 8bbe8caa256882ef2ebdbb3274e6f99f804716bd Mon Sep 17 00:00:00 2001
From: Scarfinos <158184182+Scarfinos@users.noreply.github.com>
Date: Fri, 27 Dec 2024 23:40:35 +0100
Subject: [PATCH 014/135] Improve test coverage for matrix exponentiation
 (#12388)

* #9943 : Adding coverage test for basic_graphs.py

* #9943 : Adding coverage test for basic_graphs.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Solve problem of line too long

* Improving coverage for matrix_exponentiation.py

* fix more than one file

* Update matrix_exponentiation.py

* Update matrix_exponentiation.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 maths/matrix_exponentiation.py | 36 ++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

diff --git a/maths/matrix_exponentiation.py b/maths/matrix_exponentiation.py
index 7c37151c87ca..7cdac9d34674 100644
--- a/maths/matrix_exponentiation.py
+++ b/maths/matrix_exponentiation.py
@@ -39,6 +39,21 @@ def modular_exponentiation(a, b):
 
 
 def fibonacci_with_matrix_exponentiation(n, f1, f2):
+    """
+    Returns the nth number of the Fibonacci sequence that
+    starts with f1 and f2
+    Uses the matrix exponentiation
+    >>> fibonacci_with_matrix_exponentiation(1, 5, 6)
+    5
+    >>> fibonacci_with_matrix_exponentiation(2, 10, 11)
+    11
+    >>> fibonacci_with_matrix_exponentiation(13, 0, 1)
+    144
+    >>> fibonacci_with_matrix_exponentiation(10, 5, 9)
+    411
+    >>> fibonacci_with_matrix_exponentiation(9, 2, 3)
+    89
+    """
     # Trivial Cases
     if n == 1:
         return f1
@@ -50,21 +65,34 @@ def fibonacci_with_matrix_exponentiation(n, f1, f2):
 
 
 def simple_fibonacci(n, f1, f2):
+    """
+    Returns the nth number of the Fibonacci sequence that
+    starts with f1 and f2
+    Uses the definition
+    >>> simple_fibonacci(1, 5, 6)
+    5
+    >>> simple_fibonacci(2, 10, 11)
+    11
+    >>> simple_fibonacci(13, 0, 1)
+    144
+    >>> simple_fibonacci(10, 5, 9)
+    411
+    >>> simple_fibonacci(9, 2, 3)
+    89
+    """
     # Trivial Cases
     if n == 1:
         return f1
     elif n == 2:
         return f2
 
-    fn_1 = f1
-    fn_2 = f2
     n -= 2
 
     while n > 0:
-        fn_1, fn_2 = fn_1 + fn_2, fn_1
+        f2, f1 = f1 + f2, f2
         n -= 1
 
-    return fn_1
+    return f2
 
 
 def matrix_exponentiation_time():

From 76471819bd5b9df6fe5fde4c763396412ce45edc Mon Sep 17 00:00:00 2001
From: Scarfinos <158184182+Scarfinos@users.noreply.github.com>
Date: Fri, 27 Dec 2024 23:52:40 +0100
Subject: [PATCH 015/135] Improve test coverage for armstrong numbers (#12327)

---
 maths/special_numbers/armstrong_numbers.py | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/maths/special_numbers/armstrong_numbers.py b/maths/special_numbers/armstrong_numbers.py
index b2b4010a8f5b..a3cb69b814de 100644
--- a/maths/special_numbers/armstrong_numbers.py
+++ b/maths/special_numbers/armstrong_numbers.py
@@ -43,9 +43,9 @@ def armstrong_number(n: int) -> bool:
 def pluperfect_number(n: int) -> bool:
     """Return True if n is a pluperfect number or False if it is not
 
-    >>> all(armstrong_number(n) for n in PASSING)
+    >>> all(pluperfect_number(n) for n in PASSING)
     True
-    >>> any(armstrong_number(n) for n in FAILING)
+    >>> any(pluperfect_number(n) for n in FAILING)
     False
     """
     if not isinstance(n, int) or n < 1:
@@ -70,9 +70,9 @@ def pluperfect_number(n: int) -> bool:
 def narcissistic_number(n: int) -> bool:
     """Return True if n is a narcissistic number or False if it is not.
 
-    >>> all(armstrong_number(n) for n in PASSING)
+    >>> all(narcissistic_number(n) for n in PASSING)
     True
-    >>> any(armstrong_number(n) for n in FAILING)
+    >>> any(narcissistic_number(n) for n in FAILING)
     False
     """
     if not isinstance(n, int) or n < 1:

From 2ae9534fc68b1901d8056331aa2a4dbedc9d947e Mon Sep 17 00:00:00 2001
From: Anamaria Miranda <miranda_anam@hotmail.com>
Date: Sat, 28 Dec 2024 00:03:13 +0100
Subject: [PATCH 016/135] Added test to linear regression (#12353)

---
 machine_learning/linear_regression.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py
index 839a5366d1cc..1d11e5a9cc2b 100644
--- a/machine_learning/linear_regression.py
+++ b/machine_learning/linear_regression.py
@@ -41,6 +41,14 @@ def run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta):
     :param theta    : Feature vector (weight's for our model)
     ;param return    : Updated Feature's, using
                        curr_features - alpha_ * gradient(w.r.t. feature)
+    >>> import numpy as np
+    >>> data_x = np.array([[1, 2], [3, 4]])
+    >>> data_y = np.array([5, 6])
+    >>> len_data = len(data_x)
+    >>> alpha = 0.01
+    >>> theta = np.array([0.1, 0.2])
+    >>> run_steep_gradient_descent(data_x, data_y, len_data, alpha, theta)
+    array([0.196, 0.343])
     """
     n = len_data
 
@@ -58,6 +66,12 @@ def sum_of_square_error(data_x, data_y, len_data, theta):
     :param len_data  : len of the dataset
     :param theta     : contains the feature vector
     :return          : sum of square error computed from given feature's
+
+    Example:
+    >>> vc_x = np.array([[1.1], [2.1], [3.1]])
+    >>> vc_y = np.array([1.2, 2.2, 3.2])
+    >>> round(sum_of_square_error(vc_x, vc_y, 3, np.array([1])),3)
+    np.float64(0.005)
     """
     prod = np.dot(theta, data_x.transpose())
     prod -= data_y.transpose()
@@ -93,6 +107,11 @@ def mean_absolute_error(predicted_y, original_y):
     :param predicted_y   : contains the output of prediction (result vector)
     :param original_y    : contains values of expected outcome
     :return          : mean absolute error computed from given feature's
+
+    >>> predicted_y = [3, -0.5, 2, 7]
+    >>> original_y = [2.5, 0.0, 2, 8]
+    >>> mean_absolute_error(predicted_y, original_y)
+    0.5
     """
     total = sum(abs(y - predicted_y[i]) for i, y in enumerate(original_y))
     return total / len(original_y)
@@ -114,4 +133,7 @@ def main():
 
 
 if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
     main()

From 1652d05e9ee25d54eea5576976d537975dcad9bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julia=20Arag=C3=A3o?=
 <101305675+juliaaragao@users.noreply.github.com>
Date: Sat, 28 Dec 2024 00:26:29 +0100
Subject: [PATCH 017/135] adding test to electronics/electric_power.py (#12387)

* test electric_power

* Update electric_power.py

* Update electric_power.py

* Update electric_power.py

* Update electric_power.py

---------

Co-authored-by: Julia <julia.de-jesus-aragao@imt-atlantique.net>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 electronics/electric_power.py | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/electronics/electric_power.py b/electronics/electric_power.py
index 8b92e320ace3..8e3454e39c3f 100644
--- a/electronics/electric_power.py
+++ b/electronics/electric_power.py
@@ -23,20 +23,22 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
     >>> electric_power(voltage=2, current=4, power=2)
     Traceback (most recent call last):
         ...
-    ValueError: Only one argument must be 0
+    ValueError: Exactly one argument must be 0
     >>> electric_power(voltage=0, current=0, power=2)
     Traceback (most recent call last):
         ...
-    ValueError: Only one argument must be 0
+    ValueError: Exactly one argument must be 0
     >>> electric_power(voltage=0, current=2, power=-4)
     Traceback (most recent call last):
         ...
     ValueError: Power cannot be negative in any electrical/electronics system
     >>> electric_power(voltage=2.2, current=2.2, power=0)
     Result(name='power', value=4.84)
+    >>> electric_power(current=0, power=6, voltage=2)
+    Result(name='current', value=3.0)
     """
     if (voltage, current, power).count(0) != 1:
-        raise ValueError("Only one argument must be 0")
+        raise ValueError("Exactly one argument must be 0")
     elif power < 0:
         raise ValueError(
             "Power cannot be negative in any electrical/electronics system"
@@ -48,7 +50,7 @@ def electric_power(voltage: float, current: float, power: float) -> tuple:
     elif power == 0:
         return Result("power", float(round(abs(voltage * current), 2)))
     else:
-        raise ValueError("Exactly one argument must be 0")
+        raise AssertionError
 
 
 if __name__ == "__main__":

From 929b7dc057cd56f90b260cd665fb67886bcadeea Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sat, 28 Dec 2024 11:43:25 +0300
Subject: [PATCH 018/135] Fix Gaussian elimination pivoting (#11393)

* updating DIRECTORY.md

* Fix Gaussian elimination pivoting

* Fix review issues

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .../src/gaussian_elimination_pivoting.py      | 39 ++++++++-----------
 1 file changed, 16 insertions(+), 23 deletions(-)

diff --git a/linear_algebra/src/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting.py
index ecaacce19a31..efc1ddd64a2e 100644
--- a/linear_algebra/src/gaussian_elimination_pivoting.py
+++ b/linear_algebra/src/gaussian_elimination_pivoting.py
@@ -22,40 +22,33 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray:
     >>> solution = solve_linear_system(np.column_stack((A, B)))
     >>> np.allclose(solution, np.array([2., 3., -1.]))
     True
-    >>> solve_linear_system(np.array([[0, 0], [0, 0]],  dtype=float))
-    array([nan, nan])
+    >>> solve_linear_system(np.array([[0, 0, 0]], dtype=float))
+    Traceback (most recent call last):
+        ...
+    ValueError: Matrix is not square
+    >>> solve_linear_system(np.array([[0, 0, 0], [0, 0, 0]], dtype=float))
+    Traceback (most recent call last):
+        ...
+    ValueError: Matrix is singular
     """
     ab = np.copy(matrix)
     num_of_rows = ab.shape[0]
     num_of_columns = ab.shape[1] - 1
     x_lst: list[float] = []
 
-    # Lead element search
-    for column_num in range(num_of_rows):
-        for i in range(column_num, num_of_columns):
-            if abs(ab[i][column_num]) > abs(ab[column_num][column_num]):
-                ab[[column_num, i]] = ab[[i, column_num]]
-                if ab[column_num, column_num] == 0.0:
-                    raise ValueError("Matrix is not correct")
-            else:
-                pass
-        if column_num != 0:
-            for i in range(column_num, num_of_rows):
-                ab[i, :] -= (
-                    ab[i, column_num - 1]
-                    / ab[column_num - 1, column_num - 1]
-                    * ab[column_num - 1, :]
-                )
+    if num_of_rows != num_of_columns:
+        raise ValueError("Matrix is not square")
 
-    # Upper triangular matrix
     for column_num in range(num_of_rows):
+        # Lead element search
         for i in range(column_num, num_of_columns):
             if abs(ab[i][column_num]) > abs(ab[column_num][column_num]):
                 ab[[column_num, i]] = ab[[i, column_num]]
-                if ab[column_num, column_num] == 0.0:
-                    raise ValueError("Matrix is not correct")
-            else:
-                pass
+
+        # Upper triangular matrix
+        if abs(ab[column_num, column_num]) < 1e-8:
+            raise ValueError("Matrix is singular")
+
         if column_num != 0:
             for i in range(column_num, num_of_rows):
                 ab[i, :] -= (

From b5c8fbf2e8254b53056b741aacce3842736ba177 Mon Sep 17 00:00:00 2001
From: Joy Khandelwal <116290658+joy-programs@users.noreply.github.com>
Date: Sat, 28 Dec 2024 14:21:28 +0530
Subject: [PATCH 019/135] Add additional doctests, fix grammatical errors for
 maths/perfect_number.py (#12477)

* Add additional doctests for the perfect number algorithm and fix grammatical errors. Contributes to #9943

* Added newline at End of file

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
---
 maths/perfect_number.py | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/maths/perfect_number.py b/maths/perfect_number.py
index df6b6e3d91d8..52c816cc7895 100644
--- a/maths/perfect_number.py
+++ b/maths/perfect_number.py
@@ -46,17 +46,27 @@ def perfect(number: int) -> bool:
     False
     >>> perfect(-1)
     False
+    >>> perfect(33550336)  # Large perfect number
+    True
+    >>> perfect(33550337)  # Just above a large perfect number
+    False
+    >>> perfect(1)  # Edge case: 1 is not a perfect number
+    False
+    >>> perfect("123")  # String representation of a number
+    Traceback (most recent call last):
+    ...
+    ValueError: number must be an integer
     >>> perfect(12.34)
     Traceback (most recent call last):
       ...
-    ValueError: number must an integer
+    ValueError: number must be an integer
     >>> perfect("Hello")
     Traceback (most recent call last):
       ...
-    ValueError: number must an integer
+    ValueError: number must be an integer
     """
     if not isinstance(number, int):
-        raise ValueError("number must an integer")
+        raise ValueError("number must be an integer")
     if number <= 0:
         return False
     return sum(i for i in range(1, number // 2 + 1) if number % i == 0) == number
@@ -70,8 +80,7 @@ def perfect(number: int) -> bool:
     try:
         number = int(input("Enter a positive integer: ").strip())
     except ValueError:
-        msg = "number must an integer"
-        print(msg)
+        msg = "number must be an integer"
         raise ValueError(msg)
 
     print(f"{number} is {'' if perfect(number) else 'not '}a Perfect Number.")

From d496d5611fe266b7123b4a1f3efb4bcb7c2f38f2 Mon Sep 17 00:00:00 2001
From: Shi Entong <144505619+setbit123@users.noreply.github.com>
Date: Sat, 28 Dec 2024 17:22:07 +0800
Subject: [PATCH 020/135] Remove inaccessible URL in computer_vision/README.md
 (#12383)

Remove inaccessible URL.
---
 computer_vision/README.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/computer_vision/README.md b/computer_vision/README.md
index 1657128fd25e..61462567b662 100644
--- a/computer_vision/README.md
+++ b/computer_vision/README.md
@@ -8,4 +8,3 @@ Image processing and computer vision are a little different from each other. Ima
 While computer vision comes from modelling image processing using the techniques of machine learning, computer vision applies machine learning to recognize patterns for interpretation of images (much like the process of visual reasoning of human vision).
 
 * <https://en.wikipedia.org/wiki/Computer_vision>
-* <https://www.datarobot.com/blog/introduction-to-computer-vision-what-it-is-and-how-it-works/>

From 1909f2272f11ebe7626d2dee78c11a91134e39e7 Mon Sep 17 00:00:00 2001
From: jperezr <122382210+MRJPEREZR@users.noreply.github.com>
Date: Sat, 28 Dec 2024 11:03:24 +0100
Subject: [PATCH 021/135] adding doctests to maths/trapezoidal_rule.py (#12193)

* adding doctests to trapezoidal_rule.py

* adding algorithm delta-star transformation

* updating DIRECTORY.md

* delete file star_delta_transform.py

* updating DIRECTORY.md

* modified:   ../DIRECTORY.md

---------

Co-authored-by: MRJPEREZR <MRJPEREZR@users.noreply.github.com>
---
 maths/trapezoidal_rule.py | 48 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 45 insertions(+), 3 deletions(-)

diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py
index 9a4ddc8af66b..0186629ee378 100644
--- a/maths/trapezoidal_rule.py
+++ b/maths/trapezoidal_rule.py
@@ -5,13 +5,25 @@
 
 method 1:
 "extended trapezoidal rule"
+int(f) = dx/2 * (f1 + 2f2 + ... + fn)
 
 """
 
 
 def method_1(boundary, steps):
-    # "extended trapezoidal rule"
-    # int(f) = dx/2 * (f1 + 2f2 + ... + fn)
+    """
+    Apply the extended trapezoidal rule to approximate the integral of function f(x)
+    over the interval defined by 'boundary' with the number of 'steps'.
+
+    Args:
+        boundary (list of floats): A list containing the start and end values [a, b].
+        steps (int): The number of steps or subintervals.
+    Returns:
+        float: Approximation of the integral of f(x) over [a, b].
+    Examples:
+    >>> method_1([0, 1], 10)
+    0.3349999999999999
+    """
     h = (boundary[1] - boundary[0]) / steps
     a = boundary[0]
     b = boundary[1]
@@ -26,13 +38,40 @@ def method_1(boundary, steps):
 
 
 def make_points(a, b, h):
+    """
+    Generates points between 'a' and 'b' with step size 'h', excluding the end points.
+    Args:
+        a (float): Start value
+        b (float): End value
+        h (float): Step size
+    Examples:
+    >>> list(make_points(0, 10, 2.5))
+    [2.5, 5.0, 7.5]
+
+    >>> list(make_points(0, 10, 2))
+    [2, 4, 6, 8]
+
+    >>> list(make_points(1, 21, 5))
+    [6, 11, 16]
+
+    >>> list(make_points(1, 5, 2))
+    [3]
+
+    >>> list(make_points(1, 4, 3))
+    []
+    """
     x = a + h
-    while x < (b - h):
+    while x <= (b - h):
         yield x
         x = x + h
 
 
 def f(x):  # enter your function here
+    """
+    Example:
+    >>> f(2)
+    4
+    """
     y = (x - 0) * (x - 0)
     return y
 
@@ -47,4 +86,7 @@ def main():
 
 
 if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
     main()

From 2b58ab040295fbbf2e463ba8cd77ad935d942968 Mon Sep 17 00:00:00 2001
From: Andrwaa <165920381+Andrwaa@users.noreply.github.com>
Date: Sat, 28 Dec 2024 12:17:48 +0100
Subject: [PATCH 022/135] compare-method added to Vector class in lib.py
 (#12448)

* compare-method added to Vector class in lib.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Updated lib.py with suggestions

* Updated lib.py with suggestions

* Updated lib.py with __eq__ method

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 linear_algebra/src/lib.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/linear_algebra/src/lib.py b/linear_algebra/src/lib.py
index 5af6c62e3ad4..0d6a348475cd 100644
--- a/linear_algebra/src/lib.py
+++ b/linear_algebra/src/lib.py
@@ -46,7 +46,6 @@ class Vector:
         change_component(pos: int, value: float): changes specified component
         euclidean_length(): returns the euclidean length of the vector
         angle(other: Vector, deg: bool): returns the angle between two vectors
-        TODO: compare-operator
     """
 
     def __init__(self, components: Collection[float] | None = None) -> None:
@@ -96,6 +95,16 @@ def __sub__(self, other: Vector) -> Vector:
         else:  # error case
             raise Exception("must have the same size")
 
+    def __eq__(self, other: object) -> bool:
+        """
+        performs the comparison between two vectors
+        """
+        if not isinstance(other, Vector):
+            return NotImplemented
+        if len(self) != len(other):
+            return False
+        return all(self.component(i) == other.component(i) for i in range(len(self)))
+
     @overload
     def __mul__(self, other: float) -> Vector: ...
 

From 2d68bb50e5f12532b5a0d616305c4f805d2b8ff9 Mon Sep 17 00:00:00 2001
From: KICH Yassine <kishyassin@gmail.com>
Date: Sun, 29 Dec 2024 12:56:36 +0100
Subject: [PATCH 023/135] Fix split function to handle trailing delimiters
 correctly (#12423)

* Fix split function to handle trailing delimiters correctly

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update split.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 strings/split.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/strings/split.py b/strings/split.py
index b62b86d2401f..ed194ec69c2f 100644
--- a/strings/split.py
+++ b/strings/split.py
@@ -14,6 +14,9 @@ def split(string: str, separator: str = " ") -> list:
 
     >>> split("12:43:39",separator = ":")
     ['12', '43', '39']
+
+    >>> split(";abbb;;c;", separator=';')
+    ['', 'abbb', '', 'c', '']
     """
 
     split_words = []
@@ -23,7 +26,7 @@ def split(string: str, separator: str = " ") -> list:
         if char == separator:
             split_words.append(string[last_index:index])
             last_index = index + 1
-        elif index + 1 == len(string):
+        if index + 1 == len(string):
             split_words.append(string[last_index : index + 1])
     return split_words
 

From 972a5c1e432e0a3fa9e990422318269219192a53 Mon Sep 17 00:00:00 2001
From: RajdeepBakolia2004
 <144157867+RajdeepBakolia2004@users.noreply.github.com>
Date: Sun, 29 Dec 2024 19:05:33 +0530
Subject: [PATCH 024/135] fixed the issue in strings/join.py (#12434)

* fixed the issue in strings/join.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update join.py

* Update join.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 strings/join.py | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/strings/join.py b/strings/join.py
index 5c02f65a20ce..cdcc3a1377f4 100644
--- a/strings/join.py
+++ b/strings/join.py
@@ -24,6 +24,8 @@ def join(separator: str, separated: list[str]) -> str:
     'a'
     >>> join(" ", ["You", "are", "amazing!"])
     'You are amazing!'
+    >>> join(",", ["", "", ""])
+    ',,'
 
     This example should raise an
     exception for non-string elements:
@@ -37,15 +39,33 @@ def join(separator: str, separated: list[str]) -> str:
     'apple-banana-cherry'
     """
 
-    joined = ""
+    # Check that all elements are strings
     for word_or_phrase in separated:
+        # If the element is not a string, raise an exception
         if not isinstance(word_or_phrase, str):
             raise Exception("join() accepts only strings")
+
+    joined: str = ""
+    """
+    The last element of the list is not followed by the separator.
+    So, we need to iterate through the list and join each element
+    with the separator except the last element.
+    """
+    last_index: int = len(separated) - 1
+    """
+    Iterate through the list and join each element with the separator.
+    Except the last element, all other elements are followed by the separator.
+    """
+    for word_or_phrase in separated[:last_index]:
+        # join the element with the separator.
         joined += word_or_phrase + separator
 
-    # Remove the trailing separator
-    # by stripping it from the result
-    return joined.strip(separator)
+    # If the list is not empty, join the last element.
+    if separated != []:
+        joined += separated[last_index]
+
+    # Return the joined string.
+    return joined
 
 
 if __name__ == "__main__":

From d9092d88dd8b47323d14f87025195e0c76fe7889 Mon Sep 17 00:00:00 2001
From: Sankalpa Sarkar <137193167+sanks011@users.noreply.github.com>
Date: Sun, 29 Dec 2024 19:23:31 +0530
Subject: [PATCH 025/135] fixes requirements error (#12438)

* fixes join.py action

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixes split.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fixed two requirements

* Custom Implementation of join.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* updated join.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update split.py

* Update join.py

* Update join.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 requirements.txt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index 4cc83f44987d..b104505e01bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 beautifulsoup4
-fake_useragent
+fake-useragent
 imageio
 keras
 lxml
@@ -11,7 +11,7 @@ pillow
 requests
 rich
 scikit-learn
-sphinx_pyproject
+sphinx-pyproject
 statsmodels
 sympy
 tweepy

From bfc804a41c6fb7f3c2e371b15d50ba4830bab3a7 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sun, 29 Dec 2024 18:41:28 +0300
Subject: [PATCH 026/135] Fix sphinx/build_docs warnings for
 physics/newtons_second_law_of_motion (#12480)

* Fix sphinx/build_docs warnings for physics/newtons_second_law_of_motion

* Fix

* Fix

* Fix review issue
---
 physics/newtons_second_law_of_motion.py | 83 ++++++++++++++-----------
 1 file changed, 47 insertions(+), 36 deletions(-)

diff --git a/physics/newtons_second_law_of_motion.py b/physics/newtons_second_law_of_motion.py
index 53fab6ce78b9..4149e2494f31 100644
--- a/physics/newtons_second_law_of_motion.py
+++ b/physics/newtons_second_law_of_motion.py
@@ -1,18 +1,22 @@
-"""
-Description :
-Newton's second law of motion pertains to the behavior of objects for which
-all existing forces are not balanced.
-The second law states that the acceleration of an object is dependent upon two variables
-- the net force acting upon the object and the mass of the object.
-The acceleration of an object depends directly
-upon the net force acting upon the object,
-and inversely upon the mass of the object.
-As the force acting upon an object is increased,
-the acceleration of the object is increased.
-As the mass of an object is increased, the acceleration of the object is decreased.
+r"""
+Description:
+    Newton's second law of motion pertains to the behavior of objects for which
+    all existing forces are not balanced.
+    The second law states that the acceleration of an object is dependent upon
+    two variables - the net force acting upon the object and the mass of the object.
+    The acceleration of an object depends directly
+    upon the net force acting upon the object,
+    and inversely upon the mass of the object.
+    As the force acting upon an object is increased,
+    the acceleration of the object is increased.
+    As the mass of an object is increased, the acceleration of the object is decreased.
+
 Source: https://www.physicsclassroom.com/class/newtlaws/Lesson-3/Newton-s-Second-Law
-Formulation: Fnet = m • a
-Diagrammatic Explanation:
+
+Formulation: F_net = m • a
+
+Diagrammatic Explanation::
+
               Forces are unbalanced
                         |
                         |
@@ -26,35 +30,42 @@
                     /        \
                    /          \
                   /            \
-        __________________   ____ ________________
-        |The acceleration |  |The acceleration   |
-        |depends directly |  |depends inversely  |
-        |on the net Force |  |upon the object's  |
-        |_________________|  |mass_______________|
-Units:
-1 Newton = 1 kg X meters / (seconds^2)
+    __________________      ____________________
+   | The acceleration |    | The acceleration   |
+   | depends directly |    | depends inversely  |
+   | on the net force |    | upon the object's  |
+   |                  |    | mass               |
+   |__________________|    |____________________|
+
+Units: 1 Newton = 1 kg • meters/seconds^2
+
 How to use?
-Inputs:
-    ___________________________________________________
-   |Name         | Units                   | Type      |
-   |-------------|-------------------------|-----------|
-   |mass         | (in kgs)                | float     |
-   |-------------|-------------------------|-----------|
-   |acceleration | (in meters/(seconds^2)) | float     |
-   |_____________|_________________________|___________|
-
-Output:
-    ___________________________________________________
-   |Name         | Units                   | Type      |
-   |-------------|-------------------------|-----------|
-   |force        | (in Newtons)            | float     |
-   |_____________|_________________________|___________|
+
+Inputs::
+
+    ______________ _____________________ ___________
+   | Name         | Units               | Type      |
+   |--------------|---------------------|-----------|
+   | mass         | in kgs              | float     |
+   |--------------|---------------------|-----------|
+   | acceleration | in meters/seconds^2 | float     |
+   |______________|_____________________|___________|
+
+Output::
+
+    ______________ _______________________ ___________
+   | Name         | Units                 | Type      |
+   |--------------|-----------------------|-----------|
+   | force        | in Newtons            | float     |
+   |______________|_______________________|___________|
 
 """
 
 
 def newtons_second_law_of_motion(mass: float, acceleration: float) -> float:
     """
+    Calculates force from `mass` and `acceleration`
+
     >>> newtons_second_law_of_motion(10, 10)
     100
     >>> newtons_second_law_of_motion(2.0, 1)

From c93288389d220297f972137293f4565c62131516 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Sun, 29 Dec 2024 18:16:45 +0100
Subject: [PATCH 027/135] [pre-commit.ci] pre-commit autoupdate (#12466)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.3 → v0.8.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.3...v0.8.4)
- [github.com/pre-commit/mirrors-mypy: v1.13.0 → v1.14.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.13.0...v1.14.0)

* Update convert_number_to_words.py

* Update convert_number_to_words.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml                | 4 ++--
 conversions/convert_number_to_words.py | 8 ++++----
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0c8108ac55be..71ac72c29b5f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.3
+    rev: v0.8.4
     hooks:
       - id: ruff
       - id: ruff-format
@@ -47,7 +47,7 @@ repos:
       - id: validate-pyproject
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.13.0
+    rev: v1.14.0
     hooks:
       - id: mypy
         args:
diff --git a/conversions/convert_number_to_words.py b/conversions/convert_number_to_words.py
index dbab44c72e1f..6aa43738b9fe 100644
--- a/conversions/convert_number_to_words.py
+++ b/conversions/convert_number_to_words.py
@@ -1,5 +1,5 @@
 from enum import Enum
-from typing import ClassVar, Literal
+from typing import Literal
 
 
 class NumberingSystem(Enum):
@@ -54,7 +54,7 @@ def max_value(cls, system: str) -> int:
 
 
 class NumberWords(Enum):
-    ONES: ClassVar[dict[int, str]] = {
+    ONES = {  # noqa: RUF012
         0: "",
         1: "one",
         2: "two",
@@ -67,7 +67,7 @@ class NumberWords(Enum):
         9: "nine",
     }
 
-    TEENS: ClassVar[dict[int, str]] = {
+    TEENS = {  # noqa: RUF012
         0: "ten",
         1: "eleven",
         2: "twelve",
@@ -80,7 +80,7 @@ class NumberWords(Enum):
         9: "nineteen",
     }
 
-    TENS: ClassVar[dict[int, str]] = {
+    TENS = {  # noqa: RUF012
         2: "twenty",
         3: "thirty",
         4: "forty",

From bfb0447efb73dd049c6a56331cea36cb1345686b Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sun, 29 Dec 2024 20:29:48 +0300
Subject: [PATCH 028/135] Fix sphinx/build_docs warnings for
 maths/zellers_congruence (#12481)

* Fix sphinx/build_docs warnings for maths/zellers_congruence

* Fix
---
 maths/zellers_congruence.py | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/maths/zellers_congruence.py b/maths/zellers_congruence.py
index 483fb000f86b..b958ed3b8659 100644
--- a/maths/zellers_congruence.py
+++ b/maths/zellers_congruence.py
@@ -4,13 +4,14 @@
 
 def zeller(date_input: str) -> str:
     """
-    Zellers Congruence Algorithm
-    Find the day of the week for nearly any Gregorian or Julian calendar date
+    | Zellers Congruence Algorithm
+    | Find the day of the week for nearly any Gregorian or Julian calendar date
 
     >>> zeller('01-31-2010')
     'Your date 01-31-2010, is a Sunday!'
 
-    Validate out of range month
+    Validate out of range month:
+
     >>> zeller('13-31-2010')
     Traceback (most recent call last):
         ...
@@ -21,6 +22,7 @@ def zeller(date_input: str) -> str:
     ValueError: invalid literal for int() with base 10: '.2'
 
     Validate out of range date:
+
     >>> zeller('01-33-2010')
     Traceback (most recent call last):
         ...
@@ -31,30 +33,35 @@ def zeller(date_input: str) -> str:
     ValueError: invalid literal for int() with base 10: '.4'
 
     Validate second separator:
+
     >>> zeller('01-31*2010')
     Traceback (most recent call last):
         ...
     ValueError: Date separator must be '-' or '/'
 
     Validate first separator:
+
     >>> zeller('01^31-2010')
     Traceback (most recent call last):
         ...
     ValueError: Date separator must be '-' or '/'
 
     Validate out of range year:
+
     >>> zeller('01-31-8999')
     Traceback (most recent call last):
         ...
     ValueError: Year out of range. There has to be some sort of limit...right?
 
     Test null input:
+
     >>> zeller()
     Traceback (most recent call last):
         ...
     TypeError: zeller() missing 1 required positional argument: 'date_input'
 
-    Test length of date_input:
+    Test length of `date_input`:
+
     >>> zeller('')
     Traceback (most recent call last):
         ...

From ce036db2131626b86b94ab87854c82a9bc6c3d0e Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sun, 29 Dec 2024 23:01:15 +0300
Subject: [PATCH 029/135] Fix sphinx/build_docs warnings for
 physics/speeds_of_gas_molecules (#12471)

* Fix sphinx/build_docs warnings for physics/speeds_of_gas_molecules

* Fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Fix review issue

* Fix

* Fix

* Fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 physics/speeds_of_gas_molecules.py | 36 ++++++++++++++++--------------
 1 file changed, 19 insertions(+), 17 deletions(-)

diff --git a/physics/speeds_of_gas_molecules.py b/physics/speeds_of_gas_molecules.py
index a50d1c0f6d76..42f90a9fd6f3 100644
--- a/physics/speeds_of_gas_molecules.py
+++ b/physics/speeds_of_gas_molecules.py
@@ -4,43 +4,43 @@
 distribution is a probability distribution that describes the distribution of
 speeds of particles in an ideal gas.
 
-The distribution is given by the following equation:
+The distribution is given by the following equation::
 
         -------------------------------------------------
         | f(v) = (M/2πRT)^(3/2) * 4πv^2 * e^(-Mv^2/2RT) |
         -------------------------------------------------
 
 where:
-    f(v) is the fraction of molecules with a speed v
-    M is the molar mass of the gas in kg/mol
-    R is the gas constant
-    T is the absolute temperature
+    * ``f(v)`` is the fraction of molecules with a speed ``v``
+    * ``M`` is the molar mass of the gas in kg/mol
+    * ``R`` is the gas constant
+    * ``T`` is the absolute temperature
 
 More information about the Maxwell-Boltzmann distribution can be found here:
 https://en.wikipedia.org/wiki/Maxwell%E2%80%93Boltzmann_distribution
 
 The average speed can be calculated by integrating the Maxwell-Boltzmann distribution
-from 0 to infinity and dividing by the total number of molecules. The result is:
+from 0 to infinity and dividing by the total number of molecules. The result is::
 
-        ---------------------
-        | vavg = √(8RT/πM)  |
-        ---------------------
+        ----------------------
+        | v_avg = √(8RT/πM)  |
+        ----------------------
 
 The most probable speed is the speed at which the Maxwell-Boltzmann distribution
 is at its maximum. This can be found by differentiating the Maxwell-Boltzmann
-distribution with respect to v and setting the result equal to zero. The result is:
+distribution with respect to ``v`` and setting the result equal to zero. The result is::
 
-        ---------------------
-        | vmp = √(2RT/M)    |
-        ---------------------
+        ----------------------
+        | v_mp = √(2RT/M)    |
+        ----------------------
 
 The root-mean-square speed is another measure of the average speed
 of the molecules in a gas. It is calculated by taking the square root
-of the average of the squares of the speeds of the molecules. The result is:
+of the average of the squares of the speeds of the molecules. The result is::
 
-        ---------------------
-        | vrms = √(3RT/M)   |
-        ---------------------
+        ----------------------
+        | v_rms = √(3RT/M)   |
+        ----------------------
 
 Here we have defined functions to calculate the average and
 most probable speeds of molecules in a gas given the
@@ -57,6 +57,7 @@ def avg_speed_of_molecule(temperature: float, molar_mass: float) -> float:
     and returns the average speed of a molecule in the gas (in m/s).
 
     Examples:
+
     >>> avg_speed_of_molecule(273, 0.028) # nitrogen at 273 K
     454.3488755020387
     >>> avg_speed_of_molecule(300, 0.032) # oxygen at 300 K
@@ -84,6 +85,7 @@ def mps_speed_of_molecule(temperature: float, molar_mass: float) -> float:
     and returns the most probable speed of a molecule in the gas (in m/s).
 
     Examples:
+
     >>> mps_speed_of_molecule(273, 0.028) # nitrogen at 273 K
     402.65620701908966
     >>> mps_speed_of_molecule(300, 0.032) # oxygen at 300 K

From 3622e940c9db74ebac06a5b12f83fd638d7c5511 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sun, 29 Dec 2024 23:31:53 +0300
Subject: [PATCH 030/135] Fix sphinx/build_docs warnings for other (#12482)

* Fix sphinx/build_docs warnings for other

* Fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 other/bankers_algorithm.py              | 16 +++--
 other/davis_putnam_logemann_loveland.py | 94 ++++++++++++++-----------
 other/scoring_algorithm.py              | 30 ++++----
 3 files changed, 77 insertions(+), 63 deletions(-)

diff --git a/other/bankers_algorithm.py b/other/bankers_algorithm.py
index d4254f479a4f..b1da851fc0f3 100644
--- a/other/bankers_algorithm.py
+++ b/other/bankers_algorithm.py
@@ -10,9 +10,10 @@
 predetermined maximum possible amounts of all resources, and then makes a "s-state"
 check to test for possible deadlock conditions for all other pending activities,
 before deciding whether allocation should be allowed to continue.
-[Source] Wikipedia
-[Credit] Rosetta Code C implementation helped very much.
- (https://rosettacode.org/wiki/Banker%27s_algorithm)
+
+| [Source] Wikipedia
+| [Credit] Rosetta Code C implementation helped very much.
+| (https://rosettacode.org/wiki/Banker%27s_algorithm)
 """
 
 from __future__ import annotations
@@ -75,7 +76,7 @@ def __available_resources(self) -> list[int]:
     def __need(self) -> list[list[int]]:
         """
         Implement safety checker that calculates the needs by ensuring that
-        max_claim[i][j] - alloc_table[i][j] <= avail[j]
+        ``max_claim[i][j] - alloc_table[i][j] <= avail[j]``
         """
         return [
             list(np.array(self.__maximum_claim_table[i]) - np.array(allocated_resource))
@@ -86,7 +87,9 @@ def __need_index_manager(self) -> dict[int, list[int]]:
         """
         This function builds an index control dictionary to track original ids/indices
         of processes when altered during execution of method "main"
-            Return: {0: [a: int, b: int], 1: [c: int, d: int]}
+
+            :Return: {0: [a: int, b: int], 1: [c: int, d: int]}
+
         >>> index_control = BankersAlgorithm(
         ...     test_claim_vector, test_allocated_res_table, test_maximum_claim_table
         ... )._BankersAlgorithm__need_index_manager()
@@ -100,7 +103,8 @@ def __need_index_manager(self) -> dict[int, list[int]]:
     def main(self, **kwargs) -> None:
         """
         Utilize various methods in this class to simulate the Banker's algorithm
-        Return: None
+            :Return: None
+
         >>> BankersAlgorithm(test_claim_vector, test_allocated_res_table,
         ...    test_maximum_claim_table).main(describe=True)
                  Allocated Resource Table
diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py
index 0f3100b1bc2e..e95bf371a817 100644
--- a/other/davis_putnam_logemann_loveland.py
+++ b/other/davis_putnam_logemann_loveland.py
@@ -17,13 +17,15 @@
 
 class Clause:
     """
-    A clause represented in Conjunctive Normal Form.
-    A clause is a set of literals, either complemented or otherwise.
+    | A clause represented in Conjunctive Normal Form.
+    | A clause is a set of literals, either complemented or otherwise.
+
     For example:
-        {A1, A2, A3'} is the clause (A1 v A2 v A3')
-        {A5', A2', A1} is the clause (A5' v A2' v A1)
+        * {A1, A2, A3'} is the clause (A1 v A2 v A3')
+        * {A5', A2', A1} is the clause (A5' v A2' v A1)
 
     Create model
+
     >>> clause = Clause(["A1", "A2'", "A3"])
     >>> clause.evaluate({"A1": True})
     True
@@ -39,6 +41,7 @@ def __init__(self, literals: list[str]) -> None:
     def __str__(self) -> str:
         """
         To print a clause as in Conjunctive Normal Form.
+
         >>> str(Clause(["A1", "A2'", "A3"]))
         "{A1 , A2' , A3}"
         """
@@ -47,6 +50,7 @@ def __str__(self) -> str:
     def __len__(self) -> int:
         """
         To print a clause as in Conjunctive Normal Form.
+
         >>> len(Clause([]))
         0
         >>> len(Clause(["A1", "A2'", "A3"]))
@@ -72,11 +76,13 @@ def assign(self, model: dict[str, bool | None]) -> None:
     def evaluate(self, model: dict[str, bool | None]) -> bool | None:
         """
         Evaluates the clause with the assignments in model.
+
         This has the following steps:
-        1. Return True if both a literal and its complement exist in the clause.
-        2. Return True if a single literal has the assignment True.
-        3. Return None(unable to complete evaluation) if a literal has no assignment.
-        4. Compute disjunction of all values assigned in clause.
+          1. Return ``True`` if both a literal and its complement exist in the clause.
+          2. Return ``True`` if a single literal has the assignment ``True``.
+          3. Return ``None`` (unable to complete evaluation)
+             if a literal has no assignment.
+          4. Compute disjunction of all values assigned in clause.
         """
         for literal in self.literals:
             symbol = literal.rstrip("'") if literal.endswith("'") else literal + "'"
@@ -92,10 +98,10 @@ def evaluate(self, model: dict[str, bool | None]) -> bool | None:
 
 class Formula:
     """
-    A formula represented in Conjunctive Normal Form.
-    A formula is a set of clauses.
-    For example,
-        {{A1, A2, A3'}, {A5', A2', A1}} is ((A1 v A2 v A3') and (A5' v A2' v A1))
+    | A formula represented in Conjunctive Normal Form.
+    | A formula is a set of clauses.
+    | For example,
+    |   {{A1, A2, A3'}, {A5', A2', A1}} is ((A1 v A2 v A3') and (A5' v A2' v A1))
     """
 
     def __init__(self, clauses: Iterable[Clause]) -> None:
@@ -107,7 +113,8 @@ def __init__(self, clauses: Iterable[Clause]) -> None:
     def __str__(self) -> str:
         """
         To print a formula as in Conjunctive Normal Form.
-        str(Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]))
+
+        >>> str(Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]))
         "{{A1 , A2' , A3} , {A5' , A2' , A1}}"
         """
         return "{" + " , ".join(str(clause) for clause in self.clauses) + "}"
@@ -115,8 +122,8 @@ def __str__(self) -> str:
 
 def generate_clause() -> Clause:
     """
-    Randomly generate a clause.
-    All literals have the name Ax, where x is an integer from 1 to 5.
+    | Randomly generate a clause.
+    | All literals have the name Ax, where x is an integer from ``1`` to ``5``.
     """
     literals = []
     no_of_literals = random.randint(1, 5)
@@ -149,11 +156,12 @@ def generate_formula() -> Formula:
 
 def generate_parameters(formula: Formula) -> tuple[list[Clause], list[str]]:
     """
-    Return the clauses and symbols from a formula.
-    A symbol is the uncomplemented form of a literal.
+    | Return the clauses and symbols from a formula.
+    | A symbol is the uncomplemented form of a literal.
+
     For example,
-        Symbol of A3 is A3.
-        Symbol of A5' is A5.
+      * Symbol of A3 is A3.
+      * Symbol of A5' is A5.
 
     >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])])
     >>> clauses, symbols = generate_parameters(formula)
@@ -177,21 +185,20 @@ def find_pure_symbols(
     clauses: list[Clause], symbols: list[str], model: dict[str, bool | None]
 ) -> tuple[list[str], dict[str, bool | None]]:
     """
-    Return pure symbols and their values to satisfy clause.
-    Pure symbols are symbols in a formula that exist only
-    in one form, either complemented or otherwise.
-    For example,
-        { { A4 , A3 , A5' , A1 , A3' } , { A4 } , { A3 } } has
-        pure symbols A4, A5' and A1.
+    | Return pure symbols and their values to satisfy clause.
+    | Pure symbols are symbols in a formula that exist only in one form,
+    | either complemented or otherwise.
+    | For example,
+    |   {{A4 , A3 , A5' , A1 , A3'} , {A4} , {A3}} has pure symbols A4, A5' and A1.
+
     This has the following steps:
-    1. Ignore clauses that have already evaluated to be True.
-    2. Find symbols that occur only in one form in the rest of the clauses.
-    3. Assign value True or False depending on whether the symbols occurs
-    in normal or complemented form respectively.
+      1. Ignore clauses that have already evaluated to be ``True``.
+      2. Find symbols that occur only in one form in the rest of the clauses.
+      3. Assign value ``True`` or ``False`` depending on whether the symbols occurs
+         in normal or complemented form respectively.
 
     >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])])
     >>> clauses, symbols = generate_parameters(formula)
-
     >>> pure_symbols, values = find_pure_symbols(clauses, symbols, {})
     >>> pure_symbols
     ['A1', 'A2', 'A3', 'A5']
@@ -231,20 +238,21 @@ def find_unit_clauses(
 ) -> tuple[list[str], dict[str, bool | None]]:
     """
     Returns the unit symbols and their values to satisfy clause.
+
     Unit symbols are symbols in a formula that are:
-    - Either the only symbol in a clause
-    - Or all other literals in that clause have been assigned False
+      - Either the only symbol in a clause
+      - Or all other literals in that clause have been assigned ``False``
+
     This has the following steps:
-    1. Find symbols that are the only occurrences in a clause.
-    2. Find symbols in a clause where all other literals are assigned False.
-    3. Assign True or False depending on whether the symbols occurs in
-    normal or complemented form respectively.
+      1. Find symbols that are the only occurrences in a clause.
+      2. Find symbols in a clause where all other literals are assigned ``False``.
+      3. Assign ``True`` or ``False`` depending on whether the symbols occurs in
+         normal or complemented form respectively.
 
     >>> clause1 = Clause(["A4", "A3", "A5'", "A1", "A3'"])
     >>> clause2 = Clause(["A4"])
     >>> clause3 = Clause(["A3"])
     >>> clauses, symbols = generate_parameters(Formula([clause1, clause2, clause3]))
-
     >>> unit_clauses, values = find_unit_clauses(clauses, {})
     >>> unit_clauses
     ['A4', 'A3']
@@ -278,16 +286,16 @@ def dpll_algorithm(
     clauses: list[Clause], symbols: list[str], model: dict[str, bool | None]
 ) -> tuple[bool | None, dict[str, bool | None] | None]:
     """
-    Returns the model if the formula is satisfiable, else None
+    Returns the model if the formula is satisfiable, else ``None``
+
     This has the following steps:
-    1. If every clause in clauses is True, return True.
-    2. If some clause in clauses is False, return False.
-    3. Find pure symbols.
-    4. Find unit symbols.
+      1. If every clause in clauses is ``True``, return ``True``.
+      2. If some clause in clauses is ``False``, return ``False``.
+      3. Find pure symbols.
+      4. Find unit symbols.
 
     >>> formula = Formula([Clause(["A4", "A3", "A5'", "A1", "A3'"]), Clause(["A4"])])
     >>> clauses, symbols = generate_parameters(formula)
-
     >>> soln, model = dpll_algorithm(clauses, symbols, {})
     >>> soln
     True
diff --git a/other/scoring_algorithm.py b/other/scoring_algorithm.py
index af04f432e433..0185d7a2e0c0 100644
--- a/other/scoring_algorithm.py
+++ b/other/scoring_algorithm.py
@@ -1,25 +1,26 @@
 """
-developed by: markmelnic
-original repo: https://github.com/markmelnic/Scoring-Algorithm
+| developed by: markmelnic
+| original repo: https://github.com/markmelnic/Scoring-Algorithm
 
 Analyse data using a range based percentual proximity algorithm
 and calculate the linear maximum likelihood estimation.
 The basic principle is that all values supplied will be broken
-down to a range from 0 to 1 and each column's score will be added
+down to a range from ``0`` to ``1`` and each column's score will be added
 up to get the total score.
 
-==========
 Example for data of vehicles
-price|mileage|registration_year
-20k  |60k    |2012
-22k  |50k    |2011
-23k  |90k    |2015
-16k  |210k   |2010
+::
+
+    price|mileage|registration_year
+    20k  |60k    |2012
+    22k  |50k    |2011
+    23k  |90k    |2015
+    16k  |210k   |2010
 
 We want the vehicle with the lowest price,
 lowest mileage but newest registration year.
 Thus the weights for each column are as follows:
-[0, 0, 1]
+``[0, 0, 1]``
 """
 
 
@@ -97,10 +98,11 @@ def procentual_proximity(
     source_data: list[list[float]], weights: list[int]
 ) -> list[list[float]]:
     """
-    weights - int list
-    possible values - 0 / 1
-    0 if lower values have higher weight in the data set
-    1 if higher values have higher weight in the data set
+    | `weights` - ``int`` list
+    | possible values - ``0`` / ``1``
+
+        * ``0`` if lower values have higher weight in the data set
+        * ``1`` if higher values have higher weight in the data set
 
     >>> procentual_proximity([[20, 60, 2012],[23, 90, 2015],[22, 50, 2011]], [0, 0, 1])
     [[20, 60, 2012, 2.0], [23, 90, 2015, 1.0], [22, 50, 2011, 1.3333333333333335]]

From 94b3777936101bcc592fc5ef143ac08ad49195e7 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 30 Dec 2024 00:35:34 +0300
Subject: [PATCH 031/135] Fix sphinx/build_docs warnings for linear_algebra
 (#12483)

* Fix sphinx/build_docs warnings for linear_algebra/

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 linear_algebra/gaussian_elimination.py        | 28 +++++++++++++------
 linear_algebra/lu_decomposition.py            | 20 +++++++------
 .../src/gaussian_elimination_pivoting.py      |  7 +++--
 linear_algebra/src/rank_of_matrix.py          |  6 +++-
 linear_algebra/src/schur_complement.py        | 13 +++++----
 linear_algebra/src/transformations_2d.py      | 14 ++++++----
 6 files changed, 54 insertions(+), 34 deletions(-)

diff --git a/linear_algebra/gaussian_elimination.py b/linear_algebra/gaussian_elimination.py
index 724773c0db98..6f4075b710fd 100644
--- a/linear_algebra/gaussian_elimination.py
+++ b/linear_algebra/gaussian_elimination.py
@@ -1,6 +1,6 @@
 """
-Gaussian elimination method for solving a system of linear equations.
-Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination
+| Gaussian elimination method for solving a system of linear equations.
+| Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination
 """
 
 import numpy as np
@@ -13,12 +13,17 @@ def retroactive_resolution(
 ) -> NDArray[float64]:
     """
     This function performs a retroactive linear system resolution
-        for triangular matrix
+    for triangular matrix
 
     Examples:
-        2x1 + 2x2 - 1x3 = 5         2x1 + 2x2 = -1
-        0x1 - 2x2 - 1x3 = -7        0x1 - 2x2 = -1
-        0x1 + 0x2 + 5x3 = 15
+        1.
+            * 2x1 + 2x2 - 1x3 = 5
+            * 0x1 - 2x2 - 1x3 = -7
+            * 0x1 + 0x2 + 5x3 = 15
+        2.
+            * 2x1 + 2x2 = -1
+            * 0x1 - 2x2 = -1
+
     >>> gaussian_elimination([[2, 2, -1], [0, -2, -1], [0, 0, 5]], [[5], [-7], [15]])
     array([[2.],
            [2.],
@@ -45,9 +50,14 @@ def gaussian_elimination(
     This function performs Gaussian elimination method
 
     Examples:
-        1x1 - 4x2 - 2x3 = -2        1x1 + 2x2 = 5
-        5x1 + 2x2 - 2x3 = -3        5x1 + 2x2 = 5
-        1x1 - 1x2 + 0x3 = 4
+        1.
+            * 1x1 - 4x2 - 2x3 = -2
+            * 5x1 + 2x2 - 2x3 = -3
+            * 1x1 - 1x2 + 0x3 = 4
+        2.
+            * 1x1 + 2x2 = 5
+            * 5x1 + 2x2 = 5
+
     >>> gaussian_elimination([[1, -4, -2], [5, 2, -2], [1, -1, 0]], [[-2], [-3], [4]])
     array([[ 2.3 ],
            [-1.7 ],
diff --git a/linear_algebra/lu_decomposition.py b/linear_algebra/lu_decomposition.py
index 3620674835cd..3d89b53a48fb 100644
--- a/linear_algebra/lu_decomposition.py
+++ b/linear_algebra/lu_decomposition.py
@@ -2,13 +2,14 @@
 Lower-upper (LU) decomposition factors a matrix as a product of a lower
 triangular matrix and an upper triangular matrix. A square matrix has an LU
 decomposition under the following conditions:
+
     - If the matrix is invertible, then it has an LU decomposition if and only
-    if all of its leading principal minors are non-zero (see
-    https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
-    leading principal minors of a matrix).
+      if all of its leading principal minors are non-zero (see
+      https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
+      leading principal minors of a matrix).
     - If the matrix is singular (i.e., not invertible) and it has a rank of k
-    (i.e., it has k linearly independent columns), then it has an LU
-    decomposition if its first k leading principal minors are non-zero.
+      (i.e., it has k linearly independent columns), then it has an LU
+      decomposition if its first k leading principal minors are non-zero.
 
 This algorithm will simply attempt to perform LU decomposition on any square
 matrix and raise an error if no such decomposition exists.
@@ -25,6 +26,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
     """
     Perform LU decomposition on a given matrix and raises an error if the matrix
     isn't square or if no such decomposition exists
+
     >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]])
     >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
     >>> lower_mat
@@ -45,7 +47,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
     array([[ 4. ,  3. ],
            [ 0. , -1.5]])
 
-    # Matrix is not square
+    >>> # Matrix is not square
     >>> matrix = np.array([[2, -2, 1], [0, 1, 2]])
     >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
     Traceback (most recent call last):
@@ -54,14 +56,14 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
     [[ 2 -2  1]
      [ 0  1  2]]
 
-    # Matrix is invertible, but its first leading principal minor is 0
+    >>> # Matrix is invertible, but its first leading principal minor is 0
     >>> matrix = np.array([[0, 1], [1, 0]])
     >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
     Traceback (most recent call last):
     ...
     ArithmeticError: No LU decomposition exists
 
-    # Matrix is singular, but its first leading principal minor is 1
+    >>> # Matrix is singular, but its first leading principal minor is 1
     >>> matrix = np.array([[1, 0], [1, 0]])
     >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
     >>> lower_mat
@@ -71,7 +73,7 @@ def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray
     array([[1., 0.],
            [0., 0.]])
 
-    # Matrix is singular, but its first leading principal minor is 0
+    >>> # Matrix is singular, but its first leading principal minor is 0
     >>> matrix = np.array([[0, 1], [0, 1]])
     >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
     Traceback (most recent call last):
diff --git a/linear_algebra/src/gaussian_elimination_pivoting.py b/linear_algebra/src/gaussian_elimination_pivoting.py
index efc1ddd64a2e..540f57b0cff6 100644
--- a/linear_algebra/src/gaussian_elimination_pivoting.py
+++ b/linear_algebra/src/gaussian_elimination_pivoting.py
@@ -6,17 +6,18 @@ def solve_linear_system(matrix: np.ndarray) -> np.ndarray:
     Solve a linear system of equations using Gaussian elimination with partial pivoting
 
     Args:
-    - matrix: Coefficient matrix with the last column representing the constants.
+      - `matrix`: Coefficient matrix with the last column representing the constants.
 
     Returns:
-    - Solution vector.
+      - Solution vector.
 
     Raises:
-    - ValueError: If the matrix is not correct (i.e., singular).
+      - ``ValueError``: If the matrix is not correct (i.e., singular).
 
     https://courses.engr.illinois.edu/cs357/su2013/lect.htm Lecture 7
 
     Example:
+
     >>> A = np.array([[2, 1, -1], [-3, -1, 2], [-2, 1, 2]], dtype=float)
     >>> B = np.array([8, -11, -3], dtype=float)
     >>> solution = solve_linear_system(np.column_stack((A, B)))
diff --git a/linear_algebra/src/rank_of_matrix.py b/linear_algebra/src/rank_of_matrix.py
index 7ff3c1699a69..2c4fe2a8d1da 100644
--- a/linear_algebra/src/rank_of_matrix.py
+++ b/linear_algebra/src/rank_of_matrix.py
@@ -8,11 +8,15 @@
 def rank_of_matrix(matrix: list[list[int | float]]) -> int:
     """
     Finds the rank of a matrix.
+
     Args:
-        matrix: The matrix as a list of lists.
+        `matrix`: The matrix as a list of lists.
+
     Returns:
         The rank of the matrix.
+
     Example:
+
     >>> matrix1 = [[1, 2, 3],
     ...            [4, 5, 6],
     ...            [7, 8, 9]]
diff --git a/linear_algebra/src/schur_complement.py b/linear_algebra/src/schur_complement.py
index 7c79bb70abfc..74ac75e3fce2 100644
--- a/linear_algebra/src/schur_complement.py
+++ b/linear_algebra/src/schur_complement.py
@@ -12,13 +12,14 @@ def schur_complement(
 ) -> np.ndarray:
     """
     Schur complement of a symmetric matrix X given as a 2x2 block matrix
-    consisting of matrices A, B and C.
-    Matrix A must be quadratic and non-singular.
-    In case A is singular, a pseudo-inverse may be provided using
-    the pseudo_inv argument.
+    consisting of matrices `A`, `B` and `C`.
+    Matrix `A` must be quadratic and non-singular.
+    In case `A` is singular, a pseudo-inverse may be provided using
+    the `pseudo_inv` argument.
+
+    | Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement
+    | See also Convex Optimization - Boyd and Vandenberghe, A.5.5
 
-    Link to Wiki: https://en.wikipedia.org/wiki/Schur_complement
-    See also Convex Optimization - Boyd and Vandenberghe, A.5.5
     >>> import numpy as np
     >>> a = np.array([[1, 2], [2, 1]])
     >>> b = np.array([[0, 3], [3, 0]])
diff --git a/linear_algebra/src/transformations_2d.py b/linear_algebra/src/transformations_2d.py
index b4185cd2848f..5dee59024752 100644
--- a/linear_algebra/src/transformations_2d.py
+++ b/linear_algebra/src/transformations_2d.py
@@ -3,13 +3,15 @@
 
 I have added the codes for reflection, projection, scaling and rotation 2D matrices.
 
+.. code-block:: python
+
     scaling(5) = [[5.0, 0.0], [0.0, 5.0]]
-  rotation(45) = [[0.5253219888177297, -0.8509035245341184],
-                  [0.8509035245341184, 0.5253219888177297]]
-projection(45) = [[0.27596319193541496, 0.446998331800279],
-                  [0.446998331800279, 0.7240368080645851]]
-reflection(45) = [[0.05064397763545947, 0.893996663600558],
-                  [0.893996663600558, 0.7018070490682369]]
+    rotation(45) = [[0.5253219888177297, -0.8509035245341184],
+                    [0.8509035245341184, 0.5253219888177297]]
+    projection(45) = [[0.27596319193541496, 0.446998331800279],
+                      [0.446998331800279, 0.7240368080645851]]
+    reflection(45) = [[0.05064397763545947, 0.893996663600558],
+                      [0.893996663600558, 0.7018070490682369]]
 """
 
 from math import cos, sin

From f45e392cf6e94259eca8c47b13cd3ae22bcd901e Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 30 Dec 2024 12:56:24 +0300
Subject: [PATCH 032/135] Fix sphinx/build_docs warnings for ciphers (#12485)

* Fix sphinx/build_docs warnings for ciphers

* Fix
---
 ciphers/autokey.py                         |  7 +-
 ciphers/caesar_cipher.py                   | 77 ++++++++++-------
 ciphers/decrypt_caesar_with_chi_squared.py | 99 +++++++++++-----------
 ciphers/enigma_machine2.py                 | 64 +++++++-------
 ciphers/rsa_factorization.py               | 13 +--
 ciphers/simple_keyword_cypher.py           | 10 ++-
 ciphers/trifid_cipher.py                   | 25 +++---
 7 files changed, 170 insertions(+), 125 deletions(-)

diff --git a/ciphers/autokey.py b/ciphers/autokey.py
index 05d8c066b139..7751a32d7546 100644
--- a/ciphers/autokey.py
+++ b/ciphers/autokey.py
@@ -1,5 +1,6 @@
 """
 https://en.wikipedia.org/wiki/Autokey_cipher
+
 An autokey cipher (also known as the autoclave cipher) is a cipher that
 incorporates the message (the plaintext) into the key.
 The key is generated from the message in some automated fashion,
@@ -10,8 +11,9 @@
 
 def encrypt(plaintext: str, key: str) -> str:
     """
-    Encrypt a given plaintext (string) and key (string), returning the
+    Encrypt a given `plaintext` (string) and `key` (string), returning the
     encrypted ciphertext.
+
     >>> encrypt("hello world", "coffee")
     'jsqqs avvwo'
     >>> encrypt("coffee is good as python", "TheAlgorithms")
@@ -74,8 +76,9 @@ def encrypt(plaintext: str, key: str) -> str:
 
 def decrypt(ciphertext: str, key: str) -> str:
     """
-    Decrypt a given ciphertext (string) and key (string), returning the decrypted
+    Decrypt a given `ciphertext` (string) and `key` (string), returning the decrypted
     ciphertext.
+
     >>> decrypt("jsqqs avvwo", "coffee")
     'hello world'
     >>> decrypt("vvjfpk wj ohvp su ddylsv", "TheAlgorithms")
diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py
index d19b9a337221..9c096fe8a7da 100644
--- a/ciphers/caesar_cipher.py
+++ b/ciphers/caesar_cipher.py
@@ -7,24 +7,29 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
     """
     encrypt
     =======
+
     Encodes a given string with the caesar cipher and returns the encoded
     message
 
     Parameters:
     -----------
-    *   input_string: the plain-text that needs to be encoded
-    *   key: the number of letters to shift the message by
+
+    *   `input_string`: the plain-text that needs to be encoded
+    *   `key`: the number of letters to shift the message by
 
     Optional:
-    *   alphabet (None): the alphabet used to encode the cipher, if not
+
+    *   `alphabet` (``None``): the alphabet used to encode the cipher, if not
         specified, the standard english alphabet with upper and lowercase
         letters is used
 
     Returns:
+
     *   A string containing the encoded cipher-text
 
     More on the caesar cipher
     =========================
+
     The caesar cipher is named after Julius Caesar who used it when sending
     secret military messages to his troops. This is a simple substitution cipher
     where every character in the plain-text is shifted by a certain number known
@@ -32,26 +37,28 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
 
     Example:
     Say we have the following message:
-    "Hello, captain"
+    ``Hello, captain``
 
     And our alphabet is made up of lower and uppercase letters:
-    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    ``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
 
-    And our shift is "2"
+    And our shift is ``2``
 
-    We can then encode the message, one letter at a time. "H" would become "J",
-    since "J" is two letters away, and so on. If the shift is ever two large, or
+    We can then encode the message, one letter at a time. ``H`` would become ``J``,
+    since ``J`` is two letters away, and so on. If the shift is ever two large, or
     our letter is at the end of the alphabet, we just start at the beginning
-    ("Z" would shift to "a" then "b" and so on).
+    (``Z`` would shift to ``a`` then ``b`` and so on).
 
-    Our final message would be "Jgnnq, ecrvckp"
+    Our final message would be ``Jgnnq, ecrvckp``
 
     Further reading
     ===============
+
     *   https://en.m.wikipedia.org/wiki/Caesar_cipher
 
     Doctests
     ========
+
     >>> encrypt('The quick brown fox jumps over the lazy dog', 8)
     'bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo'
 
@@ -85,23 +92,28 @@ def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
     """
     decrypt
     =======
+
     Decodes a given string of cipher-text and returns the decoded plain-text
 
     Parameters:
     -----------
-    *   input_string: the cipher-text that needs to be decoded
-    *   key: the number of letters to shift the message backwards by to decode
+
+    *   `input_string`: the cipher-text that needs to be decoded
+    *   `key`: the number of letters to shift the message backwards by to decode
 
     Optional:
-    *   alphabet (None): the alphabet used to decode the cipher, if not
+
+    *   `alphabet` (``None``): the alphabet used to decode the cipher, if not
         specified, the standard english alphabet with upper and lowercase
         letters is used
 
     Returns:
+
     *   A string containing the decoded plain-text
 
     More on the caesar cipher
     =========================
+
     The caesar cipher is named after Julius Caesar who used it when sending
     secret military messages to his troops. This is a simple substitution cipher
     where very character in the plain-text is shifted by a certain number known
@@ -110,27 +122,29 @@ def decrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
 
     Example:
     Say we have the following cipher-text:
-    "Jgnnq, ecrvckp"
+    ``Jgnnq, ecrvckp``
 
     And our alphabet is made up of lower and uppercase letters:
-    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+    ``abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ``
 
-    And our shift is "2"
+    And our shift is ``2``
 
     To decode the message, we would do the same thing as encoding, but in
-    reverse. The first letter, "J" would become "H" (remember: we are decoding)
-    because "H" is two letters in reverse (to the left) of "J". We would
-    continue doing this. A letter like "a" would shift back to the end of
-    the alphabet, and would become "Z" or "Y" and so on.
+    reverse. The first letter, ``J`` would become ``H`` (remember: we are decoding)
+    because ``H`` is two letters in reverse (to the left) of ``J``. We would
+    continue doing this. A letter like ``a`` would shift back to the end of
+    the alphabet, and would become ``Z`` or ``Y`` and so on.
 
-    Our final message would be "Hello, captain"
+    Our final message would be ``Hello, captain``
 
     Further reading
     ===============
+
     *   https://en.m.wikipedia.org/wiki/Caesar_cipher
 
     Doctests
     ========
+
     >>> decrypt('bpm yCqks jzwEv nwF rCuxA wDmz Bpm tiHG lwo', 8)
     'The quick brown fox jumps over the lazy dog'
 
@@ -150,41 +164,44 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str
     """
     brute_force
     ===========
+
     Returns all the possible combinations of keys and the decoded strings in the
     form of a dictionary
 
     Parameters:
     -----------
-    *   input_string: the cipher-text that needs to be used during brute-force
+
+    *   `input_string`: the cipher-text that needs to be used during brute-force
 
     Optional:
-    *   alphabet:  (None): the alphabet used to decode the cipher, if not
+
+    *   `alphabet` (``None``): the alphabet used to decode the cipher, if not
         specified, the standard english alphabet with upper and lowercase
         letters is used
 
     More about brute force
     ======================
+
     Brute force is when a person intercepts a message or password, not knowing
     the key and tries every single combination. This is easy with the caesar
     cipher since there are only all the letters in the alphabet. The more
     complex the cipher, the larger amount of time it will take to do brute force
 
     Ex:
-    Say we have a 5 letter alphabet (abcde), for simplicity and we intercepted the
-    following message:
-
-    "dbc"
-
+    Say we have a ``5`` letter alphabet (``abcde``), for simplicity and we intercepted
+    the following message: ``dbc``,
     we could then just write out every combination:
-    ecd... and so on, until we reach a combination that makes sense:
-    "cab"
+    ``ecd``... and so on, until we reach a combination that makes sense:
+    ``cab``
 
     Further reading
     ===============
+
     *   https://en.wikipedia.org/wiki/Brute_force
 
     Doctests
     ========
+
     >>> brute_force("jFyuMy xIH'N vLONy zILwy Gy!")[20]
     "Please don't brute force me!"
 
diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py
index 10832203e531..fb95c0f90628 100644
--- a/ciphers/decrypt_caesar_with_chi_squared.py
+++ b/ciphers/decrypt_caesar_with_chi_squared.py
@@ -11,33 +11,31 @@ def decrypt_caesar_with_chi_squared(
     """
     Basic Usage
     ===========
+
     Arguments:
-    * ciphertext (str): the text to decode (encoded with the caesar cipher)
+      * `ciphertext` (str): the text to decode (encoded with the caesar cipher)
 
     Optional Arguments:
-    * cipher_alphabet (list): the alphabet used for the cipher (each letter is
-      a string separated by commas)
-    * frequencies_dict (dict): a dictionary of word frequencies where keys are
-      the letters and values are a percentage representation of the frequency as
-      a decimal/float
-    * case_sensitive (bool): a boolean value: True if the case matters during
-      decryption, False if it doesn't
+      * `cipher_alphabet` (list): the alphabet used for the cipher (each letter is
+        a string separated by commas)
+      * `frequencies_dict` (dict): a dictionary of word frequencies where keys are
+        the letters and values are a percentage representation of the frequency as
+        a decimal/float
+      * `case_sensitive` (bool): a boolean value: ``True`` if the case matters during
+        decryption, ``False`` if it doesn't
 
     Returns:
-    * A tuple in the form of:
-      (
-        most_likely_cipher,
-        most_likely_cipher_chi_squared_value,
-        decoded_most_likely_cipher
-      )
+      * A tuple in the form of:
+        (`most_likely_cipher`, `most_likely_cipher_chi_squared_value`,
+        `decoded_most_likely_cipher`)
 
-      where...
-      - most_likely_cipher is an integer representing the shift of the smallest
-        chi-squared statistic (most likely key)
-      - most_likely_cipher_chi_squared_value is a float representing the
-        chi-squared statistic of the most likely shift
-      - decoded_most_likely_cipher is a string with the decoded cipher
-        (decoded by the most_likely_cipher key)
+        where...
+          - `most_likely_cipher` is an integer representing the shift of the smallest
+            chi-squared statistic (most likely key)
+          - `most_likely_cipher_chi_squared_value` is a float representing the
+            chi-squared statistic of the most likely shift
+          - `decoded_most_likely_cipher` is a string with the decoded cipher
+            (decoded by the most_likely_cipher key)
 
 
     The Chi-squared test
@@ -45,52 +43,57 @@ def decrypt_caesar_with_chi_squared(
 
     The caesar cipher
     -----------------
+
     The caesar cipher is a very insecure encryption algorithm, however it has
     been used since Julius Caesar. The cipher is a simple substitution cipher
     where each character in the plain text is replaced by a character in the
     alphabet a certain number of characters after the original character. The
     number of characters away is called the shift or key. For example:
 
-    Plain text: hello
-    Key: 1
-    Cipher text: ifmmp
-    (each letter in hello has been shifted one to the right in the eng. alphabet)
+    | Plain text: ``hello``
+    | Key: ``1``
+    | Cipher text: ``ifmmp``
+    | (each letter in ``hello`` has been shifted one to the right in the eng. alphabet)
 
     As you can imagine, this doesn't provide lots of security. In fact
     decrypting ciphertext by brute-force is extremely easy even by hand. However
-     one way to do that is the chi-squared test.
+    one way to do that is the chi-squared test.
 
     The chi-squared test
-    -------------------
+    --------------------
+
     Each letter in the english alphabet has a frequency, or the amount of times
     it shows up compared to other letters (usually expressed as a decimal
     representing the percentage likelihood). The most common letter in the
-    english language is "e" with a frequency of 0.11162 or 11.162%. The test is
-    completed in the following fashion.
+    english language is ``e`` with a frequency of ``0.11162`` or ``11.162%``.
+    The test is completed in the following fashion.
 
     1. The ciphertext is decoded in a brute force way (every combination of the
-       26 possible combinations)
+       ``26`` possible combinations)
     2. For every combination, for each letter in the combination, the average
        amount of times the letter should appear the message is calculated by
-       multiplying the total number of characters by the frequency of the letter
+       multiplying the total number of characters by the frequency of the letter.
+
+       | For example:
+       | In a message of ``100`` characters, ``e`` should appear around ``11.162``
+         times.
 
-       For example:
-       In a message of 100 characters, e should appear around 11.162 times.
+    3. Then, to calculate the margin of error (the amount of times the letter
+       SHOULD appear with the amount of times the letter DOES appear), we use
+       the chi-squared test. The following formula is used:
 
-     3. Then, to calculate the margin of error (the amount of times the letter
-        SHOULD appear with the amount of times the letter DOES appear), we use
-        the chi-squared test. The following formula is used:
+       Let:
+         - n be the number of times the letter actually appears
+         - p be the predicted value of the number of times the letter should
+           appear (see item ``2``)
+         - let v be the chi-squared test result (referred to here as chi-squared
+           value/statistic)
 
-        Let:
-        - n be the number of times the letter actually appears
-        - p be the predicted value of the number of times the letter should
-          appear (see #2)
-        - let v be the chi-squared test result (referred to here as chi-squared
-          value/statistic)
+         ::
 
-        (n - p)^2
-        --------- = v
-           p
+           (n - p)^2
+           --------- = v
+              p
 
     4. Each chi squared value for each letter is then added up to the total.
        The total is the chi-squared statistic for that encryption key.
@@ -98,16 +101,16 @@ def decrypt_caesar_with_chi_squared(
        to be the decoded answer.
 
     Further Reading
-    ================
+    ===============
 
-    * http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-
-        statistic/
+    * http://practicalcryptography.com/cryptanalysis/text-characterisation/chi-squared-statistic/
     * https://en.wikipedia.org/wiki/Letter_frequency
     * https://en.wikipedia.org/wiki/Chi-squared_test
     * https://en.m.wikipedia.org/wiki/Caesar_cipher
 
     Doctests
     ========
+
     >>> decrypt_caesar_with_chi_squared(
     ...    'dof pz aol jhlzhy jpwoly zv wvwbshy? pa pz avv lhzf av jyhjr!'
     ... )  # doctest: +NORMALIZE_WHITESPACE
diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py
index 163aa7172c11..e42fdd82ed41 100644
--- a/ciphers/enigma_machine2.py
+++ b/ciphers/enigma_machine2.py
@@ -1,14 +1,16 @@
 """
-Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine
-Video explanation: https://youtu.be/QwQVMqfoB2E
-Also check out Numberphile's and Computerphile's videos on this topic
+| Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine
+| Video explanation: https://youtu.be/QwQVMqfoB2E
+| Also check out Numberphile's and Computerphile's videos on this topic
 
-This module contains function 'enigma' which emulates
+This module contains function ``enigma`` which emulates
 the famous Enigma machine from WWII.
+
 Module includes:
-- enigma function
+
+- ``enigma`` function
 - showcase of function usage
-- 9 randomly generated rotors
+- ``9`` randomly generated rotors
 - reflector (aka static rotor)
 - original alphabet
 
@@ -73,7 +75,7 @@ def _validator(
     rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str
 ) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]:
     """
-    Checks if the values can be used for the 'enigma' function
+    Checks if the values can be used for the ``enigma`` function
 
     >>> _validator((1,1,1), (rotor1, rotor2, rotor3), 'POLAND')
     ((1, 1, 1), ('EGZWVONAHDCLFQMSIPJBYUKXTR', 'FOBHMDKEXQNRAULPGSJVTYICZW', \
@@ -83,7 +85,7 @@ def _validator(
     :param rotpos: rotor_positon
     :param rotsel: rotor_selection
     :param pb: plugb -> validated and transformed
-    :return: (rotpos, rotsel, pb)
+    :return: (`rotpos`, `rotsel`, `pb`)
     """
     # Checks if there are 3 unique rotors
 
@@ -118,9 +120,10 @@ def _plugboard(pbstring: str) -> dict[str, str]:
     >>> _plugboard('POLAND')
     {'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'}
 
-    In the code, 'pb' stands for 'plugboard'
+    In the code, ``pb`` stands for ``plugboard``
 
     Pairs can be separated by spaces
+
     :param pbstring: string containing plugboard setting for the Enigma machine
     :return: dictionary containing converted pairs
     """
@@ -168,31 +171,34 @@ def enigma(
     plugb: str = "",
 ) -> str:
     """
-    The only difference with real-world enigma is that I allowed string input.
+    The only difference with real-world enigma is that ``I`` allowed string input.
     All characters are converted to uppercase. (non-letter symbol are ignored)
-    How it works:
-    (for every letter in the message)
+
+    | How it works:
+    | (for every letter in the message)
 
     - Input letter goes into the plugboard.
-    If it is connected to another one, switch it.
+      If it is connected to another one, switch it.
+
+    - Letter goes through ``3`` rotors.
+      Each rotor can be represented as ``2`` sets of symbol, where one is shuffled.
+      Each symbol from the first set has corresponding symbol in
+      the second set and vice versa.
 
-    - Letter goes through 3 rotors.
-    Each rotor can be represented as 2 sets of symbol, where one is shuffled.
-    Each symbol from the first set has corresponding symbol in
-    the second set and vice versa.
+      example::
 
-    example:
-    | ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F
-    | VKLEPDBGRNWTFCJOHQAMUZYIXS |
+      | ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F
+      | VKLEPDBGRNWTFCJOHQAMUZYIXS |
 
     - Symbol then goes through reflector (static rotor).
-    There it is switched with paired symbol
-    The reflector can be represented as2 sets, each with half of the alphanet.
-    There are usually 10 pairs of letters.
+      There it is switched with paired symbol.
+      The reflector can be represented as ``2`` sets, each with half of the alphanet.
+      There are usually ``10`` pairs of letters.
+
+      Example::
 
-    Example:
-    | ABCDEFGHIJKLM | e.g. E is paired to X
-    | ZYXWVUTSRQPON | so when E goes in X goes out and vice versa
+      | ABCDEFGHIJKLM | e.g. E is paired to X
+      | ZYXWVUTSRQPON | so when E goes in X goes out and vice versa
 
     - Letter then goes through the rotors again
 
@@ -211,9 +217,9 @@ def enigma(
 
 
     :param text: input message
-    :param rotor_position: tuple with 3 values in range 1..26
-    :param rotor_selection: tuple with 3 rotors ()
-    :param plugb: string containing plugboard configuration (default '')
+    :param rotor_position: tuple with ``3`` values in range ``1``.. ``26``
+    :param rotor_selection: tuple with ``3`` rotors
+    :param plugb: string containing plugboard configuration (default ``''``)
     :return: en/decrypted string
     """
 
diff --git a/ciphers/rsa_factorization.py b/ciphers/rsa_factorization.py
index 0a358a4fc2d4..585b21fac856 100644
--- a/ciphers/rsa_factorization.py
+++ b/ciphers/rsa_factorization.py
@@ -3,8 +3,10 @@
 
 The program can efficiently factor RSA prime number given the private key d and
 public key e.
-Source: on page 3 of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf
-More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html
+
+| Source: on page ``3`` of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf
+| More readable source: https://www.di-mgt.com.au/rsa_factorize_n.html
+
 large number can take minutes to factor, therefore are not included in doctest.
 """
 
@@ -17,13 +19,14 @@
 def rsafactor(d: int, e: int, n: int) -> list[int]:
     """
     This function returns the factors of N, where p*q=N
-      Return: [p, q]
+
+    Return: [p, q]
 
     We call N the RSA modulus, e the encryption exponent, and d the decryption exponent.
     The pair (N, e) is the public key. As its name suggests, it is public and is used to
-        encrypt messages.
+    encrypt messages.
     The pair (N, d) is the secret key or private key and is known only to the recipient
-        of encrypted messages.
+    of encrypted messages.
 
     >>> rsafactor(3, 16971, 25777)
     [149, 173]
diff --git a/ciphers/simple_keyword_cypher.py b/ciphers/simple_keyword_cypher.py
index 9dc624e7762c..bde137d826c3 100644
--- a/ciphers/simple_keyword_cypher.py
+++ b/ciphers/simple_keyword_cypher.py
@@ -1,9 +1,11 @@
 def remove_duplicates(key: str) -> str:
     """
     Removes duplicate alphabetic characters in a keyword (letter is ignored after its
-        first appearance).
+    first appearance).
+
     :param key: Keyword to use
     :return: String with duplicates removed
+
     >>> remove_duplicates('Hello World!!')
     'Helo Wrd'
     """
@@ -18,6 +20,7 @@ def remove_duplicates(key: str) -> str:
 def create_cipher_map(key: str) -> dict[str, str]:
     """
     Returns a cipher map given a keyword.
+
     :param key: keyword to use
     :return: dictionary cipher map
     """
@@ -43,9 +46,11 @@ def create_cipher_map(key: str) -> dict[str, str]:
 def encipher(message: str, cipher_map: dict[str, str]) -> str:
     """
     Enciphers a message given a cipher map.
+
     :param message: Message to encipher
     :param cipher_map: Cipher map
     :return: enciphered string
+
     >>> encipher('Hello World!!', create_cipher_map('Goodbye!!'))
     'CYJJM VMQJB!!'
     """
@@ -55,9 +60,11 @@ def encipher(message: str, cipher_map: dict[str, str]) -> str:
 def decipher(message: str, cipher_map: dict[str, str]) -> str:
     """
     Deciphers a message given a cipher map
+
     :param message: Message to decipher
     :param cipher_map: Dictionary mapping to use
     :return: Deciphered string
+
     >>> cipher_map = create_cipher_map('Goodbye!!')
     >>> decipher(encipher('Hello World!!', cipher_map), cipher_map)
     'HELLO WORLD!!'
@@ -70,6 +77,7 @@ def decipher(message: str, cipher_map: dict[str, str]) -> str:
 def main() -> None:
     """
     Handles I/O
+
     :return: void
     """
     message = input("Enter message to encode or decode: ").strip()
diff --git a/ciphers/trifid_cipher.py b/ciphers/trifid_cipher.py
index 16b9faf67688..9613cee0669d 100644
--- a/ciphers/trifid_cipher.py
+++ b/ciphers/trifid_cipher.py
@@ -22,7 +22,7 @@
 
 def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str:
     """
-    Arrange the triagram value of each letter of 'message_part' vertically and join
+    Arrange the triagram value of each letter of `message_part` vertically and join
     them horizontally.
 
     >>> __encrypt_part('ASK', TEST_CHARACTER_TO_NUMBER)
@@ -65,8 +65,8 @@ def __prepare(
     """
     A helper function that generates the triagrams and assigns each letter of the
     alphabet to its corresponding triagram and stores this in a dictionary
-    ("character_to_number" and "number_to_character") after confirming if the
-    alphabet's length is 27.
+    (`character_to_number` and `number_to_character`) after confirming if the
+    alphabet's length is ``27``.
 
     >>> test = __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxYZ+')
     >>> expected = ('IAMABOY','ABCDEFGHIJKLMNOPQRSTUVWXYZ+',
@@ -75,24 +75,28 @@ def __prepare(
     True
 
     Testing with incomplete alphabet
+
     >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVw')
     Traceback (most recent call last):
         ...
     KeyError: 'Length of alphabet has to be 27.'
 
     Testing with extra long alphabets
+
     >>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxyzzwwtyyujjgfd')
     Traceback (most recent call last):
         ...
     KeyError: 'Length of alphabet has to be 27.'
 
     Testing with punctuations that are not in the given alphabet
+
     >>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+')
     Traceback (most recent call last):
         ...
     ValueError: Each message character has to be included in alphabet!
 
     Testing with numbers
+
     >>> __prepare(500,'abCdeFghijkLmnopqrStuVwxYZ+')
     Traceback (most recent call last):
         ...
@@ -130,9 +134,9 @@ def encrypt_message(
     PARAMETERS
     ----------
 
-    *   message: The message you want to encrypt.
-    *   alphabet (optional): The characters to be used for the cipher .
-    *   period (optional): The number of characters you want in a group whilst
+    *   `message`: The message you want to encrypt.
+    *   `alphabet` (optional): The characters to be used for the cipher .
+    *   `period` (optional): The number of characters you want in a group whilst
         encrypting.
 
     >>> encrypt_message('I am a boy')
@@ -169,20 +173,21 @@ def decrypt_message(
     decrypt_message
     ===============
 
-    Decrypts a trifid_cipher encrypted message .
+    Decrypts a trifid_cipher encrypted message.
 
     PARAMETERS
     ----------
 
-    *   message: The message you want to decrypt .
-    *   alphabet (optional): The characters used for the cipher.
-    *   period (optional): The number of characters used in grouping when it
+    *   `message`: The message you want to decrypt.
+    *   `alphabet` (optional): The characters used for the cipher.
+    *   `period` (optional): The number of characters used in grouping when it
         was encrypted.
 
     >>> decrypt_message('BCDGBQY')
     'IAMABOY'
 
     Decrypting with your own alphabet and period
+
     >>> decrypt_message('FMJFVOISSUFTFPUFEQQC','FELIXMARDSTBCGHJKNOPQUVWYZ+',5)
     'AIDETOILECIELTAIDERA'
     """

From 68b4c6b4793867126f71ebf2a399402b02472edb Mon Sep 17 00:00:00 2001
From: mahdi tavasoli <meyttavasoli@gmail.com>
Date: Mon, 30 Dec 2024 13:52:20 +0330
Subject: [PATCH 033/135] fix is_ip_v4_address_valid.py (#12394)

* fix is_ip_v4_address_valid

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update is_ip_v4_address_valid.py

---------

Co-authored-by: m.tavasoli <mehdi.sherkattavasoli@basalam.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 maths/is_ip_v4_address_valid.py | 37 +++++++++++++++++++++++++--------
 1 file changed, 28 insertions(+), 9 deletions(-)

diff --git a/maths/is_ip_v4_address_valid.py b/maths/is_ip_v4_address_valid.py
index 0ae8e021ead1..305afabffed3 100644
--- a/maths/is_ip_v4_address_valid.py
+++ b/maths/is_ip_v4_address_valid.py
@@ -1,13 +1,15 @@
 """
+wiki: https://en.wikipedia.org/wiki/IPv4
+
 Is IP v4 address valid?
 A valid IP address must be four octets in the form of A.B.C.D,
-where A,B,C and D are numbers from 0-254
-for example: 192.168.23.1, 172.254.254.254 are valid IP address
-             192.168.255.0, 255.192.3.121 are invalid IP address
+where A, B, C and D are numbers from 0-255
+for example: 192.168.23.1, 172.255.255.255 are valid IP address
+             192.168.256.0, 256.192.3.121 are invalid IP address
 """
 
 
-def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
+def is_ip_v4_address_valid(ip: str) -> bool:
     """
     print "Valid IP address" If IP is valid.
     or
@@ -16,13 +18,13 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
     >>> is_ip_v4_address_valid("192.168.0.23")
     True
 
-    >>> is_ip_v4_address_valid("192.255.15.8")
+    >>> is_ip_v4_address_valid("192.256.15.8")
     False
 
     >>> is_ip_v4_address_valid("172.100.0.8")
     True
 
-    >>> is_ip_v4_address_valid("254.255.0.255")
+    >>> is_ip_v4_address_valid("255.256.0.256")
     False
 
     >>> is_ip_v4_address_valid("1.2.33333333.4")
@@ -45,12 +47,29 @@ def is_ip_v4_address_valid(ip_v4_address: str) -> bool:
 
     >>> is_ip_v4_address_valid("1.2.3.")
     False
+
+    >>> is_ip_v4_address_valid("1.2.3.05")
+    False
     """
-    octets = [int(i) for i in ip_v4_address.split(".") if i.isdigit()]
-    return len(octets) == 4 and all(0 <= int(octet) <= 254 for octet in octets)
+    octets = ip.split(".")
+    if len(octets) != 4:
+        return False
+
+    for octet in octets:
+        if not octet.isdigit():
+            return False
+
+        number = int(octet)
+        if len(str(number)) != len(octet):
+            return False
+
+        if not 0 <= number <= 255:
+            return False
+
+    return True
 
 
 if __name__ == "__main__":
     ip = input().strip()
     valid_or_invalid = "valid" if is_ip_v4_address_valid(ip) else "invalid"
-    print(f"{ip} is a {valid_or_invalid} IP v4 address.")
+    print(f"{ip} is a {valid_or_invalid} IPv4 address.")

From 2ca96b7c8ec2134a7282bed13f1cc93358c13c45 Mon Sep 17 00:00:00 2001
From: jperezr <122382210+MRJPEREZR@users.noreply.github.com>
Date: Mon, 30 Dec 2024 11:37:21 +0100
Subject: [PATCH 034/135] current_stock_price test added (#12390)

* adding test to web_programming/current_stock_price

* adding test to web_programming/current_stock_price

* Update current_stock_price.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update current_stock_price.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 web_programming/current_stock_price.py | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py
index 9567c05b0558..d0a65e9aac84 100644
--- a/web_programming/current_stock_price.py
+++ b/web_programming/current_stock_price.py
@@ -1,14 +1,30 @@
 import requests
 from bs4 import BeautifulSoup
 
+"""
+Get the HTML code of finance yahoo and select the current qsp-price
+Current AAPL stock price is   228.43
+Current AMZN stock price is   201.85
+Current IBM  stock price is   210.30
+Current GOOG stock price is   177.86
+Current MSFT stock price is   414.82
+Current ORCL stock price is   188.87
+"""
+
 
 def stock_price(symbol: str = "AAPL") -> str:
+    """
+    >>> stock_price("EEEE")
+    '-'
+    >>> isinstance(float(stock_price("GOOG")),float)
+    True
+    """
     url = f"https://finance.yahoo.com/quote/{symbol}?p={symbol}"
     yahoo_finance_source = requests.get(
         url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10
     ).text
     soup = BeautifulSoup(yahoo_finance_source, "html.parser")
-    specific_fin_streamer_tag = soup.find("fin-streamer", {"data-test": "qsp-price"})
+    specific_fin_streamer_tag = soup.find("fin-streamer", {"data-testid": "qsp-price"})
 
     if specific_fin_streamer_tag:
         text = specific_fin_streamer_tag.get_text()
@@ -18,5 +34,9 @@ def stock_price(symbol: str = "AAPL") -> str:
 
 # Search for the symbol at https://finance.yahoo.com/lookup
 if __name__ == "__main__":
+    from doctest import testmod
+
+    testmod()
+
     for symbol in "AAPL AMZN IBM GOOG MSFT ORCL".split():
         print(f"Current {symbol:<4} stock price is {stock_price(symbol):>8}")

From 24923ee635973a05f7713dd672fea07361fa0466 Mon Sep 17 00:00:00 2001
From: Shikhar Maheshwari <83123897+shikhar-sm@users.noreply.github.com>
Date: Mon, 30 Dec 2024 16:21:10 +0530
Subject: [PATCH 035/135] Add doctest to
 maths/numerical_analysis/intersection.py (#12148)

---
 maths/numerical_analysis/intersection.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/maths/numerical_analysis/intersection.py b/maths/numerical_analysis/intersection.py
index 826c0ead0a00..325abeaca996 100644
--- a/maths/numerical_analysis/intersection.py
+++ b/maths/numerical_analysis/intersection.py
@@ -42,6 +42,11 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl
 
 
 def f(x: float) -> float:
+    """
+    function is f(x) = x^3 - 2x - 5
+    >>> f(2)
+    -1.0
+    """
     return math.pow(x, 3) - (2 * x) - 5
 
 

From da587d06ac88e338e7db8f10fa8ca2ae556e7bae Mon Sep 17 00:00:00 2001
From: Sankabapur <152031570+Parthjhalani07@users.noreply.github.com>
Date: Mon, 30 Dec 2024 16:29:03 +0530
Subject: [PATCH 036/135] Added doctest to /maths/power_using_recursion.py
 (#11994)

---
 maths/power_using_recursion.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/maths/power_using_recursion.py b/maths/power_using_recursion.py
index 29283ca0f67c..eb775b161ae8 100644
--- a/maths/power_using_recursion.py
+++ b/maths/power_using_recursion.py
@@ -38,6 +38,14 @@ def power(base: int, exponent: int) -> float:
     Traceback (most recent call last):
         ...
     RecursionError: maximum recursion depth exceeded
+    >>> power(0, 0)
+    1
+    >>> power(0, 1)
+    0
+    >>> power(5,6)
+    15625
+    >>> power(23, 12)
+    21914624432020321
     """
     return base * power(base, (exponent - 1)) if exponent else 1
 

From 493a7c153c1ca1805e60c109842ee1a1ee63cde2 Mon Sep 17 00:00:00 2001
From: Jeel Rupapara <zeelrupapara@gmail.com>
Date: Mon, 30 Dec 2024 16:40:44 +0530
Subject: [PATCH 037/135] feat: add testcase of assemble_transformation
 (#11810)

---
 strings/min_cost_string_conversion.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/strings/min_cost_string_conversion.py b/strings/min_cost_string_conversion.py
index 93791e2a7ed3..87eb5189e16a 100644
--- a/strings/min_cost_string_conversion.py
+++ b/strings/min_cost_string_conversion.py
@@ -91,6 +91,14 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]:
     >>> y1 = len(ops1[0]) - 1
     >>> assemble_transformation(ops1, x1, y1)
     []
+
+    >>> ops2 = [['0', 'I1', 'I2', 'I3'],
+    ...         ['D1', 'C1', 'I2', 'I3'],
+    ...         ['D2', 'D2', 'R23', 'R23']]
+    >>> x2 = len(ops2) - 1
+    >>> y2 = len(ops2[0]) - 1
+    >>> assemble_transformation(ops2, x2, y2)
+    ['C1', 'I2', 'R23']
     """
     if i == 0 and j == 0:
         return []

From 7fa9b4bf1bc9822517bb0046aebc2e8b2997d3e1 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 30 Dec 2024 14:52:03 +0300
Subject: [PATCH 038/135] Fix sphinx/build_docs warnings for
 dynamic_programming (#12484)

* Fix sphinx/build_docs warnings for dynamic_programming

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 dynamic_programming/all_construct.py          |   7 +-
 dynamic_programming/combination_sum_iv.py     |  23 +-
 dynamic_programming/fizz_buzz.py              |  11 +-
 dynamic_programming/knapsack.py               |  40 ++--
 .../longest_common_substring.py               |  14 +-
 .../longest_increasing_subsequence.py         |  13 +-
 .../matrix_chain_multiplication.py            |  89 ++++----
 dynamic_programming/max_product_subarray.py   |   3 +-
 .../minimum_squares_to_represent_a_number.py  |   1 +
 dynamic_programming/regex_match.py            |  22 +-
 dynamic_programming/rod_cutting.py            |  83 ++++---
 dynamic_programming/subset_generation.py      |  63 +++---
 dynamic_programming/viterbi.py                | 212 ++++++++----------
 13 files changed, 295 insertions(+), 286 deletions(-)

diff --git a/dynamic_programming/all_construct.py b/dynamic_programming/all_construct.py
index 5d585fc7fcec..ca00f2beb06a 100644
--- a/dynamic_programming/all_construct.py
+++ b/dynamic_programming/all_construct.py
@@ -8,9 +8,10 @@
 
 def all_construct(target: str, word_bank: list[str] | None = None) -> list[list[str]]:
     """
-        returns the list containing all the possible
-        combinations a string(target) can be constructed from
-        the given list of substrings(word_bank)
+    returns the list containing all the possible
+    combinations a string(`target`) can be constructed from
+    the given list of substrings(`word_bank`)
+
     >>> all_construct("hello", ["he", "l", "o"])
     [['he', 'l', 'l', 'o']]
     >>> all_construct("purple",["purp","p","ur","le","purpl"])
diff --git a/dynamic_programming/combination_sum_iv.py b/dynamic_programming/combination_sum_iv.py
index 113c06a27a9e..ed8dcd88e6fd 100644
--- a/dynamic_programming/combination_sum_iv.py
+++ b/dynamic_programming/combination_sum_iv.py
@@ -1,24 +1,25 @@
 """
 Question:
-You are given an array of distinct integers and you have to tell how many
-different ways of selecting the elements from the array are there such that
-the sum of chosen elements is equal to the target number tar.
+    You are given an array of distinct integers and you have to tell how many
+    different ways of selecting the elements from the array are there such that
+    the sum of chosen elements is equal to the target number tar.
 
 Example
 
 Input:
-N = 3
-target = 5
-array = [1, 2, 5]
+    * N = 3
+    * target = 5
+    * array = [1, 2, 5]
 
 Output:
-9
+    9
 
 Approach:
-The basic idea is to go over recursively to find the way such that the sum
-of chosen elements is “tar”. For every element, we have two choices
-    1. Include the element in our set of chosen elements.
-    2. Don't include the element in our set of chosen elements.
+    The basic idea is to go over recursively to find the way such that the sum
+    of chosen elements is `target`. For every element, we have two choices
+
+        1. Include the element in our set of chosen elements.
+        2. Don't include the element in our set of chosen elements.
 """
 
 
diff --git a/dynamic_programming/fizz_buzz.py b/dynamic_programming/fizz_buzz.py
index e29116437a93..0cb48897875b 100644
--- a/dynamic_programming/fizz_buzz.py
+++ b/dynamic_programming/fizz_buzz.py
@@ -3,11 +3,12 @@
 
 def fizz_buzz(number: int, iterations: int) -> str:
     """
-    Plays FizzBuzz.
-    Prints Fizz if number is a multiple of 3.
-    Prints Buzz if its a multiple of 5.
-    Prints FizzBuzz if its a multiple of both 3 and 5 or 15.
-    Else Prints The Number Itself.
+    | Plays FizzBuzz.
+    | Prints Fizz if number is a multiple of ``3``.
+    | Prints Buzz if its a multiple of ``5``.
+    | Prints FizzBuzz if its a multiple of both ``3`` and ``5`` or ``15``.
+    | Else Prints The Number Itself.
+
     >>> fizz_buzz(1,7)
     '1 2 Fizz 4 Buzz Fizz 7 '
     >>> fizz_buzz(1,0)
diff --git a/dynamic_programming/knapsack.py b/dynamic_programming/knapsack.py
index 489b5ada450a..28c5b19dbe36 100644
--- a/dynamic_programming/knapsack.py
+++ b/dynamic_programming/knapsack.py
@@ -11,7 +11,7 @@ def mf_knapsack(i, wt, val, j):
     """
     This code involves the concept of memory functions. Here we solve the subproblems
     which are needed unlike the below example
-    F is a 2D array with -1s filled up
+    F is a 2D array with ``-1`` s filled up
     """
     global f  # a global dp table for knapsack
     if f[i][j] < 0:
@@ -45,22 +45,24 @@ def knapsack_with_example_solution(w: int, wt: list, val: list):
     the several possible optimal subsets.
 
     Parameters
-    ---------
+    ----------
 
-    W: int, the total maximum weight for the given knapsack problem.
-    wt: list, the vector of weights for all items where wt[i] is the weight
-    of the i-th item.
-    val: list, the vector of values for all items where val[i] is the value
-    of the i-th item
+    * `w`: int, the total maximum weight for the given knapsack problem.
+    * `wt`: list, the vector of weights for all items where ``wt[i]`` is the weight
+       of the ``i``-th item.
+    * `val`: list, the vector of values for all items where ``val[i]`` is the value
+      of the ``i``-th item
 
     Returns
     -------
-    optimal_val: float, the optimal value for the given knapsack problem
-    example_optional_set: set, the indices of one of the optimal subsets
-    which gave rise to the optimal value.
+
+    * `optimal_val`: float, the optimal value for the given knapsack problem
+    * `example_optional_set`: set, the indices of one of the optimal subsets
+      which gave rise to the optimal value.
 
     Examples
-    -------
+    --------
+
     >>> knapsack_with_example_solution(10, [1, 3, 5, 2], [10, 20, 100, 22])
     (142, {2, 3, 4})
     >>> knapsack_with_example_solution(6, [4, 3, 2, 3], [3, 2, 4, 4])
@@ -104,19 +106,19 @@ def _construct_solution(dp: list, wt: list, i: int, j: int, optimal_set: set):
     a filled DP table and the vector of weights
 
     Parameters
-    ---------
-
-    dp: list of list, the table of a solved integer weight dynamic programming problem
+    ----------
 
-    wt: list or tuple, the vector of weights of the items
-    i: int, the index of the item under consideration
-    j: int, the current possible maximum weight
-    optimal_set: set, the optimal subset so far. This gets modified by the function.
+    * `dp`: list of list, the table of a solved integer weight dynamic programming
+      problem
+    * `wt`: list or tuple, the vector of weights of the items
+    * `i`: int, the index of the item under consideration
+    * `j`: int, the current possible maximum weight
+    * `optimal_set`: set, the optimal subset so far. This gets modified by the function.
 
     Returns
     -------
-    None
 
+    ``None``
     """
     # for the current item i at a maximum weight j to be part of an optimal subset,
     # the optimal value at (i, j) must be greater than the optimal value at (i-1, j).
diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py
index e2f944a5e336..ea5233eb2d17 100644
--- a/dynamic_programming/longest_common_substring.py
+++ b/dynamic_programming/longest_common_substring.py
@@ -1,15 +1,19 @@
 """
-Longest Common Substring Problem Statement: Given two sequences, find the
-longest common substring present in both of them. A substring is
-necessarily continuous.
-Example: "abcdef" and "xabded" have two longest common substrings, "ab" or "de".
-Therefore, algorithm should return any one of them.
+Longest Common Substring Problem Statement:
+    Given two sequences, find the
+    longest common substring present in both of them. A substring is
+    necessarily continuous.
+
+Example:
+    ``abcdef`` and ``xabded`` have two longest common substrings, ``ab`` or ``de``.
+    Therefore, algorithm should return any one of them.
 """
 
 
 def longest_common_substring(text1: str, text2: str) -> str:
     """
     Finds the longest common substring between two strings.
+
     >>> longest_common_substring("", "")
     ''
     >>> longest_common_substring("a","")
diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py
index 2a78e2e7ad1d..d839757f6da5 100644
--- a/dynamic_programming/longest_increasing_subsequence.py
+++ b/dynamic_programming/longest_increasing_subsequence.py
@@ -4,11 +4,13 @@
 This is a pure Python implementation of Dynamic Programming solution to the longest
 increasing subsequence of a given sequence.
 
-The problem is  :
-Given an array, to find the longest and increasing sub-array in that given array and
-return it.
-Example: [10, 22, 9, 33, 21, 50, 41, 60, 80] as input will return
-         [10, 22, 33, 41, 60, 80] as output
+The problem is:
+    Given an array, to find the longest and increasing sub-array in that given array and
+    return it.
+
+Example:
+    ``[10, 22, 9, 33, 21, 50, 41, 60, 80]`` as input will return
+    ``[10, 22, 33, 41, 60, 80]`` as output
 """
 
 from __future__ import annotations
@@ -17,6 +19,7 @@
 def longest_subsequence(array: list[int]) -> list[int]:  # This function is recursive
     """
     Some examples
+
     >>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80])
     [10, 22, 33, 41, 60, 80]
     >>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9])
diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py
index da6e525ce816..10e136b9f0db 100644
--- a/dynamic_programming/matrix_chain_multiplication.py
+++ b/dynamic_programming/matrix_chain_multiplication.py
@@ -1,42 +1,48 @@
 """
-Find the minimum number of multiplications needed to multiply chain of matrices.
-Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/
+| Find the minimum number of multiplications needed to multiply chain of matrices.
+| Reference: https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/
 
-The algorithm has interesting real-world applications. Example:
-1. Image transformations in Computer Graphics as images are composed of matrix.
-2. Solve complex polynomial equations in the field of algebra using least processing
-   power.
-3. Calculate overall impact of macroeconomic decisions as economic equations involve a
-   number of variables.
-4. Self-driving car navigation can be made more accurate as matrix multiplication can
-   accurately determine position and orientation of obstacles in short time.
+The algorithm has interesting real-world applications.
 
-Python doctests can be run with the following command:
-python -m doctest -v matrix_chain_multiply.py
+Example:
+  1. Image transformations in Computer Graphics as images are composed of matrix.
+  2. Solve complex polynomial equations in the field of algebra using least processing
+     power.
+  3. Calculate overall impact of macroeconomic decisions as economic equations involve a
+     number of variables.
+  4. Self-driving car navigation can be made more accurate as matrix multiplication can
+     accurately determine position and orientation of obstacles in short time.
 
-Given a sequence arr[] that represents chain of 2D matrices such that the dimension of
-the ith matrix is arr[i-1]*arr[i].
-So suppose arr = [40, 20, 30, 10, 30] means we have 4 matrices of dimensions
-40*20, 20*30, 30*10 and 10*30.
+Python doctests can be run with the following command::
 
-matrix_chain_multiply() returns an integer denoting minimum number of multiplications to
-multiply the chain.
+  python -m doctest -v matrix_chain_multiply.py
+
+Given a sequence ``arr[]`` that represents chain of 2D matrices such that the dimension
+of the ``i`` th matrix is ``arr[i-1]*arr[i]``.
+So suppose ``arr = [40, 20, 30, 10, 30]`` means we have ``4`` matrices of dimensions
+``40*20``, ``20*30``, ``30*10`` and ``10*30``.
+
+``matrix_chain_multiply()`` returns an integer denoting minimum number of
+multiplications to multiply the chain.
 
 We do not need to perform actual multiplication here.
 We only need to decide the order in which to perform the multiplication.
 
 Hints:
-1. Number of multiplications (ie cost) to multiply 2 matrices
-of size m*p and p*n is m*p*n.
-2. Cost of matrix multiplication is associative ie (M1*M2)*M3 != M1*(M2*M3)
-3. Matrix multiplication is not commutative. So, M1*M2 does not mean M2*M1 can be done.
-4. To determine the required order, we can try different combinations.
+  1. Number of multiplications (ie cost) to multiply ``2`` matrices
+     of size ``m*p`` and ``p*n`` is ``m*p*n``.
+  2. Cost of matrix multiplication is not associative ie ``(M1*M2)*M3 != M1*(M2*M3)``
+  3. Matrix multiplication is not commutative. So, ``M1*M2`` does not mean ``M2*M1``
+     can be done.
+  4. To determine the required order, we can try different combinations.
+
 So, this problem has overlapping sub-problems and can be solved using recursion.
 We use Dynamic Programming for optimal time complexity.
 
 Example input:
-arr = [40, 20, 30, 10, 30]
-output: 26000
+    ``arr = [40, 20, 30, 10, 30]``
+output:
+    ``26000``
 """
 
 from collections.abc import Iterator
@@ -50,25 +56,25 @@ def matrix_chain_multiply(arr: list[int]) -> int:
     Find the minimum number of multiplcations required to multiply the chain of matrices
 
     Args:
-        arr: The input array of integers.
+        `arr`: The input array of integers.
 
     Returns:
         Minimum number of multiplications needed to multiply the chain
 
     Examples:
-        >>> matrix_chain_multiply([1, 2, 3, 4, 3])
-        30
-        >>> matrix_chain_multiply([10])
-        0
-        >>> matrix_chain_multiply([10, 20])
-        0
-        >>> matrix_chain_multiply([19, 2, 19])
-        722
-        >>> matrix_chain_multiply(list(range(1, 100)))
-        323398
-
-        # >>> matrix_chain_multiply(list(range(1, 251)))
-        # 2626798
+
+    >>> matrix_chain_multiply([1, 2, 3, 4, 3])
+    30
+    >>> matrix_chain_multiply([10])
+    0
+    >>> matrix_chain_multiply([10, 20])
+    0
+    >>> matrix_chain_multiply([19, 2, 19])
+    722
+    >>> matrix_chain_multiply(list(range(1, 100)))
+    323398
+    >>> # matrix_chain_multiply(list(range(1, 251)))
+    # 2626798
     """
     if len(arr) < 2:
         return 0
@@ -93,8 +99,10 @@ def matrix_chain_multiply(arr: list[int]) -> int:
 def matrix_chain_order(dims: list[int]) -> int:
     """
     Source: https://en.wikipedia.org/wiki/Matrix_chain_multiplication
+
     The dynamic programming solution is faster than cached the recursive solution and
     can handle larger inputs.
+
     >>> matrix_chain_order([1, 2, 3, 4, 3])
     30
     >>> matrix_chain_order([10])
@@ -105,8 +113,7 @@ def matrix_chain_order(dims: list[int]) -> int:
     722
     >>> matrix_chain_order(list(range(1, 100)))
     323398
-
-    # >>> matrix_chain_order(list(range(1, 251)))  # Max before RecursionError is raised
+    >>> # matrix_chain_order(list(range(1, 251)))  # Max before RecursionError is raised
     # 2626798
     """
 
diff --git a/dynamic_programming/max_product_subarray.py b/dynamic_programming/max_product_subarray.py
index 425859bc03e3..6f4f38e38942 100644
--- a/dynamic_programming/max_product_subarray.py
+++ b/dynamic_programming/max_product_subarray.py
@@ -1,9 +1,10 @@
 def max_product_subarray(numbers: list[int]) -> int:
     """
     Returns the maximum product that can be obtained by multiplying a
-    contiguous subarray of the given integer list `nums`.
+    contiguous subarray of the given integer list `numbers`.
 
     Example:
+
     >>> max_product_subarray([2, 3, -2, 4])
     6
     >>> max_product_subarray((-2, 0, -1))
diff --git a/dynamic_programming/minimum_squares_to_represent_a_number.py b/dynamic_programming/minimum_squares_to_represent_a_number.py
index bf5849f5bcb3..98c0602fa831 100644
--- a/dynamic_programming/minimum_squares_to_represent_a_number.py
+++ b/dynamic_programming/minimum_squares_to_represent_a_number.py
@@ -5,6 +5,7 @@
 def minimum_squares_to_represent_a_number(number: int) -> int:
     """
     Count the number of minimum squares to represent a number
+
     >>> minimum_squares_to_represent_a_number(25)
     1
     >>> minimum_squares_to_represent_a_number(37)
diff --git a/dynamic_programming/regex_match.py b/dynamic_programming/regex_match.py
index 200a882831c0..e94d82093c8b 100644
--- a/dynamic_programming/regex_match.py
+++ b/dynamic_programming/regex_match.py
@@ -1,23 +1,25 @@
 """
 Regex matching check if a text matches pattern or not.
 Pattern:
-    '.' Matches any single character.
-    '*' Matches zero or more of the preceding element.
+
+    1. ``.`` Matches any single character.
+    2. ``*`` Matches zero or more of the preceding element.
+
 More info:
     https://medium.com/trick-the-interviwer/regular-expression-matching-9972eb74c03
 """
 
 
 def recursive_match(text: str, pattern: str) -> bool:
-    """
+    r"""
     Recursive matching algorithm.
 
-    Time complexity: O(2 ^ (|text| + |pattern|))
-    Space complexity: Recursion depth is O(|text| + |pattern|).
+    | Time complexity: O(2^(\|text\| + \|pattern\|))
+    | Space complexity: Recursion depth is O(\|text\| + \|pattern\|).
 
     :param text: Text to match.
     :param pattern: Pattern to match.
-    :return: True if text matches pattern, False otherwise.
+    :return: ``True`` if `text` matches `pattern`, ``False`` otherwise.
 
     >>> recursive_match('abc', 'a.c')
     True
@@ -48,15 +50,15 @@ def recursive_match(text: str, pattern: str) -> bool:
 
 
 def dp_match(text: str, pattern: str) -> bool:
-    """
+    r"""
     Dynamic programming matching algorithm.
 
-    Time complexity: O(|text| * |pattern|)
-    Space complexity: O(|text| * |pattern|)
+    | Time complexity: O(\|text\| * \|pattern\|)
+    | Space complexity: O(\|text\| * \|pattern\|)
 
     :param text: Text to match.
     :param pattern: Pattern to match.
-    :return: True if text matches pattern, False otherwise.
+    :return: ``True`` if `text` matches `pattern`, ``False`` otherwise.
 
     >>> dp_match('abc', 'a.c')
     True
diff --git a/dynamic_programming/rod_cutting.py b/dynamic_programming/rod_cutting.py
index f80fa440ae86..d12c759dc928 100644
--- a/dynamic_programming/rod_cutting.py
+++ b/dynamic_programming/rod_cutting.py
@@ -1,7 +1,7 @@
 """
 This module provides two implementations for the rod-cutting problem:
-1. A naive recursive implementation which has an exponential runtime
-2. Two dynamic programming implementations which have quadratic runtime
+  1. A naive recursive implementation which has an exponential runtime
+  2. Two dynamic programming implementations which have quadratic runtime
 
 The rod-cutting problem is the problem of finding the maximum possible revenue
 obtainable from a rod of length ``n`` given a list of prices for each integral piece
@@ -20,18 +20,21 @@ def naive_cut_rod_recursive(n: int, prices: list):
     Runtime: O(2^n)
 
     Arguments
-    -------
-    n: int, the length of the rod
-    prices: list, the prices for each piece of rod. ``p[i-i]`` is the
-    price for a rod of length ``i``
+    ---------
+
+    * `n`: int, the length of the rod
+    * `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
+      price for a rod of length ``i``
 
     Returns
     -------
-    The maximum revenue obtainable for a rod of length n given the list of prices
+
+    The maximum revenue obtainable for a rod of length `n` given the list of prices
     for each piece.
 
     Examples
     --------
+
     >>> naive_cut_rod_recursive(4, [1, 5, 8, 9])
     10
     >>> naive_cut_rod_recursive(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
@@ -54,28 +57,30 @@ def top_down_cut_rod(n: int, prices: list):
     """
     Constructs a top-down dynamic programming solution for the rod-cutting
     problem via memoization. This function serves as a wrapper for
-    _top_down_cut_rod_recursive
+    ``_top_down_cut_rod_recursive``
 
     Runtime: O(n^2)
 
     Arguments
-    --------
-    n: int, the length of the rod
-    prices: list, the prices for each piece of rod. ``p[i-i]`` is the
-    price for a rod of length ``i``
+    ---------
 
-    Note
-    ----
-    For convenience and because Python's lists using 0-indexing, length(max_rev) =
-    n + 1, to accommodate for the revenue obtainable from a rod of length 0.
+    * `n`: int, the length of the rod
+    * `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
+      price for a rod of length ``i``
+
+    .. note::
+      For convenience and because Python's lists using ``0``-indexing, ``length(max_rev)
+      = n + 1``, to accommodate for the revenue obtainable from a rod of length ``0``.
 
     Returns
     -------
-    The maximum revenue obtainable for a rod of length n given the list of prices
+
+    The maximum revenue obtainable for a rod of length `n` given the list of prices
     for each piece.
 
     Examples
-    -------
+    --------
+
     >>> top_down_cut_rod(4, [1, 5, 8, 9])
     10
     >>> top_down_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
@@ -94,16 +99,18 @@ def _top_down_cut_rod_recursive(n: int, prices: list, max_rev: list):
     Runtime: O(n^2)
 
     Arguments
-    --------
-    n: int, the length of the rod
-    prices: list, the prices for each piece of rod. ``p[i-i]`` is the
-    price for a rod of length ``i``
-    max_rev: list, the computed maximum revenue for a piece of rod.
-    ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i``
+    ---------
+
+    * `n`: int, the length of the rod
+    * `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
+      price for a rod of length ``i``
+    * `max_rev`: list, the computed maximum revenue for a piece of rod.
+      ``max_rev[i]`` is the maximum revenue obtainable for a rod of length ``i``
 
     Returns
     -------
-    The maximum revenue obtainable for a rod of length n given the list of prices
+
+    The maximum revenue obtainable for a rod of length `n` given the list of prices
     for each piece.
     """
     if max_rev[n] >= 0:
@@ -130,18 +137,21 @@ def bottom_up_cut_rod(n: int, prices: list):
     Runtime: O(n^2)
 
     Arguments
-    ----------
-    n: int, the maximum length of the rod.
-    prices: list, the prices for each piece of rod. ``p[i-i]`` is the
-    price for a rod of length ``i``
+    ---------
+
+    * `n`: int, the maximum length of the rod.
+    * `prices`: list, the prices for each piece of rod. ``p[i-i]`` is the
+      price for a rod of length ``i``
 
     Returns
     -------
-    The maximum revenue obtainable from cutting a rod of length n given
+
+    The maximum revenue obtainable from cutting a rod of length `n` given
     the prices for each piece of rod p.
 
     Examples
-    -------
+    --------
+
     >>> bottom_up_cut_rod(4, [1, 5, 8, 9])
     10
     >>> bottom_up_cut_rod(10, [1, 5, 8, 9, 10, 17, 17, 20, 24, 30])
@@ -168,13 +178,12 @@ def _enforce_args(n: int, prices: list):
     """
     Basic checks on the arguments to the rod-cutting algorithms
 
-    n: int, the length of the rod
-    prices: list, the price list for each piece of rod.
-
-    Throws ValueError:
+    * `n`: int, the length of the rod
+    * `prices`: list, the price list for each piece of rod.
 
-    if n is negative or there are fewer items in the price list than the length of
-    the rod
+    Throws ``ValueError``:
+        if `n` is negative or there are fewer items in the price list than the length of
+        the rod
     """
     if n < 0:
         msg = f"n must be greater than or equal to 0. Got n = {n}"
diff --git a/dynamic_programming/subset_generation.py b/dynamic_programming/subset_generation.py
index d490bca737ba..08daaac6f88a 100644
--- a/dynamic_programming/subset_generation.py
+++ b/dynamic_programming/subset_generation.py
@@ -1,38 +1,41 @@
 def subset_combinations(elements: list[int], n: int) -> list:
     """
     Compute n-element combinations from a given list using dynamic programming.
+
     Args:
-        elements: The list of elements from which combinations will be generated.
-        n: The number of elements in each combination.
+        * `elements`: The list of elements from which combinations will be generated.
+        * `n`: The number of elements in each combination.
+
     Returns:
-        A list of tuples, each representing a combination of n elements.
-        >>> subset_combinations(elements=[10, 20, 30, 40], n=2)
-        [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)]
-        >>> subset_combinations(elements=[1, 2, 3], n=1)
-        [(1,), (2,), (3,)]
-        >>> subset_combinations(elements=[1, 2, 3], n=3)
-        [(1, 2, 3)]
-        >>> subset_combinations(elements=[42], n=1)
-        [(42,)]
-        >>> subset_combinations(elements=[6, 7, 8, 9], n=4)
-        [(6, 7, 8, 9)]
-        >>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0)
-        [()]
-        >>> subset_combinations(elements=[1, 2, 3, 4], n=2)
-        [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
-        >>> subset_combinations(elements=[1, 'apple', 3.14], n=2)
-        [(1, 'apple'), (1, 3.14), ('apple', 3.14)]
-        >>> subset_combinations(elements=['single'], n=0)
-        [()]
-        >>> subset_combinations(elements=[], n=9)
-        []
-        >>> from itertools import combinations
-        >>> all(subset_combinations(items, n) == list(combinations(items, n))
-        ...     for items, n in (
-        ...         ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1),
-        ...         ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2),
-        ...         ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9)))
-        True
+        A list of tuples, each representing a combination of `n` elements.
+
+    >>> subset_combinations(elements=[10, 20, 30, 40], n=2)
+    [(10, 20), (10, 30), (10, 40), (20, 30), (20, 40), (30, 40)]
+    >>> subset_combinations(elements=[1, 2, 3], n=1)
+    [(1,), (2,), (3,)]
+    >>> subset_combinations(elements=[1, 2, 3], n=3)
+    [(1, 2, 3)]
+    >>> subset_combinations(elements=[42], n=1)
+    [(42,)]
+    >>> subset_combinations(elements=[6, 7, 8, 9], n=4)
+    [(6, 7, 8, 9)]
+    >>> subset_combinations(elements=[10, 20, 30, 40, 50], n=0)
+    [()]
+    >>> subset_combinations(elements=[1, 2, 3, 4], n=2)
+    [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
+    >>> subset_combinations(elements=[1, 'apple', 3.14], n=2)
+    [(1, 'apple'), (1, 3.14), ('apple', 3.14)]
+    >>> subset_combinations(elements=['single'], n=0)
+    [()]
+    >>> subset_combinations(elements=[], n=9)
+    []
+    >>> from itertools import combinations
+    >>> all(subset_combinations(items, n) == list(combinations(items, n))
+    ...     for items, n in (
+    ...         ([10, 20, 30, 40], 2), ([1, 2, 3], 1), ([1, 2, 3], 3), ([42], 1),
+    ...         ([6, 7, 8, 9], 4), ([10, 20, 30, 40, 50], 1), ([1, 2, 3, 4], 2),
+    ...         ([1, 'apple', 3.14], 2), (['single'], 0), ([], 9)))
+    True
     """
     r = len(elements)
     if n > r:
diff --git a/dynamic_programming/viterbi.py b/dynamic_programming/viterbi.py
index 764d45dc2c05..5b78fa9e46d0 100644
--- a/dynamic_programming/viterbi.py
+++ b/dynamic_programming/viterbi.py
@@ -9,119 +9,102 @@ def viterbi(
     emission_probabilities: dict,
 ) -> list:
     """
-        Viterbi Algorithm, to find the most likely path of
-        states from the start and the expected output.
-        https://en.wikipedia.org/wiki/Viterbi_algorithm
-    sdafads
-        Wikipedia example
-        >>> observations = ["normal", "cold", "dizzy"]
-        >>> states = ["Healthy", "Fever"]
-        >>> start_p = {"Healthy": 0.6, "Fever": 0.4}
-        >>> trans_p = {
-        ...     "Healthy": {"Healthy": 0.7, "Fever": 0.3},
-        ...     "Fever": {"Healthy": 0.4, "Fever": 0.6},
-        ... }
-        >>> emit_p = {
-        ...     "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1},
-        ...     "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
-        ... }
-        >>> viterbi(observations, states, start_p, trans_p, emit_p)
-        ['Healthy', 'Healthy', 'Fever']
+    Viterbi Algorithm, to find the most likely path of
+    states from the start and the expected output.
 
-        >>> viterbi((), states, start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
-
-        >>> viterbi(observations, (), start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
-
-        >>> viterbi(observations, states, {}, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
-
-        >>> viterbi(observations, states, start_p, {}, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
-
-        >>> viterbi(observations, states, start_p, trans_p, {})
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
-
-        >>> viterbi("invalid", states, start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: observations_space must be a list
+    https://en.wikipedia.org/wiki/Viterbi_algorithm
 
-        >>> viterbi(["valid", 123], states, start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: observations_space must be a list of strings
-
-        >>> viterbi(observations, "invalid", start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: states_space must be a list
-
-        >>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: states_space must be a list of strings
-
-        >>> viterbi(observations, states, "invalid", trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: initial_probabilities must be a dict
-
-        >>> viterbi(observations, states, {2:2}, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: initial_probabilities all keys must be strings
-
-        >>> viterbi(observations, states, {"a":2}, trans_p, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: initial_probabilities all values must be float
+    Wikipedia example
 
-        >>> viterbi(observations, states, start_p, "invalid", emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: transition_probabilities must be a dict
-
-        >>> viterbi(observations, states, start_p, {"a":2}, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: transition_probabilities all values must be dict
-
-        >>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: transition_probabilities all keys must be strings
-
-        >>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: transition_probabilities all keys must be strings
-
-        >>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p)
-        Traceback (most recent call last):
-            ...
-        ValueError: transition_probabilities nested dictionary all values must be float
-
-        >>> viterbi(observations, states, start_p, trans_p, "invalid")
-        Traceback (most recent call last):
-            ...
-        ValueError: emission_probabilities must be a dict
-
-        >>> viterbi(observations, states, start_p, trans_p, None)
-        Traceback (most recent call last):
-            ...
-        ValueError: There's an empty parameter
+    >>> observations = ["normal", "cold", "dizzy"]
+    >>> states = ["Healthy", "Fever"]
+    >>> start_p = {"Healthy": 0.6, "Fever": 0.4}
+    >>> trans_p = {
+    ...     "Healthy": {"Healthy": 0.7, "Fever": 0.3},
+    ...     "Fever": {"Healthy": 0.4, "Fever": 0.6},
+    ... }
+    >>> emit_p = {
+    ...     "Healthy": {"normal": 0.5, "cold": 0.4, "dizzy": 0.1},
+    ...     "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
+    ... }
+    >>> viterbi(observations, states, start_p, trans_p, emit_p)
+    ['Healthy', 'Healthy', 'Fever']
+    >>> viterbi((), states, start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
+    >>> viterbi(observations, (), start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
+    >>> viterbi(observations, states, {}, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
+    >>> viterbi(observations, states, start_p, {}, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
+    >>> viterbi(observations, states, start_p, trans_p, {})
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
+    >>> viterbi("invalid", states, start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: observations_space must be a list
+    >>> viterbi(["valid", 123], states, start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: observations_space must be a list of strings
+    >>> viterbi(observations, "invalid", start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: states_space must be a list
+    >>> viterbi(observations, ["valid", 123], start_p, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: states_space must be a list of strings
+    >>> viterbi(observations, states, "invalid", trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: initial_probabilities must be a dict
+    >>> viterbi(observations, states, {2:2}, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: initial_probabilities all keys must be strings
+    >>> viterbi(observations, states, {"a":2}, trans_p, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: initial_probabilities all values must be float
+    >>> viterbi(observations, states, start_p, "invalid", emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: transition_probabilities must be a dict
+    >>> viterbi(observations, states, start_p, {"a":2}, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: transition_probabilities all values must be dict
+    >>> viterbi(observations, states, start_p, {2:{2:2}}, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: transition_probabilities all keys must be strings
+    >>> viterbi(observations, states, start_p, {"a":{2:2}}, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: transition_probabilities all keys must be strings
+    >>> viterbi(observations, states, start_p, {"a":{"b":2}}, emit_p)
+    Traceback (most recent call last):
+        ...
+    ValueError: transition_probabilities nested dictionary all values must be float
+    >>> viterbi(observations, states, start_p, trans_p, "invalid")
+    Traceback (most recent call last):
+        ...
+    ValueError: emission_probabilities must be a dict
+    >>> viterbi(observations, states, start_p, trans_p, None)
+    Traceback (most recent call last):
+        ...
+    ValueError: There's an empty parameter
 
     """
     _validation(
@@ -213,7 +196,6 @@ def _validation(
     ...     "Fever": {"normal": 0.1, "cold": 0.3, "dizzy": 0.6},
     ... }
     >>> _validation(observations, states, start_p, trans_p, emit_p)
-
     >>> _validation([], states, start_p, trans_p, emit_p)
     Traceback (most recent call last):
             ...
@@ -242,7 +224,6 @@ def _validate_not_empty(
     """
     >>> _validate_not_empty(["a"], ["b"], {"c":0.5},
     ... {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
-
     >>> _validate_not_empty(["a"], ["b"], {"c":0.5}, {}, {"f": {"g": 0.7}})
     Traceback (most recent call last):
             ...
@@ -267,12 +248,10 @@ def _validate_not_empty(
 def _validate_lists(observations_space: Any, states_space: Any) -> None:
     """
     >>> _validate_lists(["a"], ["b"])
-
     >>> _validate_lists(1234, ["b"])
     Traceback (most recent call last):
             ...
     ValueError: observations_space must be a list
-
     >>> _validate_lists(["a"], [3])
     Traceback (most recent call last):
             ...
@@ -285,7 +264,6 @@ def _validate_lists(observations_space: Any, states_space: Any) -> None:
 def _validate_list(_object: Any, var_name: str) -> None:
     """
     >>> _validate_list(["a"], "mock_name")
-
     >>> _validate_list("a", "mock_name")
     Traceback (most recent call last):
             ...
@@ -294,7 +272,6 @@ def _validate_list(_object: Any, var_name: str) -> None:
     Traceback (most recent call last):
             ...
     ValueError: mock_name must be a list of strings
-
     """
     if not isinstance(_object, list):
         msg = f"{var_name} must be a list"
@@ -313,7 +290,6 @@ def _validate_dicts(
 ) -> None:
     """
     >>> _validate_dicts({"c":0.5}, {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
-
     >>> _validate_dicts("invalid", {"d": {"e": 0.6}}, {"f": {"g": 0.7}})
     Traceback (most recent call last):
             ...
@@ -339,7 +315,6 @@ def _validate_dicts(
 def _validate_nested_dict(_object: Any, var_name: str) -> None:
     """
     >>> _validate_nested_dict({"a":{"b": 0.5}}, "mock_name")
-
     >>> _validate_nested_dict("invalid", "mock_name")
     Traceback (most recent call last):
             ...
@@ -367,7 +342,6 @@ def _validate_dict(
 ) -> None:
     """
     >>> _validate_dict({"b": 0.5}, "mock_name", float)
-
     >>> _validate_dict("invalid", "mock_name", float)
     Traceback (most recent call last):
             ...

From a2be5adf673c32720bbfb649c368af8fd7ae9dbb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julia=20Arag=C3=A3o?=
 <101305675+juliaaragao@users.noreply.github.com>
Date: Mon, 30 Dec 2024 13:36:55 +0100
Subject: [PATCH 039/135] Tests electronics/electric_conductivity.py  #9943
 (#12437)

* Function conversion rectangular number to polar

* #9943 : adding test to elelectronics/electric_conductivity.py

* updating DIRECTORY.md

* Apply suggestions from code review

* updating DIRECTORY.md

* Rename rec_to_pol.py to rectangular_to_polar.py

* updating DIRECTORY.md

* Update conversions/rectangular_to_polar.py

* Update conversions/rectangular_to_polar.py

---------

Co-authored-by: Julia <julia.de-jesus-aragao@imt-atlantique.net>
Co-authored-by: juliaaragao <juliaaragao@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: cclauss <cclauss@users.noreply.github.com>
---
 DIRECTORY.md                         |  1 +
 conversions/rectangular_to_polar.py  | 32 ++++++++++++++++++++++++++++
 electronics/electric_conductivity.py | 20 +++++++++++++++++
 3 files changed, 53 insertions(+)
 create mode 100644 conversions/rectangular_to_polar.py

diff --git a/DIRECTORY.md b/DIRECTORY.md
index d234d366df06..44d0414a37c8 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -169,6 +169,7 @@
   * [Prefix Conversions](conversions/prefix_conversions.py)
   * [Prefix Conversions String](conversions/prefix_conversions_string.py)
   * [Pressure Conversions](conversions/pressure_conversions.py)
+  * [Rectangular To Polar](conversions/rectangular_to_polar.py)
   * [Rgb Cmyk Conversion](conversions/rgb_cmyk_conversion.py)
   * [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py)
   * [Roman Numerals](conversions/roman_numerals.py)
diff --git a/conversions/rectangular_to_polar.py b/conversions/rectangular_to_polar.py
new file mode 100644
index 000000000000..bed97d7410ec
--- /dev/null
+++ b/conversions/rectangular_to_polar.py
@@ -0,0 +1,32 @@
+import math
+
+
+def rectangular_to_polar(real: float, img: float) -> tuple[float, float]:
+    """
+    https://en.wikipedia.org/wiki/Polar_coordinate_system
+
+    >>> rectangular_to_polar(5,-5)
+    (7.07, -45.0)
+    >>> rectangular_to_polar(-1,1)
+    (1.41, 135.0)
+    >>> rectangular_to_polar(-1,-1)
+    (1.41, -135.0)
+    >>> rectangular_to_polar(1e-10,1e-10)
+    (0.0, 45.0)
+    >>> rectangular_to_polar(-1e-10,1e-10)
+    (0.0, 135.0)
+    >>> rectangular_to_polar(9.75,5.93)
+    (11.41, 31.31)
+    >>> rectangular_to_polar(10000,99999)
+    (100497.76, 84.29)
+    """
+
+    mod = round(math.sqrt((real**2) + (img**2)), 2)
+    ang = round(math.degrees(math.atan2(img, real)), 2)
+    return (mod, ang)
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
diff --git a/electronics/electric_conductivity.py b/electronics/electric_conductivity.py
index 11f2a607d214..65bb6c5ceaf0 100644
--- a/electronics/electric_conductivity.py
+++ b/electronics/electric_conductivity.py
@@ -21,6 +21,26 @@ def electric_conductivity(
     ('conductivity', 5.12672e-14)
     >>> electric_conductivity(conductivity=1000, electron_conc=0, mobility=1200)
     ('electron_conc', 5.201506356240767e+18)
+    >>> electric_conductivity(conductivity=-10, electron_conc=100, mobility=0)
+    Traceback (most recent call last):
+        ...
+    ValueError: Conductivity cannot be negative
+    >>> electric_conductivity(conductivity=50, electron_conc=-10, mobility=0)
+    Traceback (most recent call last):
+        ...
+    ValueError: Electron concentration cannot be negative
+    >>> electric_conductivity(conductivity=50, electron_conc=0, mobility=-10)
+    Traceback (most recent call last):
+        ...
+    ValueError: mobility cannot be negative
+    >>> electric_conductivity(conductivity=50, electron_conc=0, mobility=0)
+    Traceback (most recent call last):
+        ...
+    ValueError: You cannot supply more or less than 2 values
+    >>> electric_conductivity(conductivity=50, electron_conc=200, mobility=300)
+    Traceback (most recent call last):
+        ...
+    ValueError: You cannot supply more or less than 2 values
     """
     if (conductivity, electron_conc, mobility).count(0) != 1:
         raise ValueError("You cannot supply more or less than 2 values")

From f24ddba5b2600486f7c3a4c5807cf2aeed421870 Mon Sep 17 00:00:00 2001
From: Matej <83732219+IsxImattI@users.noreply.github.com>
Date: Mon, 30 Dec 2024 16:04:28 +0100
Subject: [PATCH 040/135] Implemented doctests for geometry-related classes
 (#12368)

* Implemented doctests for geometry-related classes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Removed unused noqa directive

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* refactored sudoku_solver.py

* refactored sudoku_solver.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* context manager for file handling changed too in from_file function

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
---
 data_structures/arrays/sudoku_solver.py |  5 +++--
 geometry/geometry.py                    | 29 +++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py
index 7e38e1465728..fd1a4f3e37b8 100644
--- a/data_structures/arrays/sudoku_solver.py
+++ b/data_structures/arrays/sudoku_solver.py
@@ -23,7 +23,7 @@ def cross(items_a, items_b):
     + [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")]
 )
 units = {s: [u for u in unitlist if s in u] for s in squares}
-peers = {s: set(sum(units[s], [])) - {s} for s in squares}  # noqa: RUF017
+peers = {s: {x for u in units[s] for x in u} - {s} for s in squares}
 
 
 def test():
@@ -172,7 +172,8 @@ def unitsolved(unit):
 
 def from_file(filename, sep="\n"):
     "Parse a file into a list of strings, separated by sep."
-    return open(filename).read().strip().split(sep)
+    with open(filename) as file:
+        return file.read().strip().split(sep)
 
 
 def random_puzzle(assignments=17):
diff --git a/geometry/geometry.py b/geometry/geometry.py
index 9e353dee17a7..a0be8eb3befc 100644
--- a/geometry/geometry.py
+++ b/geometry/geometry.py
@@ -48,6 +48,18 @@ class Side:
     Side(length=5, angle=Angle(degrees=45.6), next_side=None)
     >>> Side(5, Angle(45.6), Side(1, Angle(2)))  # doctest: +ELLIPSIS
     Side(length=5, angle=Angle(degrees=45.6), next_side=Side(length=1, angle=Angle(d...
+    >>> Side(-1)
+    Traceback (most recent call last):
+        ...
+    TypeError: length must be a positive numeric value.
+    >>> Side(5, None)
+    Traceback (most recent call last):
+        ...
+    TypeError: angle must be an Angle object.
+    >>> Side(5, Angle(90), "Invalid next_side")
+    Traceback (most recent call last):
+        ...
+    TypeError: next_side must be a Side or None.
     """
 
     length: float
@@ -162,6 +174,19 @@ class Polygon:
 
     >>> Polygon()
     Polygon(sides=[])
+    >>> polygon = Polygon()
+    >>> polygon.add_side(Side(5)).get_side(0)
+    Side(length=5, angle=Angle(degrees=90), next_side=None)
+    >>> polygon.get_side(1)
+    Traceback (most recent call last):
+        ...
+    IndexError: list index out of range
+    >>> polygon.set_side(0, Side(10)).get_side(0)
+    Side(length=10, angle=Angle(degrees=90), next_side=None)
+    >>> polygon.set_side(1, Side(10))
+    Traceback (most recent call last):
+        ...
+    IndexError: list assignment index out of range
     """
 
     sides: list[Side] = field(default_factory=list)
@@ -207,6 +232,10 @@ class Rectangle(Polygon):
     30
     >>> rectangle_one.area()
     50
+    >>> Rectangle(-5, 10)
+    Traceback (most recent call last):
+        ...
+    TypeError: length must be a positive numeric value.
     """
 
     def __init__(self, short_side_length: float, long_side_length: float) -> None:

From 77425364c87908bf061ad78b770ec840086b4efb Mon Sep 17 00:00:00 2001
From: SUDO_USER <110802232+AtharvMalusare@users.noreply.github.com>
Date: Mon, 30 Dec 2024 20:42:04 +0530
Subject: [PATCH 041/135]  Intensity_based_Segmentation (#12491)

* Add files via upload

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update intensity-based_segmentation.py

* Update and rename intensity-based_segmentation.py to intensity_based_segmentation.py

* Update intensity_based_segmentation.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

* [0, 1, 1]], dtype=int32)

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .../intensity_based_segmentation.py           | 62 +++++++++++++++++++
 1 file changed, 62 insertions(+)
 create mode 100644 computer_vision/intensity_based_segmentation.py

diff --git a/computer_vision/intensity_based_segmentation.py b/computer_vision/intensity_based_segmentation.py
new file mode 100644
index 000000000000..7f2b1141acc4
--- /dev/null
+++ b/computer_vision/intensity_based_segmentation.py
@@ -0,0 +1,62 @@
+# Source: "https://www.ijcse.com/docs/IJCSE11-02-03-117.pdf"
+
+# Importing necessary libraries
+import matplotlib.pyplot as plt
+import numpy as np
+from PIL import Image
+
+
+def segment_image(image: np.ndarray, thresholds: list[int]) -> np.ndarray:
+    """
+    Performs image segmentation based on intensity thresholds.
+
+    Args:
+        image: Input grayscale image as a 2D array.
+        thresholds: Intensity thresholds to define segments.
+
+    Returns:
+        A labeled 2D array where each region corresponds to a threshold range.
+
+    Example:
+        >>> img = np.array([[80, 120, 180], [40, 90, 150], [20, 60, 100]])
+        >>> segment_image(img, [50, 100, 150])
+        array([[1, 2, 3],
+               [0, 1, 2],
+               [0, 1, 1]], dtype=int32)
+    """
+    # Initialize segmented array with zeros
+    segmented = np.zeros_like(image, dtype=np.int32)
+
+    # Assign labels based on thresholds
+    for i, threshold in enumerate(thresholds):
+        segmented[image > threshold] = i + 1
+
+    return segmented
+
+
+if __name__ == "__main__":
+    # Load the image
+    image_path = "path_to_image"  # Replace with your image path
+    original_image = Image.open(image_path).convert("L")
+    image_array = np.array(original_image)
+
+    # Define thresholds
+    thresholds = [50, 100, 150, 200]
+
+    # Perform segmentation
+    segmented_image = segment_image(image_array, thresholds)
+
+    # Display the results
+    plt.figure(figsize=(10, 5))
+
+    plt.subplot(1, 2, 1)
+    plt.title("Original Image")
+    plt.imshow(image_array, cmap="gray")
+    plt.axis("off")
+
+    plt.subplot(1, 2, 2)
+    plt.title("Segmented Image")
+    plt.imshow(segmented_image, cmap="tab20")
+    plt.axis("off")
+
+    plt.show()

From 75c5c411133f7e0f339c8d68c7c76c8054eb4249 Mon Sep 17 00:00:00 2001
From: Scarfinos <158184182+Scarfinos@users.noreply.github.com>
Date: Mon, 30 Dec 2024 16:12:26 +0100
Subject: [PATCH 042/135] #9943 : Adding coverage test for basic_graphs.py
 (#12354)

* #9943 : Adding coverage test for basic_graphs.py

* #9943 : Adding coverage test for basic_graphs.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Solve problem of line too long

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 graphs/basic_graphs.py | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/graphs/basic_graphs.py b/graphs/basic_graphs.py
index 567fa65040ae..286e9b195796 100644
--- a/graphs/basic_graphs.py
+++ b/graphs/basic_graphs.py
@@ -77,6 +77,14 @@ def initialize_weighted_undirected_graph(
 
 
 def dfs(g, s):
+    """
+    >>> dfs({1: [2, 3], 2: [4, 5], 3: [], 4: [], 5: []}, 1)
+    1
+    2
+    4
+    5
+    3
+    """
     vis, _s = {s}, [s]
     print(s)
     while _s:
@@ -104,6 +112,17 @@ def dfs(g, s):
 
 
 def bfs(g, s):
+    """
+    >>> bfs({1: [2, 3], 2: [4, 5], 3: [6, 7], 4: [], 5: [8], 6: [], 7: [], 8: []}, 1)
+    1
+    2
+    3
+    4
+    5
+    6
+    7
+    8
+    """
     vis, q = {s}, deque([s])
     print(s)
     while q:
@@ -128,6 +147,19 @@ def bfs(g, s):
 
 
 def dijk(g, s):
+    """
+    dijk({1: [(2, 7), (3, 9), (6, 14)],
+        2: [(1, 7), (3, 10), (4, 15)],
+        3: [(1, 9), (2, 10), (4, 11), (6, 2)],
+        4: [(2, 15), (3, 11), (5, 6)],
+        5: [(4, 6), (6, 9)],
+        6: [(1, 14), (3, 2), (5, 9)]}, 1)
+    7
+    9
+    11
+    20
+    20
+    """
     dist, known, path = {s: 0}, set(), {s: 0}
     while True:
         if len(known) == len(g) - 1:

From 7e55fb6474a06ecf0000fe11494fe5eefeeb54ab Mon Sep 17 00:00:00 2001
From: Jeffrey Yancey <jeffrey@cleverfox.es>
Date: Mon, 30 Dec 2024 12:00:30 -0700
Subject: [PATCH 043/135] - Implemented `find_lanczos_eigenvectors` to
 approximate the largest eigenvalues and corresponding eigenvectors of a graph
 based on its adjacency list. (#11906)

- Utilized `lanczos_iteration` to construct tridiagonal matrices, optimized for large, sparse matrices.
- Added `multiply_matrix_vector` for efficient matrix-vector multiplication using adjacency lists.
- Included `validate_adjacency_list` for input validation.

- Supports varied graph analysis applications, particularly for analyzing graph centrality.
- Included type hints, comprehensive docstrings, and doctests.
- PEP-8 compliant, with optimized handling of inputs and outputs.

This module provides essential tools for eigenvalue-based graph analysis, ideal for centrality insights and structural assessments.
---
 graphs/lanczos_eigenvectors.py | 206 +++++++++++++++++++++++++++++++++
 1 file changed, 206 insertions(+)
 create mode 100644 graphs/lanczos_eigenvectors.py

diff --git a/graphs/lanczos_eigenvectors.py b/graphs/lanczos_eigenvectors.py
new file mode 100644
index 000000000000..581a81a1127f
--- /dev/null
+++ b/graphs/lanczos_eigenvectors.py
@@ -0,0 +1,206 @@
+"""
+Lanczos Method for Finding Eigenvalues and Eigenvectors of a Graph.
+
+This module demonstrates the Lanczos method to approximate the largest eigenvalues
+and corresponding eigenvectors of a symmetric matrix represented as a graph's
+adjacency list. The method efficiently handles large, sparse matrices by converting
+the graph to a tridiagonal matrix, whose eigenvalues and eigenvectors are then
+computed.
+
+Key Functions:
+- `find_lanczos_eigenvectors`: Computes the k largest eigenvalues and vectors.
+- `lanczos_iteration`: Constructs the tridiagonal matrix and orthonormal basis vectors.
+- `multiply_matrix_vector`: Multiplies an adjacency list graph with a vector.
+
+Complexity:
+- Time: O(k * n), where k is the number of eigenvalues and n is the matrix size.
+- Space: O(n), due to sparse representation and tridiagonal matrix structure.
+
+Further Reading:
+- Lanczos Algorithm: https://en.wikipedia.org/wiki/Lanczos_algorithm
+- Eigenvector Centrality: https://en.wikipedia.org/wiki/Eigenvector_centrality
+
+Example Usage:
+Given a graph represented by an adjacency list, the `find_lanczos_eigenvectors`
+function returns the largest eigenvalues and eigenvectors. This can be used to
+analyze graph centrality.
+"""
+
+import numpy as np
+
+
+def validate_adjacency_list(graph: list[list[int | None]]) -> None:
+    """Validates the adjacency list format for the graph.
+
+    Args:
+        graph: A list of lists where each sublist contains the neighbors of a node.
+
+    Raises:
+        ValueError: If the graph is not a list of lists, or if any node has
+                    invalid neighbors (e.g., out-of-range or non-integer values).
+
+    >>> validate_adjacency_list([[1, 2], [0], [0, 1]])
+    >>> validate_adjacency_list([[]])  # No neighbors, valid case
+    >>> validate_adjacency_list([[1], [2], [-1]])  # Invalid neighbor
+    Traceback (most recent call last):
+        ...
+    ValueError: Invalid neighbor -1 in node 2 adjacency list.
+    """
+    if not isinstance(graph, list):
+        raise ValueError("Graph should be a list of lists.")
+
+    for node_index, neighbors in enumerate(graph):
+        if not isinstance(neighbors, list):
+            no_neighbors_message: str = (
+                f"Node {node_index} should have a list of neighbors."
+            )
+            raise ValueError(no_neighbors_message)
+        for neighbor_index in neighbors:
+            if (
+                not isinstance(neighbor_index, int)
+                or neighbor_index < 0
+                or neighbor_index >= len(graph)
+            ):
+                invalid_neighbor_message: str = (
+                    f"Invalid neighbor {neighbor_index} in node {node_index} "
+                    f"adjacency list."
+                )
+                raise ValueError(invalid_neighbor_message)
+
+
+def lanczos_iteration(
+    graph: list[list[int | None]], num_eigenvectors: int
+) -> tuple[np.ndarray, np.ndarray]:
+    """Constructs the tridiagonal matrix and orthonormal basis vectors using the
+    Lanczos method.
+
+    Args:
+        graph: The graph represented as a list of adjacency lists.
+        num_eigenvectors: The number of largest eigenvalues and eigenvectors
+                          to approximate.
+
+    Returns:
+        A tuple containing:
+            - tridiagonal_matrix: A (num_eigenvectors x num_eigenvectors) symmetric
+                                  matrix.
+            - orthonormal_basis: A (num_nodes x num_eigenvectors) matrix of orthonormal
+                                 basis vectors.
+
+    Raises:
+        ValueError: If num_eigenvectors is less than 1 or greater than the number of
+                    nodes.
+
+    >>> graph = [[1, 2], [0, 2], [0, 1]]
+    >>> T, Q = lanczos_iteration(graph, 2)
+    >>> T.shape == (2, 2) and Q.shape == (3, 2)
+    True
+    """
+    num_nodes: int = len(graph)
+    if not (1 <= num_eigenvectors <= num_nodes):
+        raise ValueError(
+            "Number of eigenvectors must be between 1 and the number of "
+            "nodes in the graph."
+        )
+
+    orthonormal_basis: np.ndarray = np.zeros((num_nodes, num_eigenvectors))
+    tridiagonal_matrix: np.ndarray = np.zeros((num_eigenvectors, num_eigenvectors))
+
+    rng = np.random.default_rng()
+    initial_vector: np.ndarray = rng.random(num_nodes)
+    initial_vector /= np.sqrt(np.dot(initial_vector, initial_vector))
+    orthonormal_basis[:, 0] = initial_vector
+
+    prev_beta: float = 0.0
+    for iter_index in range(num_eigenvectors):
+        result_vector: np.ndarray = multiply_matrix_vector(
+            graph, orthonormal_basis[:, iter_index]
+        )
+        if iter_index > 0:
+            result_vector -= prev_beta * orthonormal_basis[:, iter_index - 1]
+        alpha_value: float = np.dot(orthonormal_basis[:, iter_index], result_vector)
+        result_vector -= alpha_value * orthonormal_basis[:, iter_index]
+
+        prev_beta = np.sqrt(np.dot(result_vector, result_vector))
+        if iter_index < num_eigenvectors - 1 and prev_beta > 1e-10:
+            orthonormal_basis[:, iter_index + 1] = result_vector / prev_beta
+        tridiagonal_matrix[iter_index, iter_index] = alpha_value
+        if iter_index < num_eigenvectors - 1:
+            tridiagonal_matrix[iter_index, iter_index + 1] = prev_beta
+            tridiagonal_matrix[iter_index + 1, iter_index] = prev_beta
+    return tridiagonal_matrix, orthonormal_basis
+
+
+def multiply_matrix_vector(
+    graph: list[list[int | None]], vector: np.ndarray
+) -> np.ndarray:
+    """Performs multiplication of a graph's adjacency list representation with a vector.
+
+    Args:
+        graph: The adjacency list of the graph.
+        vector: A 1D numpy array representing the vector to multiply.
+
+    Returns:
+        A numpy array representing the product of the adjacency list and the vector.
+
+    Raises:
+        ValueError: If the vector's length does not match the number of nodes in the
+                    graph.
+
+    >>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([1, 1, 1]))
+    array([2., 2., 2.])
+    >>> multiply_matrix_vector([[1, 2], [0, 2], [0, 1]], np.array([0, 1, 0]))
+    array([1., 0., 1.])
+    """
+    num_nodes: int = len(graph)
+    if vector.shape[0] != num_nodes:
+        raise ValueError("Vector length must match the number of nodes in the graph.")
+
+    result: np.ndarray = np.zeros(num_nodes)
+    for node_index, neighbors in enumerate(graph):
+        for neighbor_index in neighbors:
+            result[node_index] += vector[neighbor_index]
+    return result
+
+
+def find_lanczos_eigenvectors(
+    graph: list[list[int | None]], num_eigenvectors: int
+) -> tuple[np.ndarray, np.ndarray]:
+    """Computes the largest eigenvalues and their corresponding eigenvectors using the
+    Lanczos method.
+
+    Args:
+        graph: The graph as a list of adjacency lists.
+        num_eigenvectors: Number of largest eigenvalues and eigenvectors to compute.
+
+    Returns:
+        A tuple containing:
+            - eigenvalues: 1D array of the largest eigenvalues in descending order.
+            - eigenvectors: 2D array where each column is an eigenvector corresponding
+                            to an eigenvalue.
+
+    Raises:
+        ValueError: If the graph format is invalid or num_eigenvectors is out of bounds.
+
+    >>> eigenvalues, eigenvectors = find_lanczos_eigenvectors(
+    ...     [[1, 2], [0, 2], [0, 1]], 2
+    ... )
+    >>> len(eigenvalues) == 2 and eigenvectors.shape[1] == 2
+    True
+    """
+    validate_adjacency_list(graph)
+    tridiagonal_matrix, orthonormal_basis = lanczos_iteration(graph, num_eigenvectors)
+    eigenvalues, eigenvectors = np.linalg.eigh(tridiagonal_matrix)
+    return eigenvalues[::-1], np.dot(orthonormal_basis, eigenvectors[:, ::-1])
+
+
+def main() -> None:
+    """
+    Main driver function for testing the implementation with doctests.
+    """
+    import doctest
+
+    doctest.testmod()
+
+
+if __name__ == "__main__":
+    main()

From 8921b56a8517cdc9455d764d7cddb10b2d7f2145 Mon Sep 17 00:00:00 2001
From: Melih Mehmet Sahin <msahin8765@gmail.com>
Date: Mon, 30 Dec 2024 19:53:50 +0000
Subject: [PATCH 044/135] Adding tests to monotonic_array.py (#12073)

* Contributes to #9943 by adding tests to monotonic_array.py

Addeded doctest in the if __name__. Checks for negaitves and an array of same integers

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 data_structures/arrays/monotonic_array.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/data_structures/arrays/monotonic_array.py b/data_structures/arrays/monotonic_array.py
index c50a21530814..342d443a9cfc 100644
--- a/data_structures/arrays/monotonic_array.py
+++ b/data_structures/arrays/monotonic_array.py
@@ -9,6 +9,16 @@ def is_monotonic(nums: list[int]) -> bool:
     True
     >>> is_monotonic([1, 3, 2])
     False
+    >>> is_monotonic([1,2,3,4,5,6,5])
+    False
+    >>> is_monotonic([-3,-2,-1])
+    True
+    >>> is_monotonic([-5,-6,-7])
+    True
+    >>> is_monotonic([0,0,0])
+    True
+    >>> is_monotonic([-100,0,100])
+    True
     """
     return all(nums[i] <= nums[i + 1] for i in range(len(nums) - 1)) or all(
         nums[i] >= nums[i + 1] for i in range(len(nums) - 1)
@@ -21,3 +31,7 @@ def is_monotonic(nums: list[int]) -> bool:
     print(is_monotonic([1, 2, 2, 3]))  # Output: True
     print(is_monotonic([6, 5, 4, 4]))  # Output: True
     print(is_monotonic([1, 3, 2]))  # Output: False
+
+    import doctest
+
+    doctest.testmod()

From 5942059cb571b213e5ec82fe9b45e5a9bef4864b Mon Sep 17 00:00:00 2001
From: Giulio Tantaro <giulio.tantaro@gmail.com>
Date: Mon, 30 Dec 2024 21:03:31 +0100
Subject: [PATCH 045/135] add doctest for quick_sort_3_partition (#11779)

---
 sorts/quick_sort_3_partition.py | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py
index 1a6db6a364f0..279b9a68f5a6 100644
--- a/sorts/quick_sort_3_partition.py
+++ b/sorts/quick_sort_3_partition.py
@@ -1,4 +1,27 @@
 def quick_sort_3partition(sorting: list, left: int, right: int) -> None:
+    """ "
+    Python implementation of quick sort algorithm with 3-way partition.
+    The idea of 3-way quick sort is based on "Dutch National Flag algorithm".
+
+    :param sorting: sort list
+    :param left: left endpoint of sorting
+    :param right: right endpoint of sorting
+    :return: None
+
+    Examples:
+    >>> array1 = [5, -1, -1, 5, 5, 24, 0]
+    >>> quick_sort_3partition(array1, 0, 6)
+    >>> array1
+    [-1, -1, 0, 5, 5, 5, 24]
+    >>> array2 = [9, 0, 2, 6]
+    >>> quick_sort_3partition(array2, 0, 3)
+    >>> array2
+    [0, 2, 6, 9]
+    >>> array3 = []
+    >>> quick_sort_3partition(array3, 0, 0)
+    >>> array3
+    []
+    """
     if right <= left:
         return
     a = i = left

From 8767d1d72436b8aff89f9c11d045ad95bec02ba4 Mon Sep 17 00:00:00 2001
From: Rodrigo Castro <rodrigondec@gmail.com>
Date: Mon, 30 Dec 2024 21:36:41 -0300
Subject: [PATCH 046/135] add some documentation for heap sort (#9949)

* add some documentation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix typing

* Update heap_sort.py

* Update heap_sort.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 sorts/heap_sort.py | 47 +++++++++++++++++++++++++++-------------------
 1 file changed, 28 insertions(+), 19 deletions(-)

diff --git a/sorts/heap_sort.py b/sorts/heap_sort.py
index 4dca879bd89c..44ee1d4b39f1 100644
--- a/sorts/heap_sort.py
+++ b/sorts/heap_sort.py
@@ -1,17 +1,22 @@
 """
-This is a pure Python implementation of the heap sort algorithm.
-
-For doctests run following command:
-python -m doctest -v heap_sort.py
-or
-python3 -m doctest -v heap_sort.py
-
-For manual testing run:
-python heap_sort.py
+A pure Python implementation of the heap sort algorithm.
 """
 
 
-def heapify(unsorted, index, heap_size):
+def heapify(unsorted: list[int], index: int, heap_size: int) -> None:
+    """
+    :param unsorted: unsorted list containing integers numbers
+    :param index: index
+    :param heap_size: size of the heap
+    :return: None
+    >>> unsorted = [1, 4, 3, 5, 2]
+    >>> heapify(unsorted, 0, len(unsorted))
+    >>> unsorted
+    [4, 5, 3, 1, 2]
+    >>> heapify(unsorted, 0, len(unsorted))
+    >>> unsorted
+    [5, 4, 3, 1, 2]
+    """
     largest = index
     left_index = 2 * index + 1
     right_index = 2 * index + 2
@@ -22,26 +27,26 @@ def heapify(unsorted, index, heap_size):
         largest = right_index
 
     if largest != index:
-        unsorted[largest], unsorted[index] = unsorted[index], unsorted[largest]
+        unsorted[largest], unsorted[index] = (unsorted[index], unsorted[largest])
         heapify(unsorted, largest, heap_size)
 
 
-def heap_sort(unsorted):
+def heap_sort(unsorted: list[int]) -> list[int]:
     """
-    Pure implementation of the heap sort algorithm in Python
-    :param collection: some mutable ordered collection with heterogeneous
-    comparable items inside
+    A pure Python implementation of the heap sort algorithm
+
+    :param collection: a mutable ordered collection of heterogeneous comparable items
     :return: the same collection ordered by ascending
 
     Examples:
     >>> heap_sort([0, 5, 3, 2, 2])
     [0, 2, 2, 3, 5]
-
     >>> heap_sort([])
     []
-
     >>> heap_sort([-2, -5, -45])
     [-45, -5, -2]
+    >>> heap_sort([3, 7, 9, 28, 123, -5, 8, -30, -200, 0, 4])
+    [-200, -30, -5, 0, 3, 4, 7, 8, 9, 28, 123]
     """
     n = len(unsorted)
     for i in range(n // 2 - 1, -1, -1):
@@ -53,6 +58,10 @@ def heap_sort(unsorted):
 
 
 if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
     user_input = input("Enter numbers separated by a comma:\n").strip()
-    unsorted = [int(item) for item in user_input.split(",")]
-    print(heap_sort(unsorted))
+    if user_input:
+        unsorted = [int(item) for item in user_input.split(",")]
+        print(f"{heap_sort(unsorted) = }")

From 8439fa8d1da94370250d153cd57f9bdcc382a062 Mon Sep 17 00:00:00 2001
From: Paarth Goyal <138299656+pluto-tofu@users.noreply.github.com>
Date: Tue, 31 Dec 2024 06:17:41 +0530
Subject: [PATCH 047/135] Added the algorithm to compute the time period of a
 simple pendulum (#10265)

* Added the algorithm to compute the time period of a simple pendulum

* imported g form scipy and changed doctests accordingly

* fixed formatting

* applied all suggested changes from code review

* Apply suggestions from code review

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 physics/period_of_pendulum.py | 53 +++++++++++++++++++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 physics/period_of_pendulum.py

diff --git a/physics/period_of_pendulum.py b/physics/period_of_pendulum.py
new file mode 100644
index 000000000000..2e3c7bc3ef1e
--- /dev/null
+++ b/physics/period_of_pendulum.py
@@ -0,0 +1,53 @@
+"""
+Title : Computing the time period of a simple pendulum
+
+The simple pendulum is a mechanical system that sways or moves in an
+oscillatory motion. The simple pendulum comprises of a small bob of
+mass m suspended by a thin string of length L and secured to a platform
+at its upper end. Its motion occurs in a vertical plane and is mainly
+driven by gravitational force. The period of the pendulum depends on the
+length of the string and the amplitude (the maximum angle) of oscillation.
+However, the effect of the amplitude can be ignored if the amplitude is
+small. It should be noted that the period does not depend on the mass of
+the bob.
+
+For small amplitudes, the period of a simple pendulum is given by the
+following approximation:
+T ≈ 2π * √(L / g)
+
+where:
+L = length of string from which the bob is hanging (in m)
+g = acceleration due to gravity (approx 9.8 m/s²)
+
+Reference : https://byjus.com/jee/simple-pendulum/
+"""
+
+from math import pi
+
+from scipy.constants import g
+
+
+def period_of_pendulum(length: float) -> float:
+    """
+    >>> period_of_pendulum(1.23)
+    2.2252155506257845
+    >>> period_of_pendulum(2.37)
+    3.0888278441908574
+    >>> period_of_pendulum(5.63)
+    4.76073193364765
+    >>> period_of_pendulum(-12)
+    Traceback (most recent call last):
+        ...
+    ValueError: The length should be non-negative
+    >>> period_of_pendulum(0)
+    0.0
+    """
+    if length < 0:
+        raise ValueError("The length should be non-negative")
+    return 2 * pi * (length / g) ** 0.5
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()

From a4399022e516dfba1097f91cedc0b7f4213bab84 Mon Sep 17 00:00:00 2001
From: Julien Richard <julienrichard98@gmail.com>
Date: Tue, 31 Dec 2024 02:11:29 +0100
Subject: [PATCH 048/135] chore: improve comments and add tests to trapezoidal
 rule (#11640)

* chore: improve comments and add tests to trapezoidal rule

* fix: too much characters in line

* Update maths/trapezoidal_rule.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update maths/trapezoidal_rule.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update maths/trapezoidal_rule.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update maths/trapezoidal_rule.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* fix: change function name in calls

* modify tests, changes numbers to remove coma

* updating DIRECTORY.md

* Fix doctest whitespace

* Try to fix line length in doctest

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
Co-authored-by: tianyizheng02 <tianyizheng02@users.noreply.github.com>
---
 DIRECTORY.md              |  3 ++
 maths/trapezoidal_rule.py | 97 ++++++++++++++++++++++-----------------
 2 files changed, 58 insertions(+), 42 deletions(-)

diff --git a/DIRECTORY.md b/DIRECTORY.md
index 44d0414a37c8..1248a290d294 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -142,6 +142,7 @@
   * [Haralick Descriptors](computer_vision/haralick_descriptors.py)
   * [Harris Corner](computer_vision/harris_corner.py)
   * [Horn Schunck](computer_vision/horn_schunck.py)
+  * [Intensity Based Segmentation](computer_vision/intensity_based_segmentation.py)
   * [Mean Threshold](computer_vision/mean_threshold.py)
   * [Mosaic Augmentation](computer_vision/mosaic_augmentation.py)
   * [Pooling Functions](computer_vision/pooling_functions.py)
@@ -507,6 +508,7 @@
   * [Kahns Algorithm Long](graphs/kahns_algorithm_long.py)
   * [Kahns Algorithm Topo](graphs/kahns_algorithm_topo.py)
   * [Karger](graphs/karger.py)
+  * [Lanczos Eigenvectors](graphs/lanczos_eigenvectors.py)
   * [Markov Chain](graphs/markov_chain.py)
   * [Matching Min Vertex Cover](graphs/matching_min_vertex_cover.py)
   * [Minimum Path Sum](graphs/minimum_path_sum.py)
@@ -886,6 +888,7 @@
   * [N Body Simulation](physics/n_body_simulation.py)
   * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py)
   * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
+  * [Period Of Pendulum](physics/period_of_pendulum.py)
   * [Photoelectric Effect](physics/photoelectric_effect.py)
   * [Potential Energy](physics/potential_energy.py)
   * [Rainfall Intensity](physics/rainfall_intensity.py)
diff --git a/maths/trapezoidal_rule.py b/maths/trapezoidal_rule.py
index 0186629ee378..21b10b239b5f 100644
--- a/maths/trapezoidal_rule.py
+++ b/maths/trapezoidal_rule.py
@@ -1,28 +1,25 @@
 """
 Numerical integration or quadrature for a smooth function f with known values at x_i
-
-This method is the classical approach of suming 'Equally Spaced Abscissas'
-
-method 1:
-"extended trapezoidal rule"
-int(f) = dx/2 * (f1 + 2f2 + ... + fn)
-
 """
 
 
-def method_1(boundary, steps):
+def trapezoidal_rule(boundary, steps):
     """
-    Apply the extended trapezoidal rule to approximate the integral of function f(x)
-    over the interval defined by 'boundary' with the number of 'steps'.
-
-    Args:
-        boundary (list of floats): A list containing the start and end values [a, b].
-        steps (int): The number of steps or subintervals.
-    Returns:
-        float: Approximation of the integral of f(x) over [a, b].
-    Examples:
-    >>> method_1([0, 1], 10)
-    0.3349999999999999
+    Implements the extended trapezoidal rule for numerical integration.
+    The function f(x) is provided below.
+
+    :param boundary: List containing the lower and upper bounds of integration [a, b]
+    :param steps: The number of steps (intervals) used in the approximation
+    :return: The numerical approximation of the integral
+
+    >>> abs(trapezoidal_rule([0, 1], 10) - 0.33333) < 0.01
+    True
+    >>> abs(trapezoidal_rule([0, 1], 100) - 0.33333) < 0.01
+    True
+    >>> abs(trapezoidal_rule([0, 2], 1000) - 2.66667) < 0.01
+    True
+    >>> abs(trapezoidal_rule([1, 2], 1000) - 2.33333) < 0.01
+    True
     """
     h = (boundary[1] - boundary[0]) / steps
     a = boundary[0]
@@ -31,7 +28,6 @@ def method_1(boundary, steps):
     y = 0.0
     y += (h / 2.0) * f(a)
     for i in x_i:
-        # print(i)
         y += h * f(i)
     y += (h / 2.0) * f(b)
     return y
@@ -39,49 +35,66 @@ def method_1(boundary, steps):
 
 def make_points(a, b, h):
     """
-    Generates points between 'a' and 'b' with step size 'h', excluding the end points.
-    Args:
-        a (float): Start value
-        b (float): End value
-        h (float): Step size
-    Examples:
+    Generates points between a and b with step size h for trapezoidal integration.
+
+    :param a: The lower bound of integration
+    :param b: The upper bound of integration
+    :param h: The step size
+    :yield: The next x-value in the range (a, b)
+
+    >>> list(make_points(0, 1, 0.1))    # doctest: +NORMALIZE_WHITESPACE
+    [0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6, 0.7, 0.7999999999999999, \
+    0.8999999999999999]
     >>> list(make_points(0, 10, 2.5))
     [2.5, 5.0, 7.5]
-
     >>> list(make_points(0, 10, 2))
     [2, 4, 6, 8]
-
     >>> list(make_points(1, 21, 5))
     [6, 11, 16]
-
     >>> list(make_points(1, 5, 2))
     [3]
-
     >>> list(make_points(1, 4, 3))
     []
     """
     x = a + h
     while x <= (b - h):
         yield x
-        x = x + h
+        x += h
 
 
-def f(x):  # enter your function here
+def f(x):
     """
-    Example:
-    >>> f(2)
-    4
+    This is the function to integrate, f(x) = (x - 0)^2 = x^2.
+
+    :param x: The input value
+    :return: The value of f(x)
+
+    >>> f(0)
+    0
+    >>> f(1)
+    1
+    >>> f(0.5)
+    0.25
     """
-    y = (x - 0) * (x - 0)
-    return y
+    return x**2
 
 
 def main():
-    a = 0.0  # Lower bound of integration
-    b = 1.0  # Upper bound of integration
-    steps = 10.0  # define number of steps or resolution
-    boundary = [a, b]  # define boundary of integration
-    y = method_1(boundary, steps)
+    """
+    Main function to test the trapezoidal rule.
+    :a: Lower bound of integration
+    :b: Upper bound of integration
+    :steps: define number of steps or resolution
+    :boundary: define boundary of integration
+
+    >>> main()
+    y = 0.3349999999999999
+    """
+    a = 0.0
+    b = 1.0
+    steps = 10.0
+    boundary = [a, b]
+    y = trapezoidal_rule(boundary, steps)
     print(f"y = {y}")
 
 

From 91a22c2e36477623b1f81518ff18c6f8617f81fb Mon Sep 17 00:00:00 2001
From: SEIKH NABAB UDDIN <93948993+nababuddin@users.noreply.github.com>
Date: Tue, 31 Dec 2024 07:39:14 +0530
Subject: [PATCH 049/135] Create digital differential analyzer_line.py (#10929)

* Create DDA_line_drawing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rename DDA_line_drawing.py to digital differential analyzer_line_drawing.py

* Rename DDA_line_drawing.py to digital_differential_analyzer_line_drawing.py

* Update digital_differential_analyzer_line_drawing.py

* Update digital_differential_analyzer_line_drawing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line_drawing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line_drawing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line_drawing.py

* Update digital_differential_analyzer_line_drawing.py

* Update digital_differential_analyzer_line_drawing.py

* Update digital_differential_analyzer_line_drawing.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update and rename digital_differential_analyzer_line_drawing.py to digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update digital_differential_analyzer_line.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Apply suggestions from code review

* Fix doctest

* Trigger GH workflows

* Fix function call in main block

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
---
 .../digital_differential_analyzer_line.py     | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)
 create mode 100644 graphics/digital_differential_analyzer_line.py

diff --git a/graphics/digital_differential_analyzer_line.py b/graphics/digital_differential_analyzer_line.py
new file mode 100644
index 000000000000..a51cb0b8dc37
--- /dev/null
+++ b/graphics/digital_differential_analyzer_line.py
@@ -0,0 +1,52 @@
+import matplotlib.pyplot as plt
+
+
+def digital_differential_analyzer_line(
+    p1: tuple[int, int], p2: tuple[int, int]
+) -> list[tuple[int, int]]:
+    """
+    Draws a line between two points using the DDA algorithm.
+
+    Args:
+    - p1: Coordinates of the starting point.
+    - p2: Coordinates of the ending point.
+    Returns:
+    - List of coordinate points that form the line.
+
+    >>> digital_differential_analyzer_line((1, 1), (4, 4))
+    [(2, 2), (3, 3), (4, 4)]
+    """
+    x1, y1 = p1
+    x2, y2 = p2
+    dx = x2 - x1
+    dy = y2 - y1
+    steps = max(abs(dx), abs(dy))
+    x_increment = dx / float(steps)
+    y_increment = dy / float(steps)
+    coordinates = []
+    x: float = x1
+    y: float = y1
+    for _ in range(steps):
+        x += x_increment
+        y += y_increment
+        coordinates.append((int(round(x)), int(round(y))))
+    return coordinates
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
+
+    x1 = int(input("Enter the x-coordinate of the starting point: "))
+    y1 = int(input("Enter the y-coordinate of the starting point: "))
+    x2 = int(input("Enter the x-coordinate of the ending point: "))
+    y2 = int(input("Enter the y-coordinate of the ending point: "))
+    coordinates = digital_differential_analyzer_line((x1, y1), (x2, y2))
+    x_points, y_points = zip(*coordinates)
+    plt.plot(x_points, y_points, marker="o")
+    plt.title("Digital Differential Analyzer Line Drawing Algorithm")
+    plt.xlabel("X-axis")
+    plt.ylabel("Y-axis")
+    plt.grid()
+    plt.show()

From 12b1023a9d97ca19be761c10129cba5509c9b450 Mon Sep 17 00:00:00 2001
From: Kaustubh Mani Tripathi
 <129510465+kmtGryffindor20@users.noreply.github.com>
Date: Tue, 31 Dec 2024 07:46:32 +0530
Subject: [PATCH 050/135] [ADDED] Implementation of Geometric Mean. (#10421)

* [ADDED] Implementation of Geometric Mean.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rectified type hints

* Typo

* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
---
 maths/geometric_mean.py | 55 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)
 create mode 100644 maths/geometric_mean.py

diff --git a/maths/geometric_mean.py b/maths/geometric_mean.py
new file mode 100644
index 000000000000..240d519ad398
--- /dev/null
+++ b/maths/geometric_mean.py
@@ -0,0 +1,55 @@
+"""
+The Geometric Mean of n numbers is defined as the n-th root of the product
+of those numbers. It is used to measure the central tendency of the numbers.
+https://en.wikipedia.org/wiki/Geometric_mean
+"""
+
+
+def compute_geometric_mean(*args: int) -> float:
+    """
+    Return the geometric mean of the argument numbers.
+    >>> compute_geometric_mean(2,8)
+    4.0
+    >>> compute_geometric_mean('a', 4)
+    Traceback (most recent call last):
+        ...
+    TypeError: Not a Number
+    >>> compute_geometric_mean(5, 125)
+    25.0
+    >>> compute_geometric_mean(1, 0)
+    0.0
+    >>> compute_geometric_mean(1, 5, 25, 5)
+    5.0
+    >>> compute_geometric_mean(2, -2)
+    Traceback (most recent call last):
+        ...
+    ArithmeticError: Cannot Compute Geometric Mean for these numbers.
+    >>> compute_geometric_mean(-5, 25, 1)
+    -5.0
+    """
+    product = 1
+    for number in args:
+        if not isinstance(number, int) and not isinstance(number, float):
+            raise TypeError("Not a Number")
+        product *= number
+    # Cannot calculate the even root for negative product.
+    # Frequently they are restricted to being positive.
+    if product < 0 and len(args) % 2 == 0:
+        raise ArithmeticError("Cannot Compute Geometric Mean for these numbers.")
+    mean = abs(product) ** (1 / len(args))
+    # Since python calculates complex roots for negative products with odd roots.
+    if product < 0:
+        mean = -mean
+    # Since it does floating point arithmetic, it gives 64**(1/3) as 3.99999996
+    possible_mean = float(round(mean))
+    # To check if the rounded number is actually the mean.
+    if possible_mean ** len(args) == product:
+        mean = possible_mean
+    return mean
+
+
+if __name__ == "__main__":
+    from doctest import testmod
+
+    testmod(name="compute_geometric_mean")
+    print(compute_geometric_mean(-3, -27))

From bae33acf9008aa6c80351b3c68f492ba0c4a1352 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 6 Jan 2025 21:12:13 +0100
Subject: [PATCH 051/135] [pre-commit.ci] pre-commit autoupdate (#12507)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.4 → v0.8.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.4...v0.8.6)
- [github.com/pre-commit/mirrors-mypy: v1.14.0 → v1.14.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.14.0...v1.14.1)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 DIRECTORY.md            | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 71ac72c29b5f..ec1dbca3a41c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.4
+    rev: v0.8.6
     hooks:
       - id: ruff
       - id: ruff-format
@@ -47,7 +47,7 @@ repos:
       - id: validate-pyproject
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.14.0
+    rev: v1.14.1
     hooks:
       - id: mypy
         args:
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 1248a290d294..3f0a5dbb140f 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -462,6 +462,7 @@
 
 ## Graphics
   * [Bezier Curve](graphics/bezier_curve.py)
+  * [Digital Differential Analyzer Line](graphics/digital_differential_analyzer_line.py)
   * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py)
 
 ## Graphs
@@ -663,6 +664,7 @@
   * [Gamma](maths/gamma.py)
   * [Gaussian](maths/gaussian.py)
   * [Gcd Of N Numbers](maths/gcd_of_n_numbers.py)
+  * [Geometric Mean](maths/geometric_mean.py)
   * [Germain Primes](maths/germain_primes.py)
   * [Greatest Common Divisor](maths/greatest_common_divisor.py)
   * [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py)

From b653aee627a95de423f1cad97f283de904271ff7 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Sun, 12 Jan 2025 19:05:08 +0300
Subject: [PATCH 052/135] Fix ruff (#12515)

* Empty commit

* Fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Fix

* Fix

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
---
 DIRECTORY.md                            | 4 ++--
 ciphers/{base64.py => base64_cipher.py} | 0
 project_euler/problem_002/sol4.py       | 2 +-
 strings/{wave.py => wave_string.py}     | 0
 4 files changed, 3 insertions(+), 3 deletions(-)
 rename ciphers/{base64.py => base64_cipher.py} (100%)
 rename strings/{wave.py => wave_string.py} (100%)

diff --git a/DIRECTORY.md b/DIRECTORY.md
index 3f0a5dbb140f..aad6c72aa8ee 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -86,7 +86,7 @@
   * [Baconian Cipher](ciphers/baconian_cipher.py)
   * [Base16](ciphers/base16.py)
   * [Base32](ciphers/base32.py)
-  * [Base64](ciphers/base64.py)
+  * [Base64 Cipher](ciphers/base64_cipher.py)
   * [Base85](ciphers/base85.py)
   * [Beaufort Cipher](ciphers/beaufort_cipher.py)
   * [Bifid](ciphers/bifid.py)
@@ -1331,7 +1331,7 @@
   * [Title](strings/title.py)
   * [Top K Frequent Words](strings/top_k_frequent_words.py)
   * [Upper](strings/upper.py)
-  * [Wave](strings/wave.py)
+  * [Wave String](strings/wave_string.py)
   * [Wildcard Pattern Matching](strings/wildcard_pattern_matching.py)
   * [Word Occurrence](strings/word_occurrence.py)
   * [Word Patterns](strings/word_patterns.py)
diff --git a/ciphers/base64.py b/ciphers/base64_cipher.py
similarity index 100%
rename from ciphers/base64.py
rename to ciphers/base64_cipher.py
diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py
index 3a2e4fce341c..a13d34fd760e 100644
--- a/project_euler/problem_002/sol4.py
+++ b/project_euler/problem_002/sol4.py
@@ -61,7 +61,7 @@ def solution(n: int = 4000000) -> int:
     if n <= 0:
         raise ValueError("Parameter n must be greater than or equal to one.")
     getcontext().prec = 100
-    phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2)
+    phi = (Decimal(5) ** Decimal("0.5") + 1) / Decimal(2)
 
     index = (math.floor(math.log(n * (phi + 2), phi) - 1) // 3) * 3 + 2
     num = Decimal(round(phi ** Decimal(index + 1))) / (phi + 2)
diff --git a/strings/wave.py b/strings/wave_string.py
similarity index 100%
rename from strings/wave.py
rename to strings/wave_string.py

From 4c92de5e03310811a376058e110db8d615769087 Mon Sep 17 00:00:00 2001
From: Sanjay Muthu <sanjaymuthu12@gmail.com>
Date: Mon, 13 Jan 2025 05:05:22 +0530
Subject: [PATCH 053/135] Fix
 dynamic_programming/longest_increasing_subsequence.py (#12517)

* Fix #12510

* Added the doctest mentioned in the issue

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fixed Grammer Mistake

* Update longest_increasing_subsequence.py

* Update longest_increasing_subsequence.py

* Update longest_increasing_subsequence.py

* Update longest_increasing_subsequence.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 dynamic_programming/longest_increasing_subsequence.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/dynamic_programming/longest_increasing_subsequence.py b/dynamic_programming/longest_increasing_subsequence.py
index d839757f6da5..1863a882c41e 100644
--- a/dynamic_programming/longest_increasing_subsequence.py
+++ b/dynamic_programming/longest_increasing_subsequence.py
@@ -24,8 +24,10 @@ def longest_subsequence(array: list[int]) -> list[int]:  # This function is recu
     [10, 22, 33, 41, 60, 80]
     >>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9])
     [1, 2, 3, 9]
+    >>> longest_subsequence([28, 26, 12, 23, 35, 39])
+    [12, 23, 35, 39]
     >>> longest_subsequence([9, 8, 7, 6, 5, 7])
-    [8]
+    [5, 7]
     >>> longest_subsequence([1, 1, 1])
     [1, 1, 1]
     >>> longest_subsequence([])
@@ -44,7 +46,7 @@ def longest_subsequence(array: list[int]) -> list[int]:  # This function is recu
     while not is_found and i < array_length:
         if array[i] < pivot:
             is_found = True
-            temp_array = [element for element in array[i:] if element >= array[i]]
+            temp_array = array[i:]
             temp_array = longest_subsequence(temp_array)
             if len(temp_array) > len(longest_subseq):
                 longest_subseq = temp_array

From 787aa5d3b59640b2d9161b56ca8fde763597efe4 Mon Sep 17 00:00:00 2001
From: Siddhant <87547498+Siddhant231xyz@users.noreply.github.com>
Date: Sun, 12 Jan 2025 20:54:23 -0500
Subject: [PATCH 054/135] doctest all_combinations.py (#12506)

* doctest in all_combinations.py

* added doctest in all_combinations.py

* doctests in all_combinations.py

* add doctest all_combinations.py

* add

---------

Co-authored-by: Siddhant Jain <sjain35@buffalo.edu>
---
 backtracking/all_combinations.py | 34 ++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py
index 390decf3a05b..1d15c6263e14 100644
--- a/backtracking/all_combinations.py
+++ b/backtracking/all_combinations.py
@@ -12,6 +12,8 @@
 
 def combination_lists(n: int, k: int) -> list[list[int]]:
     """
+    Generates all possible combinations of k numbers out of 1 ... n using itertools.
+
     >>> combination_lists(n=4, k=2)
     [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
     """
@@ -20,6 +22,8 @@ def combination_lists(n: int, k: int) -> list[list[int]]:
 
 def generate_all_combinations(n: int, k: int) -> list[list[int]]:
     """
+    Generates all possible combinations of k numbers out of 1 ... n using backtracking.
+
     >>> generate_all_combinations(n=4, k=2)
     [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
     >>> generate_all_combinations(n=0, k=0)
@@ -34,6 +38,14 @@ def generate_all_combinations(n: int, k: int) -> list[list[int]]:
     ValueError: n must not be negative
     >>> generate_all_combinations(n=5, k=4)
     [[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
+    >>> generate_all_combinations(n=3, k=3)
+    [[1, 2, 3]]
+    >>> generate_all_combinations(n=3, k=1)
+    [[1], [2], [3]]
+    >>> generate_all_combinations(n=1, k=0)
+    [[]]
+    >>> generate_all_combinations(n=1, k=1)
+    [[1]]
     >>> from itertools import combinations
     >>> all(generate_all_combinations(n, k) == combination_lists(n, k)
     ...     for n in range(1, 6) for k in range(1, 6))
@@ -56,6 +68,28 @@ def create_all_state(
     current_list: list[int],
     total_list: list[list[int]],
 ) -> None:
+    """
+    Helper function to recursively build all combinations.
+
+    >>> create_all_state(1, 4, 2, [], result := [])
+    >>> result
+    [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
+    >>> create_all_state(1, 3, 3, [], result := [])
+    >>> result
+    [[1, 2, 3]]
+    >>> create_all_state(2, 2, 1, [1], result := [])
+    >>> result
+    [[1, 2]]
+    >>> create_all_state(1, 0, 0, [], result := [])
+    >>> result
+    [[]]
+    >>> create_all_state(1, 4, 0, [1, 2], result := [])
+    >>> result
+    [[1, 2]]
+    >>> create_all_state(5, 4, 2, [1, 2], result := [])
+    >>> result
+    []
+    """
     if level == 0:
         total_list.append(current_list[:])
         return

From cfcc84edf7d14cb56f52ba6fbd8c8deb2e9a7852 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Mon, 13 Jan 2025 23:49:07 +0300
Subject: [PATCH 055/135] Fix build (#12516)

* Empty commit

* Fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 web_programming/current_stock_price.py | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py
index d0a65e9aac84..573e1f575c8e 100644
--- a/web_programming/current_stock_price.py
+++ b/web_programming/current_stock_price.py
@@ -15,7 +15,7 @@
 def stock_price(symbol: str = "AAPL") -> str:
     """
     >>> stock_price("EEEE")
-    '-'
+    '- '
     >>> isinstance(float(stock_price("GOOG")),float)
     True
     """
@@ -24,12 +24,10 @@ def stock_price(symbol: str = "AAPL") -> str:
         url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10
     ).text
     soup = BeautifulSoup(yahoo_finance_source, "html.parser")
-    specific_fin_streamer_tag = soup.find("fin-streamer", {"data-testid": "qsp-price"})
 
-    if specific_fin_streamer_tag:
-        text = specific_fin_streamer_tag.get_text()
-        return text
-    return "No <fin-streamer> tag with the specified data-test attribute found."
+    if specific_fin_streamer_tag := soup.find("span", {"data-testid": "qsp-price"}):
+        return specific_fin_streamer_tag.get_text()
+    return "No <fin-streamer> tag with the specified data-testid attribute found."
 
 
 # Search for the symbol at https://finance.yahoo.com/lookup

From 4fe50bc1fcf82fceb61839bae314720c092c0692 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 13 Jan 2025 21:52:12 +0100
Subject: [PATCH 056/135] [pre-commit.ci] pre-commit autoupdate -- ruff 2025
 stable format (#12521)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.8.6 → v0.9.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.6...v0.9.1)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update maths/dual_number_automatic_differentiation.py

* Update maths/dual_number_automatic_differentiation.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update dual_number_automatic_differentiation.py

* Update dual_number_automatic_differentiation.py

* No <fin-streamer> tag with the specified data-test attribute found.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml                       |  2 +-
 ciphers/base64_cipher.py                      | 12 +++---
 ciphers/caesar_cipher.py                      |  2 +-
 computer_vision/flip_augmentation.py          |  2 +-
 computer_vision/mosaic_augmentation.py        |  2 +-
 .../hashing/number_theory/prime_numbers.py    |  6 +--
 data_structures/heap/min_heap.py              |  6 +--
 data_structures/kd_tree/tests/test_kdtree.py  | 12 +++---
 .../suffix_tree/tests/test_suffix_tree.py     | 24 +++++------
 dynamic_programming/climbing_stairs.py        |  6 +--
 .../iterating_through_submasks.py             |  6 +--
 .../matrix_chain_multiplication.py            |  2 +-
 .../linear_discriminant_analysis.py           |  4 +-
 .../dual_number_automatic_differentiation.py  |  6 +--
 maths/max_sum_sliding_window.py               |  4 +-
 .../integration_by_simpson_approx.py          | 12 +++---
 maths/prime_check.py                          | 12 +++---
 maths/primelib.py                             | 42 +++++++++----------
 matrix/matrix_based_game.py                   |  2 +-
 neural_network/input_data.py                  |  6 +--
 scripts/validate_solutions.py                 |  6 +--
 strings/jaro_winkler.py                       |  4 +-
 web_programming/fetch_anime_and_play.py       |  4 +-
 23 files changed, 93 insertions(+), 91 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ec1dbca3a41c..3b1dd9658d7f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.8.6
+    rev: v0.9.1
     hooks:
       - id: ruff
       - id: ruff-format
diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py
index 2b950b1be37d..038d13963d95 100644
--- a/ciphers/base64_cipher.py
+++ b/ciphers/base64_cipher.py
@@ -105,13 +105,13 @@ def base64_decode(encoded_data: str) -> bytes:
 
     # Check if the encoded string contains non base64 characters
     if padding:
-        assert all(
-            char in B64_CHARSET for char in encoded_data[:-padding]
-        ), "Invalid base64 character(s) found."
+        assert all(char in B64_CHARSET for char in encoded_data[:-padding]), (
+            "Invalid base64 character(s) found."
+        )
     else:
-        assert all(
-            char in B64_CHARSET for char in encoded_data
-        ), "Invalid base64 character(s) found."
+        assert all(char in B64_CHARSET for char in encoded_data), (
+            "Invalid base64 character(s) found."
+        )
 
     # Check the padding
     assert len(encoded_data) % 4 == 0 and padding < 3, "Incorrect padding"
diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py
index 9c096fe8a7da..1cf4d67cbaed 100644
--- a/ciphers/caesar_cipher.py
+++ b/ciphers/caesar_cipher.py
@@ -225,7 +225,7 @@ def brute_force(input_string: str, alphabet: str | None = None) -> dict[int, str
 
 if __name__ == "__main__":
     while True:
-        print(f'\n{"-" * 10}\n Menu\n{"-" * 10}')
+        print(f"\n{'-' * 10}\n Menu\n{'-' * 10}")
         print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n")
 
         # get user input
diff --git a/computer_vision/flip_augmentation.py b/computer_vision/flip_augmentation.py
index 77a8cbd7b14f..7301424824df 100644
--- a/computer_vision/flip_augmentation.py
+++ b/computer_vision/flip_augmentation.py
@@ -33,7 +33,7 @@ def main() -> None:
         file_name = paths[index].split(os.sep)[-1].rsplit(".", 1)[0]
         file_root = f"{OUTPUT_DIR}/{file_name}_FLIP_{letter_code}"
         cv2.imwrite(f"{file_root}.jpg", image, [cv2.IMWRITE_JPEG_QUALITY, 85])
-        print(f"Success {index+1}/{len(new_images)} with {file_name}")
+        print(f"Success {index + 1}/{len(new_images)} with {file_name}")
         annos_list = []
         for anno in new_annos[index]:
             obj = f"{anno[0]} {anno[1]} {anno[2]} {anno[3]} {anno[4]}"
diff --git a/computer_vision/mosaic_augmentation.py b/computer_vision/mosaic_augmentation.py
index cd923dfe095f..d881347121ea 100644
--- a/computer_vision/mosaic_augmentation.py
+++ b/computer_vision/mosaic_augmentation.py
@@ -41,7 +41,7 @@ def main() -> None:
         file_name = path.split(os.sep)[-1].rsplit(".", 1)[0]
         file_root = f"{OUTPUT_DIR}/{file_name}_MOSAIC_{letter_code}"
         cv2.imwrite(f"{file_root}.jpg", new_image, [cv2.IMWRITE_JPEG_QUALITY, 85])
-        print(f"Succeeded {index+1}/{NUMBER_IMAGES} with {file_name}")
+        print(f"Succeeded {index + 1}/{NUMBER_IMAGES} with {file_name}")
         annos_list = []
         for anno in new_annos:
             width = anno[3] - anno[1]
diff --git a/data_structures/hashing/number_theory/prime_numbers.py b/data_structures/hashing/number_theory/prime_numbers.py
index 2549a1477b2b..82071b5e9f09 100644
--- a/data_structures/hashing/number_theory/prime_numbers.py
+++ b/data_structures/hashing/number_theory/prime_numbers.py
@@ -32,9 +32,9 @@ def is_prime(number: int) -> bool:
     """
 
     # precondition
-    assert isinstance(number, int) and (
-        number >= 0
-    ), "'number' must been an int and positive"
+    assert isinstance(number, int) and (number >= 0), (
+        "'number' must been an int and positive"
+    )
 
     if 1 < number < 4:
         # 2 and 3 are primes
diff --git a/data_structures/heap/min_heap.py b/data_structures/heap/min_heap.py
index ce7ed570a58d..577b98d788a1 100644
--- a/data_structures/heap/min_heap.py
+++ b/data_structures/heap/min_heap.py
@@ -124,9 +124,9 @@ def is_empty(self):
         return len(self.heap) == 0
 
     def decrease_key(self, node, new_value):
-        assert (
-            self.heap[self.idx_of_element[node]].val > new_value
-        ), "newValue must be less that current value"
+        assert self.heap[self.idx_of_element[node]].val > new_value, (
+            "newValue must be less that current value"
+        )
         node.val = new_value
         self.heap_dict[node.name] = new_value
         self.sift_up(self.idx_of_element[node])
diff --git a/data_structures/kd_tree/tests/test_kdtree.py b/data_structures/kd_tree/tests/test_kdtree.py
index dce5e4f34ff4..d6a4a66dd24d 100644
--- a/data_structures/kd_tree/tests/test_kdtree.py
+++ b/data_structures/kd_tree/tests/test_kdtree.py
@@ -48,14 +48,14 @@ def test_build_kdtree(num_points, cube_size, num_dimensions, depth, expected_res
         assert kdtree is not None, "Expected a KDNode, got None"
 
         # Check if root has correct dimensions
-        assert (
-            len(kdtree.point) == num_dimensions
-        ), f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}"
+        assert len(kdtree.point) == num_dimensions, (
+            f"Expected point dimension {num_dimensions}, got {len(kdtree.point)}"
+        )
 
         # Check that the tree is balanced to some extent (simplistic check)
-        assert isinstance(
-            kdtree, KDNode
-        ), f"Expected KDNode instance, got {type(kdtree)}"
+        assert isinstance(kdtree, KDNode), (
+            f"Expected KDNode instance, got {type(kdtree)}"
+        )
 
 
 def test_nearest_neighbour_search():
diff --git a/data_structures/suffix_tree/tests/test_suffix_tree.py b/data_structures/suffix_tree/tests/test_suffix_tree.py
index 45c6790ac48a..c9dbe199d19d 100644
--- a/data_structures/suffix_tree/tests/test_suffix_tree.py
+++ b/data_structures/suffix_tree/tests/test_suffix_tree.py
@@ -22,18 +22,18 @@ def test_search_existing_patterns(self) -> None:
         patterns = ["ana", "ban", "na"]
         for pattern in patterns:
             with self.subTest(pattern=pattern):
-                assert self.suffix_tree.search(
-                    pattern
-                ), f"Pattern '{pattern}' should be found."
+                assert self.suffix_tree.search(pattern), (
+                    f"Pattern '{pattern}' should be found."
+                )
 
     def test_search_non_existing_patterns(self) -> None:
         """Test searching for patterns that do not exist in the suffix tree."""
         patterns = ["xyz", "apple", "cat"]
         for pattern in patterns:
             with self.subTest(pattern=pattern):
-                assert not self.suffix_tree.search(
-                    pattern
-                ), f"Pattern '{pattern}' should not be found."
+                assert not self.suffix_tree.search(pattern), (
+                    f"Pattern '{pattern}' should not be found."
+                )
 
     def test_search_empty_pattern(self) -> None:
         """Test searching for an empty pattern."""
@@ -41,18 +41,18 @@ def test_search_empty_pattern(self) -> None:
 
     def test_search_full_text(self) -> None:
         """Test searching for the full text."""
-        assert self.suffix_tree.search(
-            self.text
-        ), "The full text should be found in the suffix tree."
+        assert self.suffix_tree.search(self.text), (
+            "The full text should be found in the suffix tree."
+        )
 
     def test_search_substrings(self) -> None:
         """Test searching for substrings of the full text."""
         substrings = ["ban", "ana", "a", "na"]
         for substring in substrings:
             with self.subTest(substring=substring):
-                assert self.suffix_tree.search(
-                    substring
-                ), f"Substring '{substring}' should be found."
+                assert self.suffix_tree.search(substring), (
+                    f"Substring '{substring}' should be found."
+                )
 
 
 if __name__ == "__main__":
diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py
index d6273d025f08..38bdb427eedc 100644
--- a/dynamic_programming/climbing_stairs.py
+++ b/dynamic_programming/climbing_stairs.py
@@ -25,9 +25,9 @@ def climb_stairs(number_of_steps: int) -> int:
         ...
     AssertionError: number_of_steps needs to be positive integer, your input -7
     """
-    assert (
-        isinstance(number_of_steps, int) and number_of_steps > 0
-    ), f"number_of_steps needs to be positive integer, your input {number_of_steps}"
+    assert isinstance(number_of_steps, int) and number_of_steps > 0, (
+        f"number_of_steps needs to be positive integer, your input {number_of_steps}"
+    )
     if number_of_steps == 1:
         return 1
     previous, current = 1, 1
diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py
index 372dd2c74a71..efab6dacff3f 100644
--- a/dynamic_programming/iterating_through_submasks.py
+++ b/dynamic_programming/iterating_through_submasks.py
@@ -37,9 +37,9 @@ def list_of_submasks(mask: int) -> list[int]:
 
     """
 
-    assert (
-        isinstance(mask, int) and mask > 0
-    ), f"mask needs to be positive integer, your input {mask}"
+    assert isinstance(mask, int) and mask > 0, (
+        f"mask needs to be positive integer, your input {mask}"
+    )
 
     """
     first submask iterated will be mask itself then operation will be performed
diff --git a/dynamic_programming/matrix_chain_multiplication.py b/dynamic_programming/matrix_chain_multiplication.py
index 10e136b9f0db..4c0c771f9092 100644
--- a/dynamic_programming/matrix_chain_multiplication.py
+++ b/dynamic_programming/matrix_chain_multiplication.py
@@ -134,7 +134,7 @@ def elapsed_time(msg: str) -> Iterator:
 
     start = perf_counter_ns()
     yield
-    print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10 ** 9} seconds.")
+    print(f"Finished: {msg} in {(perf_counter_ns() - start) / 10**9} seconds.")
 
 
 if __name__ == "__main__":
diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py
index 86f28aef671a..8528ccbbae51 100644
--- a/machine_learning/linear_discriminant_analysis.py
+++ b/machine_learning/linear_discriminant_analysis.py
@@ -322,7 +322,7 @@ def main():
             user_count = valid_input(
                 input_type=int,
                 condition=lambda x: x > 0,
-                input_msg=(f"Enter The number of instances for class_{i+1}: "),
+                input_msg=(f"Enter The number of instances for class_{i + 1}: "),
                 err_msg="Number of instances should be positive!",
             )
             counts.append(user_count)
@@ -333,7 +333,7 @@ def main():
         for a in range(n_classes):
             user_mean = valid_input(
                 input_type=float,
-                input_msg=(f"Enter the value of mean for class_{a+1}: "),
+                input_msg=(f"Enter the value of mean for class_{a + 1}: "),
                 err_msg="This is an invalid value.",
             )
             user_means.append(user_mean)
diff --git a/maths/dual_number_automatic_differentiation.py b/maths/dual_number_automatic_differentiation.py
index f98997c8be4d..09aeb17a4aea 100644
--- a/maths/dual_number_automatic_differentiation.py
+++ b/maths/dual_number_automatic_differentiation.py
@@ -17,10 +17,8 @@ def __init__(self, real, rank):
             self.duals = rank
 
     def __repr__(self):
-        return (
-            f"{self.real}+"
-            f"{'+'.join(str(dual)+'E'+str(n+1)for n,dual in enumerate(self.duals))}"
-        )
+        s = "+".join(f"{dual}E{n}" for n, dual in enumerate(self.duals, 1))
+        return f"{self.real}+{s}"
 
     def reduce(self):
         cur = self.duals.copy()
diff --git a/maths/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py
index 090117429604..c7492978a6c9 100644
--- a/maths/max_sum_sliding_window.py
+++ b/maths/max_sum_sliding_window.py
@@ -43,4 +43,6 @@ def max_sum_in_array(array: list[int], k: int) -> int:
     testmod()
     array = [randint(-1000, 1000) for i in range(100)]
     k = randint(0, 110)
-    print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}")
+    print(
+        f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array, k)}"
+    )
diff --git a/maths/numerical_analysis/integration_by_simpson_approx.py b/maths/numerical_analysis/integration_by_simpson_approx.py
index 934299997aac..043f3a9a72af 100644
--- a/maths/numerical_analysis/integration_by_simpson_approx.py
+++ b/maths/numerical_analysis/integration_by_simpson_approx.py
@@ -88,18 +88,18 @@ def simpson_integration(function, a: float, b: float, precision: int = 4) -> flo
     AssertionError: precision should be positive integer your input : -1
 
     """
-    assert callable(
-        function
-    ), f"the function(object) passed should be callable your input : {function}"
+    assert callable(function), (
+        f"the function(object) passed should be callable your input : {function}"
+    )
     assert isinstance(a, (float, int)), f"a should be float or integer your input : {a}"
     assert isinstance(function(a), (float, int)), (
         "the function should return integer or float return type of your function, "
         f"{type(a)}"
     )
     assert isinstance(b, (float, int)), f"b should be float or integer your input : {b}"
-    assert (
-        isinstance(precision, int) and precision > 0
-    ), f"precision should be positive integer your input : {precision}"
+    assert isinstance(precision, int) and precision > 0, (
+        f"precision should be positive integer your input : {precision}"
+    )
 
     # just applying the formula of simpson for approximate integration written in
     # mentioned article in first comment of this file and above this function
diff --git a/maths/prime_check.py b/maths/prime_check.py
index f1bc4def2469..a757c4108f24 100644
--- a/maths/prime_check.py
+++ b/maths/prime_check.py
@@ -73,12 +73,12 @@ def test_primes(self):
     def test_not_primes(self):
         with pytest.raises(ValueError):
             is_prime(-19)
-        assert not is_prime(
-            0
-        ), "Zero doesn't have any positive factors, primes must have exactly two."
-        assert not is_prime(
-            1
-        ), "One only has 1 positive factor, primes must have exactly two."
+        assert not is_prime(0), (
+            "Zero doesn't have any positive factors, primes must have exactly two."
+        )
+        assert not is_prime(1), (
+            "One only has 1 positive factor, primes must have exactly two."
+        )
         assert not is_prime(2 * 2)
         assert not is_prime(2 * 3)
         assert not is_prime(3 * 3)
diff --git a/maths/primelib.py b/maths/primelib.py
index a26b0eaeb328..3a966e5cd936 100644
--- a/maths/primelib.py
+++ b/maths/primelib.py
@@ -66,9 +66,9 @@ def is_prime(number: int) -> bool:
     """
 
     # precondition
-    assert isinstance(number, int) and (
-        number >= 0
-    ), "'number' must been an int and positive"
+    assert isinstance(number, int) and (number >= 0), (
+        "'number' must been an int and positive"
+    )
 
     status = True
 
@@ -254,9 +254,9 @@ def greatest_prime_factor(number):
     """
 
     # precondition
-    assert isinstance(number, int) and (
-        number >= 0
-    ), "'number' must been an int and >= 0"
+    assert isinstance(number, int) and (number >= 0), (
+        "'number' must been an int and >= 0"
+    )
 
     ans = 0
 
@@ -296,9 +296,9 @@ def smallest_prime_factor(number):
     """
 
     # precondition
-    assert isinstance(number, int) and (
-        number >= 0
-    ), "'number' must been an int and >= 0"
+    assert isinstance(number, int) and (number >= 0), (
+        "'number' must been an int and >= 0"
+    )
 
     ans = 0
 
@@ -399,9 +399,9 @@ def goldbach(number):
     """
 
     # precondition
-    assert (
-        isinstance(number, int) and (number > 2) and is_even(number)
-    ), "'number' must been an int, even and > 2"
+    assert isinstance(number, int) and (number > 2) and is_even(number), (
+        "'number' must been an int, even and > 2"
+    )
 
     ans = []  # this list will returned
 
@@ -525,9 +525,9 @@ def kg_v(number1, number2):
             done.append(n)
 
     # precondition
-    assert isinstance(ans, int) and (
-        ans >= 0
-    ), "'ans' must been from type int and positive"
+    assert isinstance(ans, int) and (ans >= 0), (
+        "'ans' must been from type int and positive"
+    )
 
     return ans
 
@@ -574,9 +574,9 @@ def get_prime(n):
             ans += 1
 
     # precondition
-    assert isinstance(ans, int) and is_prime(
-        ans
-    ), "'ans' must been a prime number and from type int"
+    assert isinstance(ans, int) and is_prime(ans), (
+        "'ans' must been a prime number and from type int"
+    )
 
     return ans
 
@@ -705,9 +705,9 @@ def is_perfect_number(number):
     """
 
     # precondition
-    assert isinstance(number, int) and (
-        number > 1
-    ), "'number' must been an int and >= 1"
+    assert isinstance(number, int) and (number > 1), (
+        "'number' must been an int and >= 1"
+    )
 
     divisors = get_divisors(number)
 
diff --git a/matrix/matrix_based_game.py b/matrix/matrix_based_game.py
index 1ff0cbe93435..6181086c6704 100644
--- a/matrix/matrix_based_game.py
+++ b/matrix/matrix_based_game.py
@@ -273,7 +273,7 @@ def process_game(size: int, matrix: list[str], moves: list[tuple[int, int]]) ->
         size = int(input("Enter the size of the matrix: "))
         validate_matrix_size(size)
         print(f"Enter the {size} rows of the matrix:")
-        matrix = [input(f"Row {i+1}: ") for i in range(size)]
+        matrix = [input(f"Row {i + 1}: ") for i in range(size)]
         validate_matrix_content(matrix, size)
         moves_input = input("Enter the moves (e.g., '0 0, 1 1'): ")
         moves = parse_moves(moves_input)
diff --git a/neural_network/input_data.py b/neural_network/input_data.py
index 72debabb566a..3a8628f939f8 100644
--- a/neural_network/input_data.py
+++ b/neural_network/input_data.py
@@ -160,9 +160,9 @@ def __init__(
             self._num_examples = 10000
             self.one_hot = one_hot
         else:
-            assert (
-                images.shape[0] == labels.shape[0]
-            ), f"images.shape: {images.shape} labels.shape: {labels.shape}"
+            assert images.shape[0] == labels.shape[0], (
+                f"images.shape: {images.shape} labels.shape: {labels.shape}"
+            )
             self._num_examples = images.shape[0]
 
             # Convert shape from [num examples, rows, columns, depth]
diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py
index 325c245e0d77..df5d01086bbe 100755
--- a/scripts/validate_solutions.py
+++ b/scripts/validate_solutions.py
@@ -94,6 +94,6 @@ def test_project_euler(solution_path: pathlib.Path) -> None:
     solution_module = convert_path_to_module(solution_path)
     answer = str(solution_module.solution())
     answer = hashlib.sha256(answer.encode()).hexdigest()
-    assert (
-        answer == expected
-    ), f"Expected solution to {problem_number} to have hash {expected}, got {answer}"
+    assert answer == expected, (
+        f"Expected solution to {problem_number} to have hash {expected}, got {answer}"
+    )
diff --git a/strings/jaro_winkler.py b/strings/jaro_winkler.py
index cae2068fabc1..0ce5d83b3c41 100644
--- a/strings/jaro_winkler.py
+++ b/strings/jaro_winkler.py
@@ -33,7 +33,9 @@ def get_matched_characters(_str1: str, _str2: str) -> str:
             right = int(min(i + limit + 1, len(_str2)))
             if char in _str2[left:right]:
                 matched.append(char)
-                _str2 = f"{_str2[0:_str2.index(char)]} {_str2[_str2.index(char) + 1:]}"
+                _str2 = (
+                    f"{_str2[0 : _str2.index(char)]} {_str2[_str2.index(char) + 1 :]}"
+                )
 
         return "".join(matched)
 
diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py
index fd7c3a3a7381..e56b7124eeb5 100644
--- a/web_programming/fetch_anime_and_play.py
+++ b/web_programming/fetch_anime_and_play.py
@@ -165,7 +165,7 @@ def get_anime_episode(episode_endpoint: str) -> list:
         print(f"Found {len(anime_list)} results: ")
         for i, anime in enumerate(anime_list):
             anime_title = anime["title"]
-            print(f"{i+1}. {anime_title}")
+            print(f"{i + 1}. {anime_title}")
 
         anime_choice = int(input("\nPlease choose from the following list: ").strip())
         chosen_anime = anime_list[anime_choice - 1]
@@ -177,7 +177,7 @@ def get_anime_episode(episode_endpoint: str) -> list:
         else:
             print(f"Found {len(episode_list)} results: ")
             for i, episode in enumerate(episode_list):
-                print(f"{i+1}. {episode['title']}")
+                print(f"{i + 1}. {episode['title']}")
 
             episode_choice = int(input("\nChoose an episode by serial no: ").strip())
             chosen_episode = episode_list[episode_choice - 1]

From f04d308431266759dce36265d8701dfb106932af Mon Sep 17 00:00:00 2001
From: Sanjay Muthu <sanjaymuthu12@gmail.com>
Date: Wed, 15 Jan 2025 02:19:04 +0530
Subject: [PATCH 057/135] Create longest_increasing_subsequence_iterative.py
 (#12524)

* Create longest_increasing_subsequence_iterative.py

* Update longest_increasing_subsequence_iterative.py

* Update longest_increasing_subsequence_iterative.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 ...ongest_increasing_subsequence_iterative.py | 72 +++++++++++++++++++
 1 file changed, 72 insertions(+)
 create mode 100644 dynamic_programming/longest_increasing_subsequence_iterative.py

diff --git a/dynamic_programming/longest_increasing_subsequence_iterative.py b/dynamic_programming/longest_increasing_subsequence_iterative.py
new file mode 100644
index 000000000000..665c86a35d2e
--- /dev/null
+++ b/dynamic_programming/longest_increasing_subsequence_iterative.py
@@ -0,0 +1,72 @@
+"""
+Author  : Sanjay Muthu <https://github.com/XenoBytesX>
+
+This is a pure Python implementation of Dynamic Programming solution to the longest
+increasing subsequence of a given sequence.
+
+The problem is:
+    Given an array, to find the longest and increasing sub-array in that given array and
+    return it.
+
+Example:
+    ``[10, 22, 9, 33, 21, 50, 41, 60, 80]`` as input will return
+    ``[10, 22, 33, 50, 60, 80]`` as output
+"""
+
+from __future__ import annotations
+
+import copy
+
+
+def longest_subsequence(array: list[int]) -> list[int]:
+    """
+    Some examples
+
+    >>> longest_subsequence([10, 22, 9, 33, 21, 50, 41, 60, 80])
+    [10, 22, 33, 50, 60, 80]
+    >>> longest_subsequence([4, 8, 7, 5, 1, 12, 2, 3, 9])
+    [1, 2, 3, 9]
+    >>> longest_subsequence([9, 8, 7, 6, 5, 7])
+    [7, 7]
+    >>> longest_subsequence([28, 26, 12, 23, 35, 39])
+    [12, 23, 35, 39]
+    >>> longest_subsequence([1, 1, 1])
+    [1, 1, 1]
+    >>> longest_subsequence([])
+    []
+    """
+    n = len(array)
+    # The longest increasing subsequence ending at array[i]
+    longest_increasing_subsequence = []
+    for i in range(n):
+        longest_increasing_subsequence.append([array[i]])
+
+    for i in range(1, n):
+        for prev in range(i):
+            # If array[prev] is less than or equal to array[i], then
+            # longest_increasing_subsequence[prev] + array[i]
+            # is a valid increasing subsequence
+
+            # longest_increasing_subsequence[i] is only set to
+            # longest_increasing_subsequence[prev] + array[i] if the length is longer.
+
+            if array[prev] <= array[i] and len(
+                longest_increasing_subsequence[prev]
+            ) + 1 > len(longest_increasing_subsequence[i]):
+                longest_increasing_subsequence[i] = copy.copy(
+                    longest_increasing_subsequence[prev]
+                )
+                longest_increasing_subsequence[i].append(array[i])
+
+    result: list[int] = []
+    for i in range(n):
+        if len(longest_increasing_subsequence[i]) > len(result):
+            result = longest_increasing_subsequence[i]
+
+    return result
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()

From 0040ad47f928f299dadbb97c5cea00bc1daf8c75 Mon Sep 17 00:00:00 2001
From: aydinomer00 <109145643+aydinomer00@users.noreply.github.com>
Date: Wed, 15 Jan 2025 00:24:36 +0300
Subject: [PATCH 058/135] Add butterfly pattern implementation (#12493)

* Add butterfly pattern implementation

* Add butterfly pattern implementation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add finalized butterfly pattern implementation and test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Delete graphics/test_butterfly_pattern.py

* Update butterfly_pattern.py

* Update butterfly_pattern.py

* Update butterfly_pattern.py

* Update butterfly_pattern.py

* Update butterfly_pattern.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 graphics/butterfly_pattern.py | 46 +++++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
 create mode 100644 graphics/butterfly_pattern.py

diff --git a/graphics/butterfly_pattern.py b/graphics/butterfly_pattern.py
new file mode 100644
index 000000000000..7913b03a7e95
--- /dev/null
+++ b/graphics/butterfly_pattern.py
@@ -0,0 +1,46 @@
+def butterfly_pattern(n: int) -> str:
+    """
+    Creates a butterfly pattern of size n and returns it as a string.
+
+    >>> print(butterfly_pattern(3))
+    *   *
+    ** **
+    *****
+    ** **
+    *   *
+    >>> print(butterfly_pattern(5))
+    *       *
+    **     **
+    ***   ***
+    **** ****
+    *********
+    **** ****
+    ***   ***
+    **     **
+    *       *
+    """
+    result = []
+
+    # Upper part
+    for i in range(1, n):
+        left_stars = "*" * i
+        spaces = " " * (2 * (n - i) - 1)
+        right_stars = "*" * i
+        result.append(left_stars + spaces + right_stars)
+
+    # Middle part
+    result.append("*" * (2 * n - 1))
+
+    # Lower part
+    for i in range(n - 1, 0, -1):
+        left_stars = "*" * i
+        spaces = " " * (2 * (n - i) - 1)
+        right_stars = "*" * i
+        result.append(left_stars + spaces + right_stars)
+
+    return "\n".join(result)
+
+
+if __name__ == "__main__":
+    n = int(input("Enter the size of the butterfly pattern: "))
+    print(butterfly_pattern(n))

From 533767ff46bbcf5c594ff8196894ae2e8130bc3e Mon Sep 17 00:00:00 2001
From: Nguyen Thi Thanh Minh
 <140883075+minh-swinburne@users.noreply.github.com>
Date: Sat, 18 Jan 2025 10:07:44 +0700
Subject: [PATCH 059/135] Doomsday Algorithm: Fix leap year check (#12396)

* Fix leap year check

Replace `!=` in `(year % 400) != 0` (line 49) with `==`

Justification: Years that are divisible by 100 (centurian == 100) but not by 400 (year % 400 != 0) are skipped and NOT leap year.

* Update parentheses

Correct the parentheses to make clear the precedence of the conditional check

* Update other/doomsday.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
---
 other/doomsday.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/other/doomsday.py b/other/doomsday.py
index d8fe261156a1..be3b18eeecaa 100644
--- a/other/doomsday.py
+++ b/other/doomsday.py
@@ -46,7 +46,7 @@ def get_week_day(year: int, month: int, day: int) -> str:
     ) % 7
     day_anchor = (
         DOOMSDAY_NOT_LEAP[month - 1]
-        if (year % 4 != 0) or (centurian == 0 and (year % 400) == 0)
+        if year % 4 != 0 or (centurian == 0 and year % 400 != 0)
         else DOOMSDAY_LEAP[month - 1]
     )
     week_day = (dooms_day + day - day_anchor) % 7

From 91ebea1d99735ee2798b01ebcea0fc06e9a6af49 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Sun, 19 Jan 2025 08:33:35 +0100
Subject: [PATCH 060/135] Sphinx runs on ubuntu 24.04 arm (#12530)

* Speed up our Sphinx GitHub Action with ARM

# `runs-on: ubuntu-24.04-arm`
https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources

* updating DIRECTORY.md

---------

Co-authored-by: cclauss <cclauss@users.noreply.github.com>
---
 .github/workflows/sphinx.yml | 2 +-
 DIRECTORY.md                 | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index d02435d98028..16ff284a74f2 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -23,7 +23,7 @@ concurrency:
 
 jobs:
   build_docs:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-24.04-arm
     steps:
       - uses: actions/checkout@v4
       - uses: astral-sh/setup-uv@v5
diff --git a/DIRECTORY.md b/DIRECTORY.md
index aad6c72aa8ee..941e30dfe721 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -377,6 +377,7 @@
   * [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py)
   * [Longest Common Substring](dynamic_programming/longest_common_substring.py)
   * [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py)
+  * [Longest Increasing Subsequence Iterative](dynamic_programming/longest_increasing_subsequence_iterative.py)
   * [Longest Increasing Subsequence O Nlogn](dynamic_programming/longest_increasing_subsequence_o_nlogn.py)
   * [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py)
   * [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py)
@@ -462,6 +463,7 @@
 
 ## Graphics
   * [Bezier Curve](graphics/bezier_curve.py)
+  * [Butterfly Pattern](graphics/butterfly_pattern.py)
   * [Digital Differential Analyzer Line](graphics/digital_differential_analyzer_line.py)
   * [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py)
 

From 1f74db0c06df7557e7ae3a17ebcc303f753f824e Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 20 Jan 2025 21:22:02 +0100
Subject: [PATCH 061/135] [pre-commit.ci] pre-commit autoupdate (#12536)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.1 → v0.9.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.1...v0.9.2)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3b1dd9658d7f..c4480f47faa1 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.1
+    rev: v0.9.2
     hooks:
       - id: ruff
       - id: ruff-format

From 9fb51b4169e0f7a4952e9eb460b91f4d7ffb819f Mon Sep 17 00:00:00 2001
From: Ronald Ngounou <74538524+ronaldngounou@users.noreply.github.com>
Date: Thu, 23 Jan 2025 09:02:46 +0100
Subject: [PATCH 062/135] Update docstrings in the functions definitions.
 (#11797)

---
 data_structures/arrays/sudoku_solver.py | 61 +++++++++++++++++--------
 1 file changed, 43 insertions(+), 18 deletions(-)

diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py
index fd1a4f3e37b8..e1714e57ece8 100644
--- a/data_structures/arrays/sudoku_solver.py
+++ b/data_structures/arrays/sudoku_solver.py
@@ -9,7 +9,9 @@
 
 
 def cross(items_a, items_b):
-    "Cross product of elements in A and elements in B."
+    """
+    Cross product of elements in A and elements in B.
+    """
     return [a + b for a in items_a for b in items_b]
 
 
@@ -27,7 +29,7 @@ def cross(items_a, items_b):
 
 
 def test():
-    "A set of unit tests."
+    """A set of unit tests."""
     assert len(squares) == 81
     assert len(unitlist) == 27
     assert all(len(units[s]) == 3 for s in squares)
@@ -47,8 +49,10 @@ def test():
 
 
 def parse_grid(grid):
-    """Convert grid to a dict of possible values, {square: digits}, or
-    return False if a contradiction is detected."""
+    """
+    Convert grid to a dict of possible values, {square: digits}, or
+    return False if a contradiction is detected.
+    """
     ## To start, every square can be any digit; then assign values from the grid.
     values = {s: digits for s in squares}
     for s, d in grid_values(grid).items():
@@ -58,15 +62,19 @@ def parse_grid(grid):
 
 
 def grid_values(grid):
-    "Convert grid into a dict of {square: char} with '0' or '.' for empties."
+    """
+    Convert grid into a dict of {square: char} with '0' or '.' for empties.
+    """
     chars = [c for c in grid if c in digits or c in "0."]
     assert len(chars) == 81
     return dict(zip(squares, chars))
 
 
 def assign(values, s, d):
-    """Eliminate all the other values (except d) from values[s] and propagate.
-    Return values, except return False if a contradiction is detected."""
+    """
+    Eliminate all the other values (except d) from values[s] and propagate.
+    Return values, except return False if a contradiction is detected.
+    """
     other_values = values[s].replace(d, "")
     if all(eliminate(values, s, d2) for d2 in other_values):
         return values
@@ -75,8 +83,10 @@ def assign(values, s, d):
 
 
 def eliminate(values, s, d):
-    """Eliminate d from values[s]; propagate when values or places <= 2.
-    Return values, except return False if a contradiction is detected."""
+    """
+    Eliminate d from values[s]; propagate when values or places <= 2.
+    Return values, except return False if a contradiction is detected.
+    """
     if d not in values[s]:
         return values  ## Already eliminated
     values[s] = values[s].replace(d, "")
@@ -99,7 +109,9 @@ def eliminate(values, s, d):
 
 
 def display(values):
-    "Display these values as a 2-D grid."
+    """
+    Display these values as a 2-D grid.
+    """
     width = 1 + max(len(values[s]) for s in squares)
     line = "+".join(["-" * (width * 3)] * 3)
     for r in rows:
@@ -114,11 +126,14 @@ def display(values):
 
 
 def solve(grid):
+    """
+    Solve the grid.
+    """
     return search(parse_grid(grid))
 
 
 def some(seq):
-    "Return some element of seq that is true."
+    """Return some element of seq that is true."""
     for e in seq:
         if e:
             return e
@@ -126,7 +141,9 @@ def some(seq):
 
 
 def search(values):
-    "Using depth-first search and propagation, try all possible values."
+    """
+    Using depth-first search and propagation, try all possible values.
+    """
     if values is False:
         return False  ## Failed earlier
     if all(len(values[s]) == 1 for s in squares):
@@ -137,9 +154,11 @@ def search(values):
 
 
 def solve_all(grids, name="", showif=0.0):
-    """Attempt to solve a sequence of grids. Report results.
+    """
+    Attempt to solve a sequence of grids. Report results.
     When showif is a number of seconds, display puzzles that take longer.
-    When showif is None, don't display any puzzles."""
+    When showif is None, don't display any puzzles.
+    """
 
     def time_solve(grid):
         start = time.monotonic()
@@ -162,7 +181,9 @@ def time_solve(grid):
 
 
 def solved(values):
-    "A puzzle is solved if each unit is a permutation of the digits 1 to 9."
+    """
+    A puzzle is solved if each unit is a permutation of the digits 1 to 9.
+    """
 
     def unitsolved(unit):
         return {values[s] for s in unit} == set(digits)
@@ -177,9 +198,11 @@ def from_file(filename, sep="\n"):
 
 
 def random_puzzle(assignments=17):
-    """Make a random puzzle with N or more assignments. Restart on contradictions.
+    """
+    Make a random puzzle with N or more assignments. Restart on contradictions.
     Note the resulting puzzle is not guaranteed to be solvable, but empirically
-    about 99.8% of them are solvable. Some have multiple solutions."""
+    about 99.8% of them are solvable. Some have multiple solutions.
+    """
     values = {s: digits for s in squares}
     for s in shuffled(squares):
         if not assign(values, s, random.choice(values[s])):
@@ -191,7 +214,9 @@ def random_puzzle(assignments=17):
 
 
 def shuffled(seq):
-    "Return a randomly shuffled copy of the input sequence."
+    """
+    Return a randomly shuffled copy of the input sequence.
+    """
     seq = list(seq)
     random.shuffle(seq)
     return seq

From c666db3729b6d9f73e2f7756a3974f53279caa50 Mon Sep 17 00:00:00 2001
From: Vijayalaxmi Wakode <vijayalaxmiwakode@gmail.com>
Date: Fri, 24 Jan 2025 03:31:47 +0530
Subject: [PATCH 063/135] Add Doc test bubble sort (#12070)

* The string manipulation - replace()

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update replace.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* updating DIRECTORY.md

* Add doc test to bubble_sort

* Update DIRECTORY.md

* Delete strings/replace.py

* Update bubble_sort.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: vijayalaxmi777 <vijayalaxmi777@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 sorts/bubble_sort.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/sorts/bubble_sort.py b/sorts/bubble_sort.py
index bdf85c70dd35..9ec3d5384f38 100644
--- a/sorts/bubble_sort.py
+++ b/sorts/bubble_sort.py
@@ -85,6 +85,8 @@ def bubble_sort_recursive(collection: list[Any]) -> list[Any]:
     [1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7]
     >>> bubble_sort_recursive([1, 3.3, 5, 7.7, 2, 4.4, 6])
     [1, 2, 3.3, 4.4, 5, 6, 7.7]
+    >>> bubble_sort_recursive(['a', 'Z', 'B', 'C', 'A', 'c'])
+    ['A', 'B', 'C', 'Z', 'a', 'c']
     >>> import random
     >>> collection_arg = random.sample(range(-50, 50), 100)
     >>> bubble_sort_recursive(collection_arg) == sorted(collection_arg)

From 13e4d3e76cfaa74d8b14314d319fb6c089aa051e Mon Sep 17 00:00:00 2001
From: Rachel Spears <103690982+Rosepetal2022@users.noreply.github.com>
Date: Thu, 23 Jan 2025 21:59:36 -0800
Subject: [PATCH 064/135] Fix error in avl_tree del_node function (#11510)

* fixed error in del_node function

* Update avl_tree.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 data_structures/binary_tree/avl_tree.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py
index 9fca7237404c..8558305eefe4 100644
--- a/data_structures/binary_tree/avl_tree.py
+++ b/data_structures/binary_tree/avl_tree.py
@@ -221,6 +221,10 @@ def del_node(root: MyNode, data: Any) -> MyNode | None:
     else:
         root.set_right(del_node(right_child, data))
 
+    # Re-fetch left_child and right_child references
+    left_child = root.get_left()
+    right_child = root.get_right()
+
     if get_height(right_child) - get_height(left_child) == 2:
         assert right_child is not None
         if get_height(right_child.get_right()) > get_height(right_child.get_left()):

From 6c92c5a539276d387b85eedc89be1f888962647d Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 27 Jan 2025 22:05:20 +0100
Subject: [PATCH 065/135] [pre-commit.ci] pre-commit autoupdate (#12542)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.2 → v0.9.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.2...v0.9.3)
- [github.com/codespell-project/codespell: v2.3.0 → v2.4.0](https://github.com/codespell-project/codespell/compare/v2.3.0...v2.4.0)

* Update trifid_cipher.py

* Update pyproject.toml

* Update trifid_cipher.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml  | 4 ++--
 ciphers/trifid_cipher.py | 4 ++--
 pyproject.toml           | 2 +-
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c4480f47faa1..e34b563b05dd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,13 +16,13 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.2
+    rev: v0.9.3
     hooks:
       - id: ruff
       - id: ruff-format
 
   - repo: https://github.com/codespell-project/codespell
-    rev: v2.3.0
+    rev: v2.4.0
     hooks:
       - id: codespell
         additional_dependencies:
diff --git a/ciphers/trifid_cipher.py b/ciphers/trifid_cipher.py
index 9613cee0669d..13a47e9dd03b 100644
--- a/ciphers/trifid_cipher.py
+++ b/ciphers/trifid_cipher.py
@@ -88,7 +88,7 @@ def __prepare(
         ...
     KeyError: 'Length of alphabet has to be 27.'
 
-    Testing with punctuations that are not in the given alphabet
+    Testing with punctuation not in the given alphabet
 
     >>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+')
     Traceback (most recent call last):
@@ -128,7 +128,7 @@ def encrypt_message(
     encrypt_message
     ===============
 
-    Encrypts a message using the trifid_cipher. Any punctuatuions that
+    Encrypts a message using the trifid_cipher. Any punctuatuion chars that
     would be used should be added to the alphabet.
 
     PARAMETERS
diff --git a/pyproject.toml b/pyproject.toml
index 7b7176705c44..2135f1f5825a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -159,7 +159,7 @@ lint.pylint.max-returns = 8 # default: 6
 lint.pylint.max-statements = 88 # default: 50
 
 [tool.codespell]
-ignore-words-list = "3rt,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar"
+ignore-words-list = "3rt,abd,aer,ans,bitap,crate,damon,fo,followings,hist,iff,kwanza,manuel,mater,secant,som,sur,tim,toi,zar"
 skip = "./.*,*.json,*.lock,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt"
 
 [tool.pytest.ini_options]

From e59d819d091efdb30e385f4ecfe9ab5d36c3be71 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Wed, 5 Feb 2025 20:47:41 +0100
Subject: [PATCH 066/135] [pre-commit.ci] pre-commit autoupdate (#12554)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.3 → v0.9.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.3...v0.9.4)
- [github.com/codespell-project/codespell: v2.4.0 → v2.4.1](https://github.com/codespell-project/codespell/compare/v2.4.0...v2.4.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e34b563b05dd..d9477e216b96 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,13 +16,13 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.3
+    rev: v0.9.4
     hooks:
       - id: ruff
       - id: ruff-format
 
   - repo: https://github.com/codespell-project/codespell
-    rev: v2.4.0
+    rev: v2.4.1
     hooks:
       - id: codespell
         additional_dependencies:

From 338cbafe0d5b07d57f83060ea0f9ba3a6c1155e7 Mon Sep 17 00:00:00 2001
From: lighting9999 <120090117+lighting9999@users.noreply.github.com>
Date: Mon, 10 Feb 2025 01:51:18 +0800
Subject: [PATCH 067/135] Improve power.py (#12567)

* Fix And Add power.py

To fix the inaccuracies and allow handling of negative exponents and bases, the key issue lies in how negative numbers are handled in the power calculation, especially when dividing.
## Example Output:
```python
>>> power(4, 6)
4096
>>> power(2, 3)
8
>>> power(-2, 3)
-8
>>> power(2, -3)
0.125
>>> power(-2, -3)
-0.125
```

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update power.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update power.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update power.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update power.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 divide_and_conquer/power.py | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/divide_and_conquer/power.py b/divide_and_conquer/power.py
index faf6a3476d40..492ee6dd12f0 100644
--- a/divide_and_conquer/power.py
+++ b/divide_and_conquer/power.py
@@ -1,4 +1,4 @@
-def actual_power(a: int, b: int):
+def actual_power(a: int, b: int) -> int:
     """
     Function using divide and conquer to calculate a^b.
     It only works for integer a,b.
@@ -19,10 +19,12 @@ def actual_power(a: int, b: int):
     """
     if b == 0:
         return 1
+    half = actual_power(a, b // 2)
+
     if (b % 2) == 0:
-        return actual_power(a, int(b / 2)) * actual_power(a, int(b / 2))
+        return half * half
     else:
-        return a * actual_power(a, int(b / 2)) * actual_power(a, int(b / 2))
+        return a * half * half
 
 
 def power(a: int, b: int) -> float:
@@ -43,9 +45,9 @@ def power(a: int, b: int) -> float:
     -0.125
     """
     if b < 0:
-        return 1 / actual_power(a, b)
+        return 1 / actual_power(a, -b)
     return actual_power(a, b)
 
 
 if __name__ == "__main__":
-    print(power(-2, -3))
+    print(power(-2, -3))  # output -0.125

From 738253e80030ffdd35ac57ff64cda816f85eda71 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Fri, 14 Feb 2025 10:05:23 +0100
Subject: [PATCH 068/135] git mv data_structures/queue data_structures/queues
 (#12577)

Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml                        |  4 ++--
 DIRECTORY.md                                   | 18 +++++++++---------
 data_structures/{queue => queues}/__init__.py  |  0
 .../{queue => queues}/circular_queue.py        | 10 +++++++---
 .../circular_queue_linked_list.py              |  0
 .../{queue => queues}/double_ended_queue.py    |  0
 .../{queue => queues}/linked_queue.py          |  0
 .../priority_queue_using_list.py               |  6 +++---
 .../{queue => queues}/queue_by_list.py         |  0
 .../{queue => queues}/queue_by_two_stacks.py   |  0
 .../{queue => queues}/queue_on_pseudo_stack.py |  0
 11 files changed, 21 insertions(+), 17 deletions(-)
 rename data_structures/{queue => queues}/__init__.py (100%)
 rename data_structures/{queue => queues}/circular_queue.py (87%)
 rename data_structures/{queue => queues}/circular_queue_linked_list.py (100%)
 rename data_structures/{queue => queues}/double_ended_queue.py (100%)
 rename data_structures/{queue => queues}/linked_queue.py (100%)
 rename data_structures/{queue => queues}/priority_queue_using_list.py (96%)
 rename data_structures/{queue => queues}/queue_by_list.py (100%)
 rename data_structures/{queue => queues}/queue_by_two_stacks.py (100%)
 rename data_structures/{queue => queues}/queue_on_pseudo_stack.py (100%)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d9477e216b96..a603109fd79f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.4
+    rev: v0.9.6
     hooks:
       - id: ruff
       - id: ruff-format
@@ -47,7 +47,7 @@ repos:
       - id: validate-pyproject
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v1.14.1
+    rev: v1.15.0
     hooks:
       - id: mypy
         args:
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 941e30dfe721..a535f12cb59a 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -275,15 +275,15 @@
     * [Singly Linked List](data_structures/linked_list/singly_linked_list.py)
     * [Skip List](data_structures/linked_list/skip_list.py)
     * [Swap Nodes](data_structures/linked_list/swap_nodes.py)
-  * Queue
-    * [Circular Queue](data_structures/queue/circular_queue.py)
-    * [Circular Queue Linked List](data_structures/queue/circular_queue_linked_list.py)
-    * [Double Ended Queue](data_structures/queue/double_ended_queue.py)
-    * [Linked Queue](data_structures/queue/linked_queue.py)
-    * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py)
-    * [Queue By List](data_structures/queue/queue_by_list.py)
-    * [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py)
-    * [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py)
+  * Queues
+    * [Circular Queue](data_structures/queues/circular_queue.py)
+    * [Circular Queue Linked List](data_structures/queues/circular_queue_linked_list.py)
+    * [Double Ended Queue](data_structures/queues/double_ended_queue.py)
+    * [Linked Queue](data_structures/queues/linked_queue.py)
+    * [Priority Queue Using List](data_structures/queues/priority_queue_using_list.py)
+    * [Queue By List](data_structures/queues/queue_by_list.py)
+    * [Queue By Two Stacks](data_structures/queues/queue_by_two_stacks.py)
+    * [Queue On Pseudo Stack](data_structures/queues/queue_on_pseudo_stack.py)
   * Stacks
     * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py)
     * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py)
diff --git a/data_structures/queue/__init__.py b/data_structures/queues/__init__.py
similarity index 100%
rename from data_structures/queue/__init__.py
rename to data_structures/queues/__init__.py
diff --git a/data_structures/queue/circular_queue.py b/data_structures/queues/circular_queue.py
similarity index 87%
rename from data_structures/queue/circular_queue.py
rename to data_structures/queues/circular_queue.py
index f2fb4c01e467..efbf1efdc42d 100644
--- a/data_structures/queue/circular_queue.py
+++ b/data_structures/queues/circular_queue.py
@@ -17,7 +17,9 @@ def __len__(self) -> int:
         >>> len(cq)
         0
         >>> cq.enqueue("A")  # doctest: +ELLIPSIS
-        <data_structures.queue.circular_queue.CircularQueue object at ...
+        <data_structures.queues.circular_queue.CircularQueue object at ...
+        >>> cq.array
+        ['A', None, None, None, None]
         >>> len(cq)
         1
         """
@@ -51,11 +53,13 @@ def enqueue(self, data):
         as an index.
         >>> cq = CircularQueue(5)
         >>> cq.enqueue("A")  # doctest: +ELLIPSIS
-        <data_structures.queue.circular_queue.CircularQueue object at ...
+        <data_structures.queues.circular_queue.CircularQueue object at ...
         >>> (cq.size, cq.first())
         (1, 'A')
         >>> cq.enqueue("B")  # doctest: +ELLIPSIS
-        <data_structures.queue.circular_queue.CircularQueue object at ...
+        <data_structures.queues.circular_queue.CircularQueue object at ...
+        >>> cq.array
+        ['A', 'B', None, None, None]
         >>> (cq.size, cq.first())
         (2, 'A')
         """
diff --git a/data_structures/queue/circular_queue_linked_list.py b/data_structures/queues/circular_queue_linked_list.py
similarity index 100%
rename from data_structures/queue/circular_queue_linked_list.py
rename to data_structures/queues/circular_queue_linked_list.py
diff --git a/data_structures/queue/double_ended_queue.py b/data_structures/queues/double_ended_queue.py
similarity index 100%
rename from data_structures/queue/double_ended_queue.py
rename to data_structures/queues/double_ended_queue.py
diff --git a/data_structures/queue/linked_queue.py b/data_structures/queues/linked_queue.py
similarity index 100%
rename from data_structures/queue/linked_queue.py
rename to data_structures/queues/linked_queue.py
diff --git a/data_structures/queue/priority_queue_using_list.py b/data_structures/queues/priority_queue_using_list.py
similarity index 96%
rename from data_structures/queue/priority_queue_using_list.py
rename to data_structures/queues/priority_queue_using_list.py
index f61b5e8e664d..15e56c557069 100644
--- a/data_structures/queue/priority_queue_using_list.py
+++ b/data_structures/queues/priority_queue_using_list.py
@@ -59,12 +59,12 @@ class FixedPriorityQueue:
     >>> fpq.dequeue()
     Traceback (most recent call last):
         ...
-    data_structures.queue.priority_queue_using_list.UnderFlowError: All queues are empty
+    data_structures.queues.priority_queue_using_list.UnderFlowError: All queues are empty
     >>> print(fpq)
     Priority 0: []
     Priority 1: []
     Priority 2: []
-    """
+    """  # noqa: E501
 
     def __init__(self):
         self.queues = [
@@ -141,7 +141,7 @@ class ElementPriorityQueue:
     >>> epq.dequeue()
     Traceback (most recent call last):
         ...
-    data_structures.queue.priority_queue_using_list.UnderFlowError: The queue is empty
+    data_structures.queues.priority_queue_using_list.UnderFlowError: The queue is empty
     >>> print(epq)
     []
     """
diff --git a/data_structures/queue/queue_by_list.py b/data_structures/queues/queue_by_list.py
similarity index 100%
rename from data_structures/queue/queue_by_list.py
rename to data_structures/queues/queue_by_list.py
diff --git a/data_structures/queue/queue_by_two_stacks.py b/data_structures/queues/queue_by_two_stacks.py
similarity index 100%
rename from data_structures/queue/queue_by_two_stacks.py
rename to data_structures/queues/queue_by_two_stacks.py
diff --git a/data_structures/queue/queue_on_pseudo_stack.py b/data_structures/queues/queue_on_pseudo_stack.py
similarity index 100%
rename from data_structures/queue/queue_on_pseudo_stack.py
rename to data_structures/queues/queue_on_pseudo_stack.py

From a5aed92b4c20fd3e99c6e7a9202afcc9cf502883 Mon Sep 17 00:00:00 2001
From: Maxim Evtush <154841002+maximevtush@users.noreply.github.com>
Date: Thu, 20 Feb 2025 21:09:01 +0100
Subject: [PATCH 069/135] fix: typo in
 data_structures/linked_list/from_sequence.py (#12584)

---
 data_structures/linked_list/from_sequence.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/data_structures/linked_list/from_sequence.py b/data_structures/linked_list/from_sequence.py
index 94b44f15037f..fa43f4d10e08 100644
--- a/data_structures/linked_list/from_sequence.py
+++ b/data_structures/linked_list/from_sequence.py
@@ -1,4 +1,4 @@
-# Recursive Prorgam to create a Linked List from a sequence and
+# Recursive Program to create a Linked List from a sequence and
 # print a string representation of it.
 
 

From 183fa06f40e80c6e86ceda6e7c7d23eaf91507ac Mon Sep 17 00:00:00 2001
From: sector <104625848+infrablue1@users.noreply.github.com>
Date: Sat, 22 Feb 2025 16:16:29 +0800
Subject: [PATCH 070/135] Fix n-queens problem (#12583)

* Fix n-queens problem

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update n_queens.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update n_queens.py

* Update n_queens.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 backtracking/n_queens.py | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py
index 81668b17a0ac..d10181f319b3 100644
--- a/backtracking/n_queens.py
+++ b/backtracking/n_queens.py
@@ -27,21 +27,28 @@ def is_safe(board: list[list[int]], row: int, column: int) -> bool:
 
     >>> is_safe([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
     True
+    >>> is_safe([[0, 1, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
+    False
     >>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
     False
+    >>> is_safe([[0, 0, 1], [0, 0, 0], [0, 0, 0]], 1, 1)
+    False
     """
 
     n = len(board)  # Size of the board
 
-    # Check if there is any queen in the same row, column,
-    # left upper diagonal, and right upper diagonal
+    # Check if there is any queen in the same upper column,
+    # left upper diagonal and right upper diagonal
     return (
-        all(board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, n)))
+        all(board[i][j] != 1 for i, j in zip(range(row), [column] * row))
+        and all(
+            board[i][j] != 1
+            for i, j in zip(range(row - 1, -1, -1), range(column - 1, -1, -1))
+        )
         and all(
-            board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, -1, -1))
+            board[i][j] != 1
+            for i, j in zip(range(row - 1, -1, -1), range(column + 1, n))
         )
-        and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, n)))
-        and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, -1, -1)))
     )
 
 

From 114d4283b98e52396e2460c802f18d45eeacd90c Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 24 Feb 2025 19:27:10 +0100
Subject: [PATCH 071/135] [pre-commit.ci] pre-commit autoupdate (#12591)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.6 → v0.9.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.6...v0.9.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a603109fd79f..8de90b11767f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.6
+    rev: v0.9.7
     hooks:
       - id: ruff
       - id: ruff-format

From f528ce350b366ce40e0494fc94da65cfd4509c7d Mon Sep 17 00:00:00 2001
From: Sanjay Muthu <sanjaymuthu12@gmail.com>
Date: Thu, 27 Feb 2025 17:01:08 +0530
Subject: [PATCH 072/135] Added dynamic_programming/range_sum_query.py (#12592)

* Create prefix_sum.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix pre-commit and ruff errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Rename prefix_sum.py to range_sum_query.py

* Refactor description

* Fix

* Refactor code

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 dynamic_programming/range_sum_query.py | 92 ++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)
 create mode 100644 dynamic_programming/range_sum_query.py

diff --git a/dynamic_programming/range_sum_query.py b/dynamic_programming/range_sum_query.py
new file mode 100644
index 000000000000..484fcf785fda
--- /dev/null
+++ b/dynamic_programming/range_sum_query.py
@@ -0,0 +1,92 @@
+"""
+Author: Sanjay Muthu <https://github.com/XenoBytesX>
+
+This is an implementation of the Dynamic Programming solution to the Range Sum Query.
+
+The problem statement is:
+    Given an array and q queries,
+    each query stating you to find the sum of elements from l to r (inclusive)
+
+Example:
+    arr = [1, 4, 6, 2, 61, 12]
+    queries = 3
+    l_1 = 2, r_1 = 5
+    l_2 = 1, r_2 = 5
+    l_3 = 3, r_3 = 4
+
+    as input will return
+
+    [81, 85, 63]
+
+    as output
+
+0-indexing:
+NOTE: 0-indexing means the indexing of the array starts from 0
+Example: a = [1, 2, 3, 4, 5, 6]
+         Here, the 0th index of a is 1,
+               the 1st index of a is 2,
+               and so forth
+
+Time Complexity: O(N + Q)
+* O(N) pre-calculation time to calculate the prefix sum array
+* and O(1) time per each query = O(1 * Q) = O(Q) time
+
+Space Complexity: O(N)
+* O(N) to store the prefix sum
+
+Algorithm:
+So, first we calculate the prefix sum (dp) of the array.
+The prefix sum of the index i is the sum of all elements indexed
+from 0 to i (inclusive).
+The prefix sum of the index i is the prefix sum of index (i - 1) + the current element.
+So, the state of the dp is dp[i] = dp[i - 1] + a[i].
+
+After we calculate the prefix sum,
+for each query [l, r]
+the answer is dp[r] - dp[l - 1] (we need to be careful because l might be 0).
+For example take this array:
+    [4, 2, 1, 6, 3]
+The prefix sum calculated for this array would be:
+    [4, 4 + 2, 4 + 2 + 1, 4 + 2 + 1 + 6, 4 + 2 + 1 + 6 + 3]
+    ==> [4, 6, 7, 13, 16]
+If the query was l = 3, r = 4,
+the answer would be 6 + 3 = 9 but this would require O(r - l + 1) time ≈ O(N) time
+
+If we use prefix sums we can find it in O(1) by using the formula
+prefix[r] - prefix[l - 1].
+This formula works because prefix[r] is the sum of elements from [0, r]
+and prefix[l - 1] is the sum of elements from [0, l - 1],
+so if we do prefix[r] - prefix[l - 1] it will be
+[0, r] - [0, l - 1] = [0, l - 1] + [l, r] - [0, l - 1] = [l, r]
+"""
+
+
+def prefix_sum(array: list[int], queries: list[tuple[int, int]]) -> list[int]:
+    """
+    >>> prefix_sum([1, 4, 6, 2, 61, 12], [(2, 5), (1, 5), (3, 4)])
+    [81, 85, 63]
+    >>> prefix_sum([4, 2, 1, 6, 3], [(3, 4), (1, 3), (0, 2)])
+    [9, 9, 7]
+    """
+    # The prefix sum array
+    dp = [0] * len(array)
+    dp[0] = array[0]
+    for i in range(1, len(array)):
+        dp[i] = dp[i - 1] + array[i]
+
+    # See Algorithm section (Line 44)
+    result = []
+    for query in queries:
+        left, right = query
+        res = dp[right]
+        if left > 0:
+            res -= dp[left - 1]
+        result.append(res)
+
+    return result
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()

From 8826ad3a4d75f7a4e1d7b1a682682528c2c73672 Mon Sep 17 00:00:00 2001
From: PARIKSHIT SINGH <90330646+parikshit2111@users.noreply.github.com>
Date: Sun, 2 Mar 2025 16:33:12 +0530
Subject: [PATCH 073/135] feat: Implement Principal Component Analysis (PCA)
 (#12596)

- Added PCA implementation with dataset standardization.
- Used Singular Value Decomposition (SVD) for computing principal components.
- Fixed import sorting to comply with PEP 8 (Ruff I001).
- Ensured type hints and docstrings for better readability.
- Added doctests to validate correctness.
- Passed all Ruff checks and automated tests.
---
 DIRECTORY.md                                  |  2 +
 .../principle_component_analysis.py           | 85 +++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 machine_learning/principle_component_analysis.py

diff --git a/DIRECTORY.md b/DIRECTORY.md
index a535f12cb59a..ab3259b9a766 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -395,6 +395,7 @@
   * [Minimum Tickets Cost](dynamic_programming/minimum_tickets_cost.py)
   * [Optimal Binary Search Tree](dynamic_programming/optimal_binary_search_tree.py)
   * [Palindrome Partitioning](dynamic_programming/palindrome_partitioning.py)
+  * [Range Sum Query](dynamic_programming/range_sum_query.py)
   * [Regex Match](dynamic_programming/regex_match.py)
   * [Rod Cutting](dynamic_programming/rod_cutting.py)
   * [Smith Waterman](dynamic_programming/smith_waterman.py)
@@ -608,6 +609,7 @@
   * [Mfcc](machine_learning/mfcc.py)
   * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
   * [Polynomial Regression](machine_learning/polynomial_regression.py)
+  * [Principle Component Analysis](machine_learning/principle_component_analysis.py)
   * [Scoring Functions](machine_learning/scoring_functions.py)
   * [Self Organizing Map](machine_learning/self_organizing_map.py)
   * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py)
diff --git a/machine_learning/principle_component_analysis.py b/machine_learning/principle_component_analysis.py
new file mode 100644
index 000000000000..46ccdb968494
--- /dev/null
+++ b/machine_learning/principle_component_analysis.py
@@ -0,0 +1,85 @@
+"""
+Principal Component Analysis (PCA) is a dimensionality reduction technique
+used in machine learning. It transforms high-dimensional data into a lower-dimensional
+representation while retaining as much variance as possible.
+
+This implementation follows best practices, including:
+- Standardizing the dataset.
+- Computing principal components using Singular Value Decomposition (SVD).
+- Returning transformed data and explained variance ratio.
+"""
+
+import doctest
+
+import numpy as np
+from sklearn.datasets import load_iris
+from sklearn.decomposition import PCA
+from sklearn.preprocessing import StandardScaler
+
+
+def collect_dataset() -> tuple[np.ndarray, np.ndarray]:
+    """
+    Collects the dataset (Iris dataset) and returns feature matrix and target values.
+
+    :return: Tuple containing feature matrix (X) and target labels (y)
+
+    Example:
+    >>> X, y = collect_dataset()
+    >>> X.shape
+    (150, 4)
+    >>> y.shape
+    (150,)
+    """
+    data = load_iris()
+    return np.array(data.data), np.array(data.target)
+
+
+def apply_pca(data_x: np.ndarray, n_components: int) -> tuple[np.ndarray, np.ndarray]:
+    """
+    Applies Principal Component Analysis (PCA) to reduce dimensionality.
+
+    :param data_x: Original dataset (features)
+    :param n_components: Number of principal components to retain
+    :return: Tuple containing transformed dataset and explained variance ratio
+
+    Example:
+    >>> X, _ = collect_dataset()
+    >>> transformed_X, variance = apply_pca(X, 2)
+    >>> transformed_X.shape
+    (150, 2)
+    >>> len(variance) == 2
+    True
+    """
+    # Standardizing the dataset
+    scaler = StandardScaler()
+    data_x_scaled = scaler.fit_transform(data_x)
+
+    # Applying PCA
+    pca = PCA(n_components=n_components)
+    principal_components = pca.fit_transform(data_x_scaled)
+
+    return principal_components, pca.explained_variance_ratio_
+
+
+def main() -> None:
+    """
+    Driver function to execute PCA and display results.
+    """
+    data_x, data_y = collect_dataset()
+
+    # Number of principal components to retain
+    n_components = 2
+
+    # Apply PCA
+    transformed_data, variance_ratio = apply_pca(data_x, n_components)
+
+    print("Transformed Dataset (First 5 rows):")
+    print(transformed_data[:5])
+
+    print("\nExplained Variance Ratio:")
+    print(variance_ratio)
+
+
+if __name__ == "__main__":
+    doctest.testmod()
+    main()

From fff34ed528a7c1af373aeae68693d67639ff616b Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 3 Mar 2025 19:10:41 +0100
Subject: [PATCH 074/135] [pre-commit.ci] pre-commit autoupdate (#12599)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.7 → v0.9.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.7...v0.9.9)
- [github.com/tox-dev/pyproject-fmt: v2.5.0 → v2.5.1](https://github.com/tox-dev/pyproject-fmt/compare/v2.5.0...v2.5.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8de90b11767f..a0952928a775 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.7
+    rev: v0.9.9
     hooks:
       - id: ruff
       - id: ruff-format
@@ -29,7 +29,7 @@ repos:
           - tomli
 
   - repo: https://github.com/tox-dev/pyproject-fmt
-    rev: "v2.5.0"
+    rev: "v2.5.1"
     hooks:
       - id: pyproject-fmt
 

From a415a953c3f1bb741f14a4ba06f067e6d94653ed Mon Sep 17 00:00:00 2001
From: Ankana Pari <143877643+ankana2113@users.noreply.github.com>
Date: Sun, 9 Mar 2025 03:05:07 +0530
Subject: [PATCH 075/135] Add largest rectangle histogram (#12269)

* added ridge regression

* added ridge regression

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added ridge regression

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ridge regression

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* resolved errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* resolved conflicts

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added doctests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ruff and minor checks

* minor chenges

* minor checks

* minor checks

* minor changes

* descriptive names

* Fix ruff check in loss_functions.py

* fixed pre-commit issues

* added largest rectangle histogram function

* added largest rectangle histogram function

* Update frequent_pattern_growth.py

* Update loss_functions.py

* Delete machine_learning/ridge_regression/__init__.py

* Delete machine_learning/ridge_regression/ADRvsRating.csv

* Delete machine_learning/ridge_regression/ridge_regression.py

* Delete machine_learning/ridge_regression/test_ridge_regression.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 .../stacks/largest_rectangle_histogram.py     | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 data_structures/stacks/largest_rectangle_histogram.py

diff --git a/data_structures/stacks/largest_rectangle_histogram.py b/data_structures/stacks/largest_rectangle_histogram.py
new file mode 100644
index 000000000000..7575bd9f628d
--- /dev/null
+++ b/data_structures/stacks/largest_rectangle_histogram.py
@@ -0,0 +1,39 @@
+def largest_rectangle_area(heights: list[int]) -> int:
+    """
+    Inputs an array of integers representing the heights of bars,
+    and returns the area of the largest rectangle that can be formed
+
+    >>> largest_rectangle_area([2, 1, 5, 6, 2, 3])
+    10
+
+    >>> largest_rectangle_area([2, 4])
+    4
+
+    >>> largest_rectangle_area([6, 2, 5, 4, 5, 1, 6])
+    12
+
+    >>> largest_rectangle_area([1])
+    1
+    """
+    stack: list[int] = []
+    max_area = 0
+    heights = [*heights, 0]  # make a new list by appending the sentinel 0
+    n = len(heights)
+
+    for i in range(n):
+        # make sure the stack remains in increasing order
+        while stack and heights[i] < heights[stack[-1]]:
+            h = heights[stack.pop()]  # height of the bar
+            # if stack is empty, it means entire width can be taken from index 0 to i-1
+            w = i if not stack else i - stack[-1] - 1  # calculate width
+            max_area = max(max_area, h * w)
+
+        stack.append(i)
+
+    return max_area
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()

From 4fbd350b6e08aeb22741d694ce2e64182c66ac92 Mon Sep 17 00:00:00 2001
From: PAUL ADUTWUM <pauladutwum303@gmail.com>
Date: Sat, 8 Mar 2025 16:47:04 -0500
Subject: [PATCH 076/135] Improved test coverage in  decimal_to_fraction.py
 (#12608)

* Imporved test coverage in  decimal_to_fraction.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update decimal_to_fraction.py

* Update decimal_to_fraction.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 maths/decimal_to_fraction.py | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py
index 2aa8e3c3dfd6..7f1299b33c5c 100644
--- a/maths/decimal_to_fraction.py
+++ b/maths/decimal_to_fraction.py
@@ -16,6 +16,20 @@ def decimal_to_fraction(decimal: float | str) -> tuple[int, int]:
     >>> decimal_to_fraction("78td")
     Traceback (most recent call last):
     ValueError: Please enter a valid number
+    >>> decimal_to_fraction(0)
+    (0, 1)
+    >>> decimal_to_fraction(-2.5)
+    (-5, 2)
+    >>> decimal_to_fraction(0.125)
+    (1, 8)
+    >>> decimal_to_fraction(1000000.25)
+    (4000001, 4)
+    >>> decimal_to_fraction(1.3333)
+    (13333, 10000)
+    >>> decimal_to_fraction("1.23e2")
+    (123, 1)
+    >>> decimal_to_fraction("0.500")
+    (1, 2)
     """
     try:
         decimal = float(decimal)

From e3fb5309da98e2d07699ae39eb0a55836a063532 Mon Sep 17 00:00:00 2001
From: PAUL ADUTWUM <pauladutwum303@gmail.com>
Date: Sat, 8 Mar 2025 16:52:20 -0500
Subject: [PATCH 077/135] Improve decimal_to_fraction.py (#12611)

* Update decimal_to_fraction.py

* Update decimal_to_fraction.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 maths/decimal_to_fraction.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/maths/decimal_to_fraction.py b/maths/decimal_to_fraction.py
index 7f1299b33c5c..be42b9fb3b5a 100644
--- a/maths/decimal_to_fraction.py
+++ b/maths/decimal_to_fraction.py
@@ -48,8 +48,8 @@ def decimal_to_fraction(decimal: float | str) -> tuple[int, int]:
             if remainder == 0:
                 break
             dividend, divisor = divisor, remainder
-        numerator, denominator = numerator / divisor, denominator / divisor
-        return int(numerator), int(denominator)
+        numerator, denominator = numerator // divisor, denominator // divisor
+        return numerator, denominator
 
 
 if __name__ == "__main__":

From 23eb17462940e20b830aacce5d2eb80113a7f973 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 11 Mar 2025 08:51:28 +0100
Subject: [PATCH 078/135] [pre-commit.ci] pre-commit autoupdate (#12614)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.9 → v0.9.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.9...v0.9.10)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 DIRECTORY.md            | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a0952928a775..32580f8c7398 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.9
+    rev: v0.9.10
     hooks:
       - id: ruff
       - id: ruff-format
diff --git a/DIRECTORY.md b/DIRECTORY.md
index ab3259b9a766..1c02c191bd14 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -289,6 +289,7 @@
     * [Dijkstras Two Stack Algorithm](data_structures/stacks/dijkstras_two_stack_algorithm.py)
     * [Infix To Postfix Conversion](data_structures/stacks/infix_to_postfix_conversion.py)
     * [Infix To Prefix Conversion](data_structures/stacks/infix_to_prefix_conversion.py)
+    * [Largest Rectangle Histogram](data_structures/stacks/largest_rectangle_histogram.py)
     * [Lexicographical Numbers](data_structures/stacks/lexicographical_numbers.py)
     * [Next Greater Element](data_structures/stacks/next_greater_element.py)
     * [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py)

From 7ce998b91c45090bd9c4cdfac6ed0220497b4810 Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Tue, 11 Mar 2025 17:29:13 +0300
Subject: [PATCH 079/135] Fix some RUF012 per file ignores (#11399)

* updating DIRECTORY.md

* Fix some RUF012 per file ignores

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Fix

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix

* Improve

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Improve

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: MaximSmolskiy <MaximSmolskiy@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 other/lfu_cache.py | 18 ++++++++++--------
 other/lru_cache.py | 18 ++++++++++--------
 pyproject.toml     |  3 ---
 3 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/other/lfu_cache.py b/other/lfu_cache.py
index 788fdf19bb60..5a143c739b9d 100644
--- a/other/lfu_cache.py
+++ b/other/lfu_cache.py
@@ -196,9 +196,6 @@ class LFUCache(Generic[T, U]):
     CacheInfo(hits=196, misses=100, capacity=100, current_size=100)
     """
 
-    # class variable to map the decorator functions to their respective instance
-    decorator_function_to_instance_map: dict[Callable[[T], U], LFUCache[T, U]] = {}
-
     def __init__(self, capacity: int):
         self.list: DoubleLinkedList[T, U] = DoubleLinkedList()
         self.capacity = capacity
@@ -291,18 +288,23 @@ def decorator(
         """
 
         def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]:
+            # variable to map the decorator functions to their respective instance
+            decorator_function_to_instance_map: dict[
+                Callable[[T], U], LFUCache[T, U]
+            ] = {}
+
             def cache_decorator_wrapper(*args: T) -> U:
-                if func not in cls.decorator_function_to_instance_map:
-                    cls.decorator_function_to_instance_map[func] = LFUCache(size)
+                if func not in decorator_function_to_instance_map:
+                    decorator_function_to_instance_map[func] = LFUCache(size)
 
-                result = cls.decorator_function_to_instance_map[func].get(args[0])
+                result = decorator_function_to_instance_map[func].get(args[0])
                 if result is None:
                     result = func(*args)
-                    cls.decorator_function_to_instance_map[func].put(args[0], result)
+                    decorator_function_to_instance_map[func].put(args[0], result)
                 return result
 
             def cache_info() -> LFUCache[T, U]:
-                return cls.decorator_function_to_instance_map[func]
+                return decorator_function_to_instance_map[func]
 
             setattr(cache_decorator_wrapper, "cache_info", cache_info)  # noqa: B010
 
diff --git a/other/lru_cache.py b/other/lru_cache.py
index 1e5eeac45b4e..4f0c843c86cc 100644
--- a/other/lru_cache.py
+++ b/other/lru_cache.py
@@ -209,9 +209,6 @@ class LRUCache(Generic[T, U]):
     CacheInfo(hits=194, misses=99, capacity=100, current size=99)
     """
 
-    # class variable to map the decorator functions to their respective instance
-    decorator_function_to_instance_map: dict[Callable[[T], U], LRUCache[T, U]] = {}
-
     def __init__(self, capacity: int):
         self.list: DoubleLinkedList[T, U] = DoubleLinkedList()
         self.capacity = capacity
@@ -308,18 +305,23 @@ def decorator(
         """
 
         def cache_decorator_inner(func: Callable[[T], U]) -> Callable[..., U]:
+            # variable to map the decorator functions to their respective instance
+            decorator_function_to_instance_map: dict[
+                Callable[[T], U], LRUCache[T, U]
+            ] = {}
+
             def cache_decorator_wrapper(*args: T) -> U:
-                if func not in cls.decorator_function_to_instance_map:
-                    cls.decorator_function_to_instance_map[func] = LRUCache(size)
+                if func not in decorator_function_to_instance_map:
+                    decorator_function_to_instance_map[func] = LRUCache(size)
 
-                result = cls.decorator_function_to_instance_map[func].get(args[0])
+                result = decorator_function_to_instance_map[func].get(args[0])
                 if result is None:
                     result = func(*args)
-                    cls.decorator_function_to_instance_map[func].put(args[0], result)
+                    decorator_function_to_instance_map[func].put(args[0], result)
                 return result
 
             def cache_info() -> LRUCache[T, U]:
-                return cls.decorator_function_to_instance_map[func]
+                return decorator_function_to_instance_map[func]
 
             setattr(cache_decorator_wrapper, "cache_info", cache_info)  # noqa: B010
 
diff --git a/pyproject.toml b/pyproject.toml
index 2135f1f5825a..4a76c4ad6d11 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -135,9 +135,6 @@ lint.per-file-ignores."machine_learning/sequential_minimum_optimization.py" = [
 lint.per-file-ignores."matrix/sherman_morrison.py" = [
   "SIM103",
 ]
-lint.per-file-ignores."other/l*u_cache.py" = [
-  "RUF012",
-]
 lint.per-file-ignores."physics/newtons_second_law_of_motion.py" = [
   "BLE001",
 ]

From edf7c372a9a6a3e01a33ef92021d958029e99319 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 18 Mar 2025 09:53:49 +0100
Subject: [PATCH 080/135] [pre-commit.ci] pre-commit autoupdate (#12623)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.9.10 → v0.11.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.9.10...v0.11.0)
- [github.com/abravalheri/validate-pyproject: v0.23 → v0.24](https://github.com/abravalheri/validate-pyproject/compare/v0.23...v0.24)

* Fix ruff issues

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml                        |  4 ++--
 conversions/prefix_conversions_string.py       |  4 ++--
 data_structures/arrays/sudoku_solver.py        |  4 ++--
 graphics/digital_differential_analyzer_line.py |  2 +-
 graphs/minimum_spanning_tree_prims2.py         |  4 ++--
 hashes/enigma_machine.py                       |  4 ++--
 linear_algebra/src/test_linear_algebra.py      |  2 +-
 maths/primelib.py                              |  2 +-
 other/davis_putnam_logemann_loveland.py        |  2 +-
 other/quine.py                                 |  2 +-
 project_euler/problem_028/sol1.py              |  2 +-
 pyproject.toml                                 |  1 +
 scripts/validate_filenames.py                  | 17 +++++++----------
 sorts/external_sort.py                         |  2 +-
 strings/frequency_finder.py                    |  2 +-
 15 files changed, 26 insertions(+), 28 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 32580f8c7398..5deb66a5e5a2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.9.10
+    rev: v0.11.0
     hooks:
       - id: ruff
       - id: ruff-format
@@ -42,7 +42,7 @@ repos:
         pass_filenames: false
 
   - repo: https://github.com/abravalheri/validate-pyproject
-    rev: v0.23
+    rev: v0.24
     hooks:
       - id: validate-pyproject
 
diff --git a/conversions/prefix_conversions_string.py b/conversions/prefix_conversions_string.py
index 9344c9672a1f..c5fef49874ca 100644
--- a/conversions/prefix_conversions_string.py
+++ b/conversions/prefix_conversions_string.py
@@ -53,7 +53,7 @@ class SIUnit(Enum):
     yocto = -24
 
     @classmethod
-    def get_positive(cls: type[T]) -> dict:
+    def get_positive(cls) -> dict:
         """
         Returns a dictionary with only the elements of this enum
         that has a positive value
@@ -68,7 +68,7 @@ def get_positive(cls: type[T]) -> dict:
         return {unit.name: unit.value for unit in cls if unit.value > 0}
 
     @classmethod
-    def get_negative(cls: type[T]) -> dict:
+    def get_negative(cls) -> dict:
         """
         Returns a dictionary with only the elements of this enum
         that has a negative value
diff --git a/data_structures/arrays/sudoku_solver.py b/data_structures/arrays/sudoku_solver.py
index e1714e57ece8..4c722f12fd6e 100644
--- a/data_structures/arrays/sudoku_solver.py
+++ b/data_structures/arrays/sudoku_solver.py
@@ -54,7 +54,7 @@ def parse_grid(grid):
     return False if a contradiction is detected.
     """
     ## To start, every square can be any digit; then assign values from the grid.
-    values = {s: digits for s in squares}
+    values = dict.fromkeys(squares, digits)
     for s, d in grid_values(grid).items():
         if d in digits and not assign(values, s, d):
             return False  ## (Fail if we can't assign d to square s.)
@@ -203,7 +203,7 @@ def random_puzzle(assignments=17):
     Note the resulting puzzle is not guaranteed to be solvable, but empirically
     about 99.8% of them are solvable. Some have multiple solutions.
     """
-    values = {s: digits for s in squares}
+    values = dict.fromkeys(squares, digits)
     for s in shuffled(squares):
         if not assign(values, s, random.choice(values[s])):
             break
diff --git a/graphics/digital_differential_analyzer_line.py b/graphics/digital_differential_analyzer_line.py
index a51cb0b8dc37..f7269ab09856 100644
--- a/graphics/digital_differential_analyzer_line.py
+++ b/graphics/digital_differential_analyzer_line.py
@@ -29,7 +29,7 @@ def digital_differential_analyzer_line(
     for _ in range(steps):
         x += x_increment
         y += y_increment
-        coordinates.append((int(round(x)), int(round(y))))
+        coordinates.append((round(x), round(y)))
     return coordinates
 
 
diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py
index cc918f81dfe8..6870cc80f844 100644
--- a/graphs/minimum_spanning_tree_prims2.py
+++ b/graphs/minimum_spanning_tree_prims2.py
@@ -239,8 +239,8 @@ def prims_algo(
     13
     """
     # prim's algorithm for minimum spanning tree
-    dist: dict[T, int] = {node: maxsize for node in graph.connections}
-    parent: dict[T, T | None] = {node: None for node in graph.connections}
+    dist: dict[T, int] = dict.fromkeys(graph.connections, maxsize)
+    parent: dict[T, T | None] = dict.fromkeys(graph.connections)
 
     priority_queue: MinPriorityQueue[T] = MinPriorityQueue()
     for node, weight in dist.items():
diff --git a/hashes/enigma_machine.py b/hashes/enigma_machine.py
index d95437d12c34..0da8e4113de9 100644
--- a/hashes/enigma_machine.py
+++ b/hashes/enigma_machine.py
@@ -15,12 +15,12 @@ def rotator():
     gear_one.append(i)
     del gear_one[0]
     gear_one_pos += 1
-    if gear_one_pos % int(len(alphabets)) == 0:
+    if gear_one_pos % len(alphabets) == 0:
         i = gear_two[0]
         gear_two.append(i)
         del gear_two[0]
         gear_two_pos += 1
-        if gear_two_pos % int(len(alphabets)) == 0:
+        if gear_two_pos % len(alphabets) == 0:
             i = gear_three[0]
             gear_three.append(i)
             del gear_three[0]
diff --git a/linear_algebra/src/test_linear_algebra.py b/linear_algebra/src/test_linear_algebra.py
index fc5f90fd5cbe..5209c152013e 100644
--- a/linear_algebra/src/test_linear_algebra.py
+++ b/linear_algebra/src/test_linear_algebra.py
@@ -181,7 +181,7 @@ def test_component_matrix(self) -> None:
         test for Matrix method component()
         """
         a = Matrix([[1, 2, 3], [2, 4, 5], [6, 7, 8]], 3, 3)
-        assert a.component(2, 1) == 7, 0.01
+        assert a.component(2, 1) == 7, "0.01"
 
     def test__add__matrix(self) -> None:
         """
diff --git a/maths/primelib.py b/maths/primelib.py
index 3a966e5cd936..9f031efc50a9 100644
--- a/maths/primelib.py
+++ b/maths/primelib.py
@@ -76,7 +76,7 @@ def is_prime(number: int) -> bool:
     if number <= 1:
         status = False
 
-    for divisor in range(2, int(round(sqrt(number))) + 1):
+    for divisor in range(2, round(sqrt(number)) + 1):
         # if 'number' divisible by 'divisor' then sets 'status'
         # of false and break up the loop.
         if number % divisor == 0:
diff --git a/other/davis_putnam_logemann_loveland.py b/other/davis_putnam_logemann_loveland.py
index e95bf371a817..7d0bcce15a29 100644
--- a/other/davis_putnam_logemann_loveland.py
+++ b/other/davis_putnam_logemann_loveland.py
@@ -36,7 +36,7 @@ def __init__(self, literals: list[str]) -> None:
         Represent the literals and an assignment in a clause."
         """
         # Assign all literals to None initially
-        self.literals: dict[str, bool | None] = {literal: None for literal in literals}
+        self.literals: dict[str, bool | None] = dict.fromkeys(literals)
 
     def __str__(self) -> str:
         """
diff --git a/other/quine.py b/other/quine.py
index 08e885bc1ce7..0fc78333fed1 100644
--- a/other/quine.py
+++ b/other/quine.py
@@ -1,5 +1,5 @@
 #!/bin/python3
-# ruff: noqa
+# ruff: noqa: PLC3002
 """
 Quine:
 
diff --git a/project_euler/problem_028/sol1.py b/project_euler/problem_028/sol1.py
index 1ea5d4fcafd4..0a4648af36c4 100644
--- a/project_euler/problem_028/sol1.py
+++ b/project_euler/problem_028/sol1.py
@@ -37,7 +37,7 @@ def solution(n: int = 1001) -> int:
     """
     total = 1
 
-    for i in range(1, int(ceil(n / 2.0))):
+    for i in range(1, ceil(n / 2.0)):
         odd = 2 * i + 1
         even = 2 * i
         total = total + 4 * odd**2 - 6 * even
diff --git a/pyproject.toml b/pyproject.toml
index 4a76c4ad6d11..60f8d4ffc96f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -119,6 +119,7 @@ lint.ignore = [
   "PT018",   # Assertion should be broken down into multiple parts
   "S101",    # Use of `assert` detected -- DO NOT FIX
   "S311",    # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME
+  "SIM905",  # Consider using a list literal instead of `str.split` -- DO NOT FIX
   "SLF001",  # Private member accessed: `_Iterator` -- FIX ME
   "UP038",   # Use `X | Y` in `{}` call instead of `(X, Y)` -- DO NOT FIX
 ]
diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py
index e76b4dbfe288..80399673cced 100755
--- a/scripts/validate_filenames.py
+++ b/scripts/validate_filenames.py
@@ -9,28 +9,25 @@
 filepaths = list(good_file_paths())
 assert filepaths, "good_file_paths() failed!"
 
-upper_files = [file for file in filepaths if file != file.lower()]
-if upper_files:
+if upper_files := [file for file in filepaths if file != file.lower()]:
     print(f"{len(upper_files)} files contain uppercase characters:")
     print("\n".join(upper_files) + "\n")
 
-space_files = [file for file in filepaths if " " in file]
-if space_files:
+if space_files := [file for file in filepaths if " " in file]:
     print(f"{len(space_files)} files contain space characters:")
     print("\n".join(space_files) + "\n")
 
-hyphen_files = [file for file in filepaths if "-" in file]
-if hyphen_files:
+if hyphen_files := [
+    file for file in filepaths if "-" in file and "/site-packages/" not in file
+]:
     print(f"{len(hyphen_files)} files contain hyphen characters:")
     print("\n".join(hyphen_files) + "\n")
 
-nodir_files = [file for file in filepaths if os.sep not in file]
-if nodir_files:
+if nodir_files := [file for file in filepaths if os.sep not in file]:
     print(f"{len(nodir_files)} files are not in a directory:")
     print("\n".join(nodir_files) + "\n")
 
-bad_files = len(upper_files + space_files + hyphen_files + nodir_files)
-if bad_files:
+if bad_files := len(upper_files + space_files + hyphen_files + nodir_files):
     import sys
 
     sys.exit(bad_files)
diff --git a/sorts/external_sort.py b/sorts/external_sort.py
index 3fa7cacc0592..cfddee4fe7f8 100644
--- a/sorts/external_sort.py
+++ b/sorts/external_sort.py
@@ -61,7 +61,7 @@ def __init__(self, files):
         self.files = files
         self.empty = set()
         self.num_buffers = len(files)
-        self.buffers = {i: None for i in range(self.num_buffers)}
+        self.buffers = dict.fromkeys(range(self.num_buffers))
 
     def get_dict(self):
         return {
diff --git a/strings/frequency_finder.py b/strings/frequency_finder.py
index e5afee891bd9..98720dc36d6e 100644
--- a/strings/frequency_finder.py
+++ b/strings/frequency_finder.py
@@ -36,7 +36,7 @@
 
 
 def get_letter_count(message: str) -> dict[str, int]:
-    letter_count = {letter: 0 for letter in string.ascii_uppercase}
+    letter_count = dict.fromkeys(string.ascii_uppercase, 0)
     for letter in message.upper():
         if letter in LETTERS:
             letter_count[letter] += 1

From 580273eeca28c30a8a5da114800d21b89fdfb930 Mon Sep 17 00:00:00 2001
From: Pranjay kumar <110048711+pranjaykumar926@users.noreply.github.com>
Date: Thu, 20 Mar 2025 05:03:46 +0530
Subject: [PATCH 081/135] Improve prefix_sum.py (#12560)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update prefix_sum.py

Index Validation for get_sum

Raises ValueError if start or end is out of range or start > end.
Handles cases where the array is empty.
✅ Empty Array Support

If an empty array is passed, get_sum raises an appropriate error instead of failing unexpectedly.
✅ Optimized contains_sum Initialization

Initializes sums with {0} for efficient subarray sum checking.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update prefix_sum.py

* Update prefix_sum.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update prefix_sum.py

* Update prefix_sum.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 data_structures/arrays/prefix_sum.py | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/data_structures/arrays/prefix_sum.py b/data_structures/arrays/prefix_sum.py
index 2243a5308937..717b5f9d7e7e 100644
--- a/data_structures/arrays/prefix_sum.py
+++ b/data_structures/arrays/prefix_sum.py
@@ -30,11 +30,29 @@ def get_sum(self, start: int, end: int) -> int:
         5
         >>> PrefixSum([1,2,3]).get_sum(2, 2)
         3
+        >>> PrefixSum([]).get_sum(0, 0)
+        Traceback (most recent call last):
+        ...
+        ValueError: The array is empty.
+        >>> PrefixSum([1,2,3]).get_sum(-1, 2)
+        Traceback (most recent call last):
+        ...
+        ValueError: Invalid range specified.
         >>> PrefixSum([1,2,3]).get_sum(2, 3)
         Traceback (most recent call last):
         ...
-        IndexError: list index out of range
+        ValueError: Invalid range specified.
+        >>> PrefixSum([1,2,3]).get_sum(2, 1)
+        Traceback (most recent call last):
+        ...
+        ValueError: Invalid range specified.
         """
+        if not self.prefix_sum:
+            raise ValueError("The array is empty.")
+
+        if start < 0 or end >= len(self.prefix_sum) or start > end:
+            raise ValueError("Invalid range specified.")
+
         if start == 0:
             return self.prefix_sum[end]
 

From e3773dbec1504de17047c4fe013c0f1aaef20b38 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 24 Mar 2025 20:05:41 +0100
Subject: [PATCH 082/135] [pre-commit.ci] pre-commit autoupdate (#12631)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.0 → v0.11.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.0...v0.11.2)
- [github.com/abravalheri/validate-pyproject: v0.24 → v0.24.1](https://github.com/abravalheri/validate-pyproject/compare/v0.24...v0.24.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5deb66a5e5a2..0fc8b2b14e07 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.0
+    rev: v0.11.2
     hooks:
       - id: ruff
       - id: ruff-format
@@ -42,7 +42,7 @@ repos:
         pass_filenames: false
 
   - repo: https://github.com/abravalheri/validate-pyproject
-    rev: v0.24
+    rev: v0.24.1
     hooks:
       - id: validate-pyproject
 

From 74b540ad73bd3b1187ed6e3c89bb8f309ef543fd Mon Sep 17 00:00:00 2001
From: Tony Dang <62843153+Dang-Hoang-Tung@users.noreply.github.com>
Date: Sat, 29 Mar 2025 08:13:47 +0000
Subject: [PATCH 083/135] Genetic Algorithm: Fix bug in multi-threading
 (#12644)

* Fix bug in multi-threading

- Multi-threading (despite being commented out) had a tiny bug: missing target argument (2nd argument).
- Commented out code was also slightly hard to understand, added (Option 1/2) in comments to clarify where a user may choose between 2 implementations.

* Update basic_string.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 genetic_algorithm/basic_string.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/genetic_algorithm/basic_string.py b/genetic_algorithm/basic_string.py
index a906ce85a779..b75491d9a949 100644
--- a/genetic_algorithm/basic_string.py
+++ b/genetic_algorithm/basic_string.py
@@ -144,18 +144,18 @@ def basic(target: str, genes: list[str], debug: bool = True) -> tuple[int, int,
 
         # Random population created. Now it's time to evaluate.
 
-        # Adding a bit of concurrency can make everything faster,
+        # (Option 1) Adding a bit of concurrency can make everything faster,
         #
         # import concurrent.futures
         # population_score: list[tuple[str, float]] = []
         # with concurrent.futures.ThreadPoolExecutor(
         #                                   max_workers=NUM_WORKERS) as executor:
-        #     futures = {executor.submit(evaluate, item) for item in population}
+        #     futures = {executor.submit(evaluate, item, target) for item in population}
         #     concurrent.futures.wait(futures)
         #     population_score = [item.result() for item in futures]
         #
         # but with a simple algorithm like this, it will probably be slower.
-        # We just need to call evaluate for every item inside the population.
+        # (Option 2) We just need to call evaluate for every item inside the population.
         population_score = [evaluate(item, target) for item in population]
 
         # Check if there is a matching evolution.

From f10a5cbfccc5ee9ddb5ddd9906591ecaad58f672 Mon Sep 17 00:00:00 2001
From: Isidro <isidroariass@hotmail.es>
Date: Mon, 31 Mar 2025 23:09:14 +0200
Subject: [PATCH 084/135] prefix_evaluation: Add alternative recursive
 implementation (#12646)

* prefix_evaluation: Add alternative recursive implementation

* improve doc

* better variable name calc->operators

* Update prefix_evaluation.py

* Update prefix_evaluation.py

* Update prefix_evaluation.py

* Update prefix_evaluation.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 data_structures/stacks/prefix_evaluation.py | 39 +++++++++++++++++++--
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/data_structures/stacks/prefix_evaluation.py b/data_structures/stacks/prefix_evaluation.py
index f48eca23d7b5..03a70d884725 100644
--- a/data_structures/stacks/prefix_evaluation.py
+++ b/data_structures/stacks/prefix_evaluation.py
@@ -1,8 +1,9 @@
 """
-Python3 program to evaluate a prefix expression.
+Program to evaluate a prefix expression.
+https://en.wikipedia.org/wiki/Polish_notation
 """
 
-calc = {
+operators = {
     "+": lambda x, y: x + y,
     "-": lambda x, y: x - y,
     "*": lambda x, y: x * y,
@@ -31,6 +32,10 @@ def evaluate(expression):
     21
     >>> evaluate("/ * 10 2 + 4 1 ")
     4.0
+    >>> evaluate("2")
+    2
+    >>> evaluate("+ * 2 3 / 8 4")
+    8.0
     """
     stack = []
 
@@ -45,11 +50,39 @@ def evaluate(expression):
             # push the result onto the stack again
             o1 = stack.pop()
             o2 = stack.pop()
-            stack.append(calc[c](o1, o2))
+            stack.append(operators[c](o1, o2))
 
     return stack.pop()
 
 
+def evaluate_recursive(expression: list[str]):
+    """
+    Alternative recursive implementation
+
+    >>> evaluate_recursive(['2'])
+    2
+    >>> expression = ['+', '*', '2', '3', '/', '8', '4']
+    >>> evaluate_recursive(expression)
+    8.0
+    >>> expression
+    []
+    >>> evaluate_recursive(['+', '9', '*', '2', '6'])
+    21
+    >>> evaluate_recursive(['/', '*', '10', '2', '+', '4', '1'])
+    4.0
+    """
+
+    op = expression.pop(0)
+    if is_operand(op):
+        return int(op)
+
+    operation = operators[op]
+
+    a = evaluate_recursive(expression)
+    b = evaluate_recursive(expression)
+    return operation(a, b)
+
+
 # Driver code
 if __name__ == "__main__":
     test_expression = "+ 9 * 2 6"

From baab802965c37fa1740054a559cad8c119b2ee35 Mon Sep 17 00:00:00 2001
From: Isidro <isidroariass@hotmail.es>
Date: Tue, 1 Apr 2025 20:55:14 +0200
Subject: [PATCH 085/135] doubly linked list: add dataclass and typing (#12647)

* Node is a dataclass

* fix mypy errors

* LinkedList is a dataclass

* fix mypy errors

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 .../linked_list/doubly_linked_list_two.py     | 62 +++++++++----------
 1 file changed, 29 insertions(+), 33 deletions(-)

diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py
index e993cc5a20af..3d3bfb0cde30 100644
--- a/data_structures/linked_list/doubly_linked_list_two.py
+++ b/data_structures/linked_list/doubly_linked_list_two.py
@@ -9,25 +9,19 @@
      Delete operation is more efficient
 """
 
+from dataclasses import dataclass
+from typing import Self
 
+
+@dataclass
 class Node:
-    def __init__(self, data: int, previous=None, next_node=None):
-        self.data = data
-        self.previous = previous
-        self.next = next_node
+    data: int
+    previous: Self | None = None
+    next: Self | None = None
 
     def __str__(self) -> str:
         return f"{self.data}"
 
-    def get_data(self) -> int:
-        return self.data
-
-    def get_next(self):
-        return self.next
-
-    def get_previous(self):
-        return self.previous
-
 
 class LinkedListIterator:
     def __init__(self, head):
@@ -40,30 +34,30 @@ def __next__(self):
         if not self.current:
             raise StopIteration
         else:
-            value = self.current.get_data()
-            self.current = self.current.get_next()
+            value = self.current.data
+            self.current = self.current.next
             return value
 
 
+@dataclass
 class LinkedList:
-    def __init__(self):
-        self.head = None  # First node in list
-        self.tail = None  # Last node in list
+    head: Node | None = None  # First node in list
+    tail: Node | None = None  # Last node in list
 
     def __str__(self):
         current = self.head
         nodes = []
         while current is not None:
-            nodes.append(current.get_data())
-            current = current.get_next()
+            nodes.append(current.data)
+            current = current.next
         return " ".join(str(node) for node in nodes)
 
     def __contains__(self, value: int):
         current = self.head
         while current:
-            if current.get_data() == value:
+            if current.data == value:
                 return True
-            current = current.get_next()
+            current = current.next
         return False
 
     def __iter__(self):
@@ -71,12 +65,12 @@ def __iter__(self):
 
     def get_head_data(self):
         if self.head:
-            return self.head.get_data()
+            return self.head.data
         return None
 
     def get_tail_data(self):
         if self.tail:
-            return self.tail.get_data()
+            return self.tail.data
         return None
 
     def set_head(self, node: Node) -> None:
@@ -103,18 +97,20 @@ def insert_before_node(self, node: Node, node_to_insert: Node) -> None:
         node_to_insert.next = node
         node_to_insert.previous = node.previous
 
-        if node.get_previous() is None:
+        if node.previous is None:
             self.head = node_to_insert
         else:
             node.previous.next = node_to_insert
 
         node.previous = node_to_insert
 
-    def insert_after_node(self, node: Node, node_to_insert: Node) -> None:
+    def insert_after_node(self, node: Node | None, node_to_insert: Node) -> None:
+        assert node is not None
+
         node_to_insert.previous = node
         node_to_insert.next = node.next
 
-        if node.get_next() is None:
+        if node.next is None:
             self.tail = node_to_insert
         else:
             node.next.previous = node_to_insert
@@ -136,27 +132,27 @@ def insert_at_position(self, position: int, value: int) -> None:
     def get_node(self, item: int) -> Node:
         node = self.head
         while node:
-            if node.get_data() == item:
+            if node.data == item:
                 return node
-            node = node.get_next()
+            node = node.next
         raise Exception("Node not found")
 
     def delete_value(self, value):
         if (node := self.get_node(value)) is not None:
             if node == self.head:
-                self.head = self.head.get_next()
+                self.head = self.head.next
 
             if node == self.tail:
-                self.tail = self.tail.get_previous()
+                self.tail = self.tail.previous
 
             self.remove_node_pointers(node)
 
     @staticmethod
     def remove_node_pointers(node: Node) -> None:
-        if node.get_next():
+        if node.next:
             node.next.previous = node.previous
 
-        if node.get_previous():
+        if node.previous:
             node.previous.next = node.next
 
         node.next = None

From 0c8cf8e9871a5f91182d767adf173dccf87c2c0f Mon Sep 17 00:00:00 2001
From: Maxim Smolskiy <mithridatus@mail.ru>
Date: Wed, 2 Apr 2025 10:23:55 +0300
Subject: [PATCH 086/135] Fix bug for
 data_structures/linked_list/doubly_linked_list_two.py (#12651)

* Fix bug for data_structures/linked_list/doubly_linked_list_two.py

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py
---
 .../linked_list/doubly_linked_list_two.py     | 27 ++++++++++++++-----
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py
index 3d3bfb0cde30..8c93cddd5d31 100644
--- a/data_structures/linked_list/doubly_linked_list_two.py
+++ b/data_structures/linked_list/doubly_linked_list_two.py
@@ -81,8 +81,9 @@ def set_head(self, node: Node) -> None:
             self.insert_before_node(self.head, node)
 
     def set_tail(self, node: Node) -> None:
-        if self.head is None:
-            self.set_head(node)
+        if self.tail is None:
+            self.head = node
+            self.tail = node
         else:
             self.insert_after_node(self.tail, node)
 
@@ -104,9 +105,7 @@ def insert_before_node(self, node: Node, node_to_insert: Node) -> None:
 
         node.previous = node_to_insert
 
-    def insert_after_node(self, node: Node | None, node_to_insert: Node) -> None:
-        assert node is not None
-
+    def insert_after_node(self, node: Node, node_to_insert: Node) -> None:
         node_to_insert.previous = node
         node_to_insert.next = node.next
 
@@ -127,7 +126,7 @@ def insert_at_position(self, position: int, value: int) -> None:
                 return
             current_position += 1
             node = node.next
-        self.insert_after_node(self.tail, new_node)
+        self.set_tail(new_node)
 
     def get_node(self, item: int) -> Node:
         node = self.head
@@ -237,6 +236,22 @@ def create_linked_list() -> None:
     7
     8
     9
+    >>> linked_list = LinkedList()
+    >>> linked_list.insert_at_position(position=1, value=10)
+    >>> str(linked_list)
+    '10'
+    >>> linked_list.insert_at_position(position=2, value=20)
+    >>> str(linked_list)
+    '10 20'
+    >>> linked_list.insert_at_position(position=1, value=30)
+    >>> str(linked_list)
+    '30 10 20'
+    >>> linked_list.insert_at_position(position=3, value=40)
+    >>> str(linked_list)
+    '30 10 40 20'
+    >>> linked_list.insert_at_position(position=5, value=50)
+    >>> str(linked_list)
+    '30 10 40 20 50'
     """
 
 

From 5afe02994eb0aafb0b462fec32fe5f6ecedf7305 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 7 Apr 2025 23:20:19 +0200
Subject: [PATCH 087/135] [pre-commit.ci] pre-commit autoupdate (#12661)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.2 → v0.11.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.2...v0.11.4)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0fc8b2b14e07..20065c433062 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.2
+    rev: v0.11.4
     hooks:
       - id: ruff
       - id: ruff-format

From a4576dc2a42cbfc7585a7ce6f28917cb97f83c45 Mon Sep 17 00:00:00 2001
From: Kim <iyai@naver.com>
Date: Wed, 9 Apr 2025 15:24:37 +0900
Subject: [PATCH 088/135] fix: correct typo "util" to "until" (#12653)

---
 dynamic_programming/bitmask.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/dynamic_programming/bitmask.py b/dynamic_programming/bitmask.py
index a6e6a0cda7bf..4737a3419e8e 100644
--- a/dynamic_programming/bitmask.py
+++ b/dynamic_programming/bitmask.py
@@ -42,7 +42,7 @@ def count_ways_until(self, mask, task_no):
             return self.dp[mask][task_no]
 
         # Number of ways when we don't this task in the arrangement
-        total_ways_util = self.count_ways_until(mask, task_no + 1)
+        total_ways_until = self.count_ways_until(mask, task_no + 1)
 
         # now assign the tasks one by one to all possible persons and recursively
         # assign for the remaining tasks.
@@ -54,10 +54,10 @@ def count_ways_until(self, mask, task_no):
 
                 # assign this task to p and change the mask value. And recursively
                 # assign tasks with the new mask value.
-                total_ways_util += self.count_ways_until(mask | (1 << p), task_no + 1)
+                total_ways_until += self.count_ways_until(mask | (1 << p), task_no + 1)
 
         # save the value.
-        self.dp[mask][task_no] = total_ways_util
+        self.dp[mask][task_no] = total_ways_until
 
         return self.dp[mask][task_no]
 

From 4ed61418a8fef5a0fe3c5a05a49c7cbc5ac8298c Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 14 Apr 2025 19:55:55 +0200
Subject: [PATCH 089/135] [pre-commit.ci] pre-commit autoupdate (#12671)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.4 → v0.11.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.4...v0.11.5)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 20065c433062..8a8697ca778a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.4
+    rev: v0.11.5
     hooks:
       - id: ruff
       - id: ruff-format

From cc621f1fddac7391389270d7bc38326507b4b495 Mon Sep 17 00:00:00 2001
From: parth-6945 <140963864+parth-6945@users.noreply.github.com>
Date: Mon, 14 Apr 2025 23:31:29 +0530
Subject: [PATCH 090/135] Add find_unique_number algorithm to bit manipulation
 (#12654)

* Add find_unique_number algorithm to bit manipulation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 bit_manipulation/find_unique_number.py | 37 ++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)
 create mode 100644 bit_manipulation/find_unique_number.py

diff --git a/bit_manipulation/find_unique_number.py b/bit_manipulation/find_unique_number.py
new file mode 100644
index 000000000000..77970b4865d1
--- /dev/null
+++ b/bit_manipulation/find_unique_number.py
@@ -0,0 +1,37 @@
+def find_unique_number(arr: list[int]) -> int:
+    """
+    Given a list of integers where every element appears twice except for one,
+    this function returns the element that appears only once using bitwise XOR.
+
+    >>> find_unique_number([1, 1, 2, 2, 3])
+    3
+    >>> find_unique_number([4, 5, 4, 6, 6])
+    5
+    >>> find_unique_number([7])
+    7
+    >>> find_unique_number([10, 20, 10])
+    20
+    >>> find_unique_number([])
+    Traceback (most recent call last):
+        ...
+    ValueError: input list must not be empty
+    >>> find_unique_number([1, 'a', 1])
+    Traceback (most recent call last):
+        ...
+    TypeError: all elements must be integers
+    """
+    if not arr:
+        raise ValueError("input list must not be empty")
+    if not all(isinstance(x, int) for x in arr):
+        raise TypeError("all elements must be integers")
+
+    result = 0
+    for num in arr:
+        result ^= num
+    return result
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()

From d123cbc649e0777717b02dc74b4466872941a8d5 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Tue, 15 Apr 2025 02:30:25 +0800
Subject: [PATCH 091/135] Solution for the Euler Project Problem 122 (#12655)

* Add initial version for euler project problem 122.

* Add doctests and documentation for the project euler problem 122.

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 project_euler/problem_122/__init__.py |  0
 project_euler/problem_122/sol1.py     | 89 +++++++++++++++++++++++++++
 2 files changed, 89 insertions(+)
 create mode 100644 project_euler/problem_122/__init__.py
 create mode 100644 project_euler/problem_122/sol1.py

diff --git a/project_euler/problem_122/__init__.py b/project_euler/problem_122/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_122/sol1.py b/project_euler/problem_122/sol1.py
new file mode 100644
index 000000000000..cd8b1e67708c
--- /dev/null
+++ b/project_euler/problem_122/sol1.py
@@ -0,0 +1,89 @@
+"""
+Project Euler Problem 122: https://projecteuler.net/problem=122
+
+Efficient Exponentiation
+
+The most naive way of computing n^15 requires fourteen multiplications:
+
+                                               n x n x ... x n = n^15.
+
+But using a "binary" method you can compute it in six multiplications:
+
+                                                         n x n = n^2
+                                                     n^2 x n^2 = n^4
+                                                     n^4 x n^4 = n^8
+                                                     n^8 x n^4 = n^12
+                                                    n^12 x n^2 = n^14
+                                                      n^14 x n = n^15
+
+However it is yet possible to compute it in only five multiplications:
+
+                                                                n x n = n^2
+                                                              n^2 x n = n^3
+                                                            n^3 x n^3 = n^6
+                                                            n^6 x n^6 = n^12
+                                                           n^12 x n^3 = n^15
+
+We shall define m(k) to be the minimum number of multiplications to compute n^k;
+for example m(15) = 5.
+
+Find sum_{k = 1}^200 m(k).
+
+It uses the fact that for rather small n, applicable for this problem, the solution
+for each number can be formed by increasing the largest element.
+
+References:
+- https://en.wikipedia.org/wiki/Addition_chain
+"""
+
+
+def solve(nums: list[int], goal: int, depth: int) -> bool:
+    """
+    Checks if nums can have a sum equal to goal, given that length of nums does
+    not exceed depth.
+
+    >>> solve([1], 2, 2)
+    True
+    >>> solve([1], 2, 0)
+    False
+    """
+    if len(nums) > depth:
+        return False
+    for el in nums:
+        if el + nums[-1] == goal:
+            return True
+        nums.append(el + nums[-1])
+        if solve(nums=nums, goal=goal, depth=depth):
+            return True
+        del nums[-1]
+    return False
+
+
+def solution(n: int = 200) -> int:
+    """
+    Calculates sum of smallest number of multiplactions for each number up to
+    and including n.
+
+    >>> solution(1)
+    0
+    >>> solution(2)
+    1
+    >>> solution(14)
+    45
+    >>> solution(15)
+    50
+    """
+    total = 0
+    for i in range(2, n + 1):
+        max_length = 0
+        while True:
+            nums = [1]
+            max_length += 1
+            if solve(nums=nums, goal=i, depth=max_length):
+                break
+        total += max_length
+    return total
+
+
+if __name__ == "__main__":
+    print(f"{solution() = }")

From 42820634f3795e7d7397ad2e688d091a79c1eb83 Mon Sep 17 00:00:00 2001
From: Naitik Dwivedi <naitikdwivedi2413k@gmail.com>
Date: Tue, 15 Apr 2025 00:27:13 +0530
Subject: [PATCH 092/135] Add matrix inversion algorithm using NumPy (#12657)

* Create matrix_inversion.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update matrix_inversion.py

* Update matrix_inversion.py

* Update matrix_inversion.py

* Update matrix_inversion.py

* Update matrix_inversion.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 linear_algebra/matrix_inversion.py | 36 ++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)
 create mode 100644 linear_algebra/matrix_inversion.py

diff --git a/linear_algebra/matrix_inversion.py b/linear_algebra/matrix_inversion.py
new file mode 100644
index 000000000000..50dae1c2e825
--- /dev/null
+++ b/linear_algebra/matrix_inversion.py
@@ -0,0 +1,36 @@
+import numpy as np
+
+
+def invert_matrix(matrix: list[list[float]]) -> list[list[float]]:
+    """
+    Returns the inverse of a square matrix using NumPy.
+
+    Parameters:
+    matrix (list[list[float]]): A square matrix.
+
+    Returns:
+    list[list[float]]: Inverted matrix if invertible, else raises error.
+
+    >>> invert_matrix([[4.0, 7.0], [2.0, 6.0]])
+    [[0.6000000000000001, -0.7000000000000001], [-0.2, 0.4]]
+    >>> invert_matrix([[1.0, 2.0], [0.0, 0.0]])
+    Traceback (most recent call last):
+        ...
+    ValueError: Matrix is not invertible
+    """
+    np_matrix = np.array(matrix)
+
+    try:
+        inv_matrix = np.linalg.inv(np_matrix)
+    except np.linalg.LinAlgError:
+        raise ValueError("Matrix is not invertible")
+
+    return inv_matrix.tolist()
+
+
+if __name__ == "__main__":
+    mat = [[4.0, 7.0], [2.0, 6.0]]
+    print("Original Matrix:")
+    print(mat)
+    print("Inverted Matrix:")
+    print(invert_matrix(mat))

From c585cb122718e219870ea7a2af110939b55e52f9 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Fri, 18 Apr 2025 07:16:15 +0800
Subject: [PATCH 093/135] Solution for the Euler Project problem 136 (#12658)

* Add initial version of file for the Euler project problem 136 solution.

* Add documentation and tests for the Euler project problem 136 solution.

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 project_euler/problem_136/__init__.py |  0
 project_euler/problem_136/sol1.py     | 63 +++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)
 create mode 100644 project_euler/problem_136/__init__.py
 create mode 100644 project_euler/problem_136/sol1.py

diff --git a/project_euler/problem_136/__init__.py b/project_euler/problem_136/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_136/sol1.py b/project_euler/problem_136/sol1.py
new file mode 100644
index 000000000000..688a9a5d7f24
--- /dev/null
+++ b/project_euler/problem_136/sol1.py
@@ -0,0 +1,63 @@
+"""
+Project Euler Problem 136: https://projecteuler.net/problem=136
+
+Singleton Difference
+
+The positive integers, x, y, and z, are consecutive terms of an arithmetic progression.
+Given that n is a positive integer, the equation, x^2 - y^2 - z^2 = n,
+has exactly one solution when n = 20:
+                              13^2 - 10^2 - 7^2 = 20.
+
+In fact there are twenty-five values of n below one hundred for which
+the equation has a unique solution.
+
+How many values of n less than fifty million have exactly one solution?
+
+By change of variables
+
+x = y + delta
+z = y - delta
+
+The expression can be rewritten:
+
+x^2 - y^2 - z^2 = y * (4 * delta - y) = n
+
+The algorithm loops over delta and y, which is restricted in upper and lower limits,
+to count how many solutions each n has.
+In the end it is counted how many n's have one solution.
+"""
+
+
+def solution(n_limit: int = 50 * 10**6) -> int:
+    """
+    Define n count list and loop over delta, y to get the counts, then check
+    which n has count == 1.
+
+    >>> solution(3)
+    0
+    >>> solution(10)
+    3
+    >>> solution(100)
+    25
+    >>> solution(110)
+    27
+    """
+    n_sol = [0] * n_limit
+
+    for delta in range(1, (n_limit + 1) // 4 + 1):
+        for y in range(4 * delta - 1, delta, -1):
+            n = y * (4 * delta - y)
+            if n >= n_limit:
+                break
+            n_sol[n] += 1
+
+    ans = 0
+    for i in range(n_limit):
+        if n_sol[i] == 1:
+            ans += 1
+
+    return ans
+
+
+if __name__ == "__main__":
+    print(f"{solution() = }")

From a1aa6313e08657f0e9ae337afa81d6b6f95357c9 Mon Sep 17 00:00:00 2001
From: Samuel Willis <109305646+konsoleSam@users.noreply.github.com>
Date: Thu, 17 Apr 2025 17:33:08 -0600
Subject: [PATCH 094/135] Adding time and a half pay calculator algorithm to
 financial folder (#12662)

* Create time&half-pay.py

* Update time&half-pay.py

* Update time&half-pay.py

* Rename time&half-pay.py to time_and_half_pay.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update time_and_half_pay.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update time_and_half_pay.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update time_and_half_pay.py

* Update time_and_half_pay.py

* Update time_and_half_pay.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 financial/time_and_half_pay.py | 40 ++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)
 create mode 100644 financial/time_and_half_pay.py

diff --git a/financial/time_and_half_pay.py b/financial/time_and_half_pay.py
new file mode 100644
index 000000000000..c5dff1bc1ce1
--- /dev/null
+++ b/financial/time_and_half_pay.py
@@ -0,0 +1,40 @@
+"""
+Calculate time and a half pay
+"""
+
+
+def pay(hours_worked: float, pay_rate: float, hours: float = 40) -> float:
+    """
+    hours_worked = The total hours worked
+    pay_rate = Amount of money per hour
+    hours = Number of hours that must be worked before you receive time and a half
+
+    >>> pay(41, 1)
+    41.5
+    >>> pay(65, 19)
+    1472.5
+    >>> pay(10, 1)
+    10.0
+    """
+    # Check that all input parameters are float or integer
+    assert isinstance(hours_worked, (float, int)), (
+        "Parameter 'hours_worked' must be of type 'int' or 'float'"
+    )
+    assert isinstance(pay_rate, (float, int)), (
+        "Parameter 'pay_rate' must be of type 'int' or 'float'"
+    )
+    assert isinstance(hours, (float, int)), (
+        "Parameter 'hours' must be of type 'int' or 'float'"
+    )
+
+    normal_pay = hours_worked * pay_rate
+    over_time = max(0, hours_worked - hours)
+    over_time_pay = over_time * pay_rate / 2
+    return normal_pay + over_time_pay
+
+
+if __name__ == "__main__":
+    # Test
+    import doctest
+
+    doctest.testmod()

From 9891d2bc3051ab4242ed041ea30edd10b94925bd Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 21 Apr 2025 19:54:11 +0200
Subject: [PATCH 095/135] [pre-commit.ci] pre-commit autoupdate (#12680)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.5 → v0.11.6](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.5...v0.11.6)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 DIRECTORY.md            | 7 +++++++
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8a8697ca778a..8474deebb7ba 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.5
+    rev: v0.11.6
     hooks:
       - id: ruff
       - id: ruff-format
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 1c02c191bd14..fa731e32ff23 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -40,6 +40,7 @@
   * [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py)
   * [Excess 3 Code](bit_manipulation/excess_3_code.py)
   * [Find Previous Power Of Two](bit_manipulation/find_previous_power_of_two.py)
+  * [Find Unique Number](bit_manipulation/find_unique_number.py)
   * [Gray Code Sequence](bit_manipulation/gray_code_sequence.py)
   * [Highest Set Bit](bit_manipulation/highest_set_bit.py)
   * [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py)
@@ -442,6 +443,7 @@
   * [Present Value](financial/present_value.py)
   * [Price Plus Tax](financial/price_plus_tax.py)
   * [Simple Moving Average](financial/simple_moving_average.py)
+  * [Time And Half Pay](financial/time_and_half_pay.py)
 
 ## Fractals
   * [Julia Sets](fractals/julia_sets.py)
@@ -570,6 +572,7 @@
   * [Gaussian Elimination](linear_algebra/gaussian_elimination.py)
   * [Jacobi Iteration Method](linear_algebra/jacobi_iteration_method.py)
   * [Lu Decomposition](linear_algebra/lu_decomposition.py)
+  * [Matrix Inversion](linear_algebra/matrix_inversion.py)
   * Src
     * [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py)
     * [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting.py)
@@ -1153,6 +1156,8 @@
     * [Sol1](project_euler/problem_120/sol1.py)
   * Problem 121
     * [Sol1](project_euler/problem_121/sol1.py)
+  * Problem 122
+    * [Sol1](project_euler/problem_122/sol1.py)
   * Problem 123
     * [Sol1](project_euler/problem_123/sol1.py)
   * Problem 125
@@ -1163,6 +1168,8 @@
     * [Sol1](project_euler/problem_131/sol1.py)
   * Problem 135
     * [Sol1](project_euler/problem_135/sol1.py)
+  * Problem 136
+    * [Sol1](project_euler/problem_136/sol1.py)
   * Problem 144
     * [Sol1](project_euler/problem_144/sol1.py)
   * Problem 145

From 11a61d15dc3aebc69f153adca8568076a25f7110 Mon Sep 17 00:00:00 2001
From: Isidro <isidroariass@hotmail.es>
Date: Mon, 21 Apr 2025 21:04:39 +0200
Subject: [PATCH 096/135] Generic type hint in DDL (#12677)

* Generic type hint in DDL

Instead of forcing int

* Update doubly_linked_list_two.py

* Update doubly_linked_list_two.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 .../linked_list/doubly_linked_list_two.py        | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py
index 8c93cddd5d31..a7f639a6e289 100644
--- a/data_structures/linked_list/doubly_linked_list_two.py
+++ b/data_structures/linked_list/doubly_linked_list_two.py
@@ -10,12 +10,14 @@
 """
 
 from dataclasses import dataclass
-from typing import Self
+from typing import Self, TypeVar
+
+DataType = TypeVar("DataType")
 
 
 @dataclass
-class Node:
-    data: int
+class Node[DataType]:
+    data: DataType
     previous: Self | None = None
     next: Self | None = None
 
@@ -52,7 +54,7 @@ def __str__(self):
             current = current.next
         return " ".join(str(node) for node in nodes)
 
-    def __contains__(self, value: int):
+    def __contains__(self, value: DataType):
         current = self.head
         while current:
             if current.data == value:
@@ -87,7 +89,7 @@ def set_tail(self, node: Node) -> None:
         else:
             self.insert_after_node(self.tail, node)
 
-    def insert(self, value: int) -> None:
+    def insert(self, value: DataType) -> None:
         node = Node(value)
         if self.head is None:
             self.set_head(node)
@@ -116,7 +118,7 @@ def insert_after_node(self, node: Node, node_to_insert: Node) -> None:
 
         node.next = node_to_insert
 
-    def insert_at_position(self, position: int, value: int) -> None:
+    def insert_at_position(self, position: int, value: DataType) -> None:
         current_position = 1
         new_node = Node(value)
         node = self.head
@@ -128,7 +130,7 @@ def insert_at_position(self, position: int, value: int) -> None:
             node = node.next
         self.set_tail(new_node)
 
-    def get_node(self, item: int) -> Node:
+    def get_node(self, item: DataType) -> Node:
         node = self.head
         while node:
             if node.data == item:

From 29afed0df65fd84f53ad697ff1dbfc86c6e83631 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Fri, 25 Apr 2025 08:30:42 +0200
Subject: [PATCH 097/135] Bump astral-sh/setup-uv from 5 to 6 (#12683)

* Bump astral-sh/setup-uv from 5 to 6

Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 5 to 6.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v6)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* uv run pytest --ignore=web_programming/fetch_anime_and_play.py

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .github/workflows/build.yml         | 3 ++-
 .github/workflows/project_euler.yml | 4 ++--
 .github/workflows/ruff.yml          | 2 +-
 .github/workflows/sphinx.yml        | 2 +-
 4 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 62829b2b45a5..8b83cb41c79a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v5
+      - uses: astral-sh/setup-uv@v6
         with:
           enable-cache: true
           cache-dependency-glob: uv.lock
@@ -30,6 +30,7 @@ jobs:
           --ignore=project_euler/
           --ignore=quantum/q_fourier_transform.py
           --ignore=scripts/validate_solutions.py
+          --ignore=web_programming/fetch_anime_and_play.py
           --cov-report=term-missing:skip-covered
           --cov=. .
       - if: ${{ success() }}
diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml
index 8d51ad8850cf..eaf4150e4eaa 100644
--- a/.github/workflows/project_euler.yml
+++ b/.github/workflows/project_euler.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v5
+      - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
@@ -25,7 +25,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v5
+      - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
           python-version: 3.x
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
index cfe127b3521f..ec9f0202bd7e 100644
--- a/.github/workflows/ruff.yml
+++ b/.github/workflows/ruff.yml
@@ -12,5 +12,5 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v5
+      - uses: astral-sh/setup-uv@v6
       - run: uvx ruff check --output-format=github .
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index 16ff284a74f2..2010041d80c5 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -26,7 +26,7 @@ jobs:
     runs-on: ubuntu-24.04-arm
     steps:
       - uses: actions/checkout@v4
-      - uses: astral-sh/setup-uv@v5
+      - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
           python-version: 3.13

From 0a3a96534767c37a0f1561d0f5148b5d1d5e0272 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 28 Apr 2025 19:55:57 +0200
Subject: [PATCH 098/135] [pre-commit.ci] pre-commit autoupdate (#12692)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.6 → v0.11.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.6...v0.11.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 8474deebb7ba..034493b10912 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.6
+    rev: v0.11.7
     hooks:
       - id: ruff
       - id: ruff-format

From 145879b8b2546c74fc51446ac607823876a0f601 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Mon, 5 May 2025 15:00:32 +0800
Subject: [PATCH 099/135] Add solution for the Euler project problem 164.
 (#12663)

* Add solution for the Euler project problem 164.

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 project_euler/problem_164/__init__.py |  0
 project_euler/problem_164/sol1.py     | 65 +++++++++++++++++++++++++++
 2 files changed, 65 insertions(+)
 create mode 100644 project_euler/problem_164/__init__.py
 create mode 100644 project_euler/problem_164/sol1.py

diff --git a/project_euler/problem_164/__init__.py b/project_euler/problem_164/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_164/sol1.py b/project_euler/problem_164/sol1.py
new file mode 100644
index 000000000000..5387c89bd757
--- /dev/null
+++ b/project_euler/problem_164/sol1.py
@@ -0,0 +1,65 @@
+"""
+Project Euler Problem 164: https://projecteuler.net/problem=164
+
+Three Consecutive Digital Sum Limit
+
+How many 20 digit numbers n (without any leading zero) exist such that no three
+consecutive digits of n have a sum greater than 9?
+
+Brute-force recursive solution with caching of intermediate results.
+"""
+
+
+def solve(
+    digit: int, prev1: int, prev2: int, sum_max: int, first: bool, cache: dict[str, int]
+) -> int:
+    """
+    Solve for remaining 'digit' digits, with previous 'prev1' digit, and
+    previous-previous 'prev2' digit, total sum of 'sum_max'.
+    Pass around 'cache' to store/reuse intermediate results.
+
+    >>> solve(digit=1, prev1=0, prev2=0, sum_max=9, first=True, cache={})
+    9
+    >>> solve(digit=1, prev1=0, prev2=0, sum_max=9, first=False, cache={})
+    10
+    """
+    if digit == 0:
+        return 1
+
+    cache_str = f"{digit},{prev1},{prev2}"
+    if cache_str in cache:
+        return cache[cache_str]
+
+    comb = 0
+    for curr in range(sum_max - prev1 - prev2 + 1):
+        if first and curr == 0:
+            continue
+
+        comb += solve(
+            digit=digit - 1,
+            prev1=curr,
+            prev2=prev1,
+            sum_max=sum_max,
+            first=False,
+            cache=cache,
+        )
+
+    cache[cache_str] = comb
+    return comb
+
+
+def solution(n_digits: int = 20) -> int:
+    """
+    Solves the problem for n_digits number of digits.
+
+    >>> solution(2)
+    45
+    >>> solution(10)
+    21838806
+    """
+    cache: dict[str, int] = {}
+    return solve(digit=n_digits, prev1=0, prev2=0, sum_max=9, first=True, cache=cache)
+
+
+if __name__ == "__main__":
+    print(f"{solution(10) = }")

From 40f4c510b6047d95d07f03c9915a53bbf84789e4 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Mon, 5 May 2025 15:14:56 +0800
Subject: [PATCH 100/135] Add solution for the Euler problem 190 (#12664)

* Add solution for the Euler project problem 164.

* Add solution for the Euler project problem 190.

* Delete project_euler/problem_164/sol1.py

* Delete project_euler/problem_164/__init__.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 project_euler/problem_190/__init__.py |  0
 project_euler/problem_190/sol1.py     | 48 +++++++++++++++++++++++++++
 2 files changed, 48 insertions(+)
 create mode 100644 project_euler/problem_190/__init__.py
 create mode 100644 project_euler/problem_190/sol1.py

diff --git a/project_euler/problem_190/__init__.py b/project_euler/problem_190/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_190/sol1.py b/project_euler/problem_190/sol1.py
new file mode 100644
index 000000000000..b18d45be16b4
--- /dev/null
+++ b/project_euler/problem_190/sol1.py
@@ -0,0 +1,48 @@
+"""
+Project Euler Problem 190: https://projecteuler.net/problem=190
+
+Maximising a Weighted Product
+
+Let S_m = (x_1, x_2, ..., x_m) be the m-tuple of positive real numbers with
+x_1 + x_2 + ... + x_m = m for which P_m = x_1 * x_2^2 * ... * x_m^m is maximised.
+
+For example, it can be verified that |_ P_10 _| = 4112
+(|_ _| is the integer part function).
+
+Find Sum_{m=2}^15 = |_ P_m _|.
+
+Solution:
+- Fix x_1 = m - x_2 - ... - x_m.
+- Calculate partial derivatives of P_m wrt the x_2, ..., x_m. This gives that
+  x_2 = 2 * x_1, x_3 = 3 * x_1, ..., x_m = m * x_1.
+- Calculate partial second order derivatives of P_m wrt the x_2, ..., x_m.
+  By plugging in the values from the previous step, can verify that solution is maximum.
+"""
+
+
+def solution(n: int = 15) -> int:
+    """
+    Calculate sum of |_ P_m _| for m from 2 to n.
+
+    >>> solution(2)
+    1
+    >>> solution(3)
+    2
+    >>> solution(4)
+    4
+    >>> solution(5)
+    10
+    """
+    total = 0
+    for m in range(2, n + 1):
+        x1 = 2 / (m + 1)
+        p = 1.0
+        for i in range(1, m + 1):
+            xi = i * x1
+            p *= xi**i
+        total += int(p)
+    return total
+
+
+if __name__ == "__main__":
+    print(f"{solution() = }")

From 7ed7f042feeb3567ace384a00707d83c327310ab Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 5 May 2025 19:23:31 +0100
Subject: [PATCH 101/135] [pre-commit.ci] pre-commit autoupdate (#12708)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.7 → v0.11.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.7...v0.11.8)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 DIRECTORY.md            | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 034493b10912..9e13416dc78d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.7
+    rev: v0.11.8
     hooks:
       - id: ruff
       - id: ruff-format
diff --git a/DIRECTORY.md b/DIRECTORY.md
index fa731e32ff23..04e09c29de97 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -1174,6 +1174,8 @@
     * [Sol1](project_euler/problem_144/sol1.py)
   * Problem 145
     * [Sol1](project_euler/problem_145/sol1.py)
+  * Problem 164
+    * [Sol1](project_euler/problem_164/sol1.py)
   * Problem 173
     * [Sol1](project_euler/problem_173/sol1.py)
   * Problem 174
@@ -1184,6 +1186,8 @@
     * [Sol1](project_euler/problem_187/sol1.py)
   * Problem 188
     * [Sol1](project_euler/problem_188/sol1.py)
+  * Problem 190
+    * [Sol1](project_euler/problem_190/sol1.py)
   * Problem 191
     * [Sol1](project_euler/problem_191/sol1.py)
   * Problem 203

From d9d56b10464f6825dc762c1665716e76b70c5fa2 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Wed, 7 May 2025 03:49:59 +0800
Subject: [PATCH 102/135] Add solution for the Euler project problem 345
 (#12666)

* Add solution for the Euler project problem 345.

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 project_euler/problem_345/__init__.py |   0
 project_euler/problem_345/sol1.py     | 117 ++++++++++++++++++++++++++
 2 files changed, 117 insertions(+)
 create mode 100644 project_euler/problem_345/__init__.py
 create mode 100644 project_euler/problem_345/sol1.py

diff --git a/project_euler/problem_345/__init__.py b/project_euler/problem_345/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_345/sol1.py b/project_euler/problem_345/sol1.py
new file mode 100644
index 000000000000..4234458c5ad5
--- /dev/null
+++ b/project_euler/problem_345/sol1.py
@@ -0,0 +1,117 @@
+"""
+Project Euler Problem 345: https://projecteuler.net/problem=345
+
+Matrix Sum
+
+We define the Matrix Sum of a matrix as the maximum possible sum of
+matrix elements such that none of the selected elements share the same row or column.
+
+For example, the Matrix Sum of the matrix below equals
+3315 ( = 863 + 383 + 343 + 959 + 767):
+      7  53 183 439 863
+    497 383 563  79 973
+    287  63 343 169 583
+    627 343 773 959 943
+    767 473 103 699 303
+
+Find the Matrix Sum of:
+      7  53 183 439 863 497 383 563  79 973 287  63 343 169 583
+    627 343 773 959 943 767 473 103 699 303 957 703 583 639 913
+    447 283 463  29  23 487 463 993 119 883 327 493 423 159 743
+    217 623   3 399 853 407 103 983  89 463 290 516 212 462 350
+    960 376 682 962 300 780 486 502 912 800 250 346 172 812 350
+    870 456 192 162 593 473 915  45 989 873 823 965 425 329 803
+    973 965 905 919 133 673 665 235 509 613 673 815 165 992 326
+    322 148 972 962 286 255 941 541 265 323 925 281 601  95 973
+    445 721  11 525 473  65 511 164 138 672  18 428 154 448 848
+    414 456 310 312 798 104 566 520 302 248 694 976 430 392 198
+    184 829 373 181 631 101 969 613 840 740 778 458 284 760 390
+    821 461 843 513  17 901 711 993 293 157 274  94 192 156 574
+     34 124   4 878 450 476 712 914 838 669 875 299 823 329 699
+    815 559 813 459 522 788 168 586 966 232 308 833 251 631 107
+    813 883 451 509 615  77 281 613 459 205 380 274 302  35 805
+
+Brute force solution, with caching intermediate steps to speed up the calculation.
+"""
+
+import numpy as np
+from numpy.typing import NDArray
+
+MATRIX_1 = [
+    "7 53 183 439 863",
+    "497 383 563 79 973",
+    "287 63 343 169 583",
+    "627 343 773 959 943",
+    "767 473 103 699 303",
+]
+
+MATRIX_2 = [
+    "7 53 183 439 863 497 383 563 79 973 287 63 343 169 583",
+    "627 343 773 959 943 767 473 103 699 303 957 703 583 639 913",
+    "447 283 463 29 23 487 463 993 119 883 327 493 423 159 743",
+    "217 623 3 399 853 407 103 983 89 463 290 516 212 462 350",
+    "960 376 682 962 300 780 486 502 912 800 250 346 172 812 350",
+    "870 456 192 162 593 473 915 45 989 873 823 965 425 329 803",
+    "973 965 905 919 133 673 665 235 509 613 673 815 165 992 326",
+    "322 148 972 962 286 255 941 541 265 323 925 281 601 95 973",
+    "445 721 11 525 473 65 511 164 138 672 18 428 154 448 848",
+    "414 456 310 312 798 104 566 520 302 248 694 976 430 392 198",
+    "184 829 373 181 631 101 969 613 840 740 778 458 284 760 390",
+    "821 461 843 513 17 901 711 993 293 157 274 94 192 156 574",
+    "34 124 4 878 450 476 712 914 838 669 875 299 823 329 699",
+    "815 559 813 459 522 788 168 586 966 232 308 833 251 631 107",
+    "813 883 451 509 615 77 281 613 459 205 380 274 302 35 805",
+]
+
+
+def solve(arr: NDArray, row: int, cols: set[int], cache: dict[str, int]) -> int:
+    """
+    Finds the max sum for array `arr` starting with row index `row`, and with columns
+    included in `cols`. `cache` is used for caching intermediate results.
+
+    >>> solve(arr=np.array([[1, 2], [3, 4]]), row=0, cols={0, 1}, cache={})
+    5
+    """
+
+    cache_id = f"{row}, {sorted(cols)}"
+    if cache_id in cache:
+        return cache[cache_id]
+
+    if row == len(arr):
+        return 0
+
+    max_sum = 0
+    for col in cols:
+        new_cols = cols - {col}
+        max_sum = max(
+            max_sum,
+            int(arr[row, col])
+            + solve(arr=arr, row=row + 1, cols=new_cols, cache=cache),
+        )
+    cache[cache_id] = max_sum
+    return max_sum
+
+
+def solution(matrix_str: list[str] = MATRIX_2) -> int:
+    """
+    Takes list of strings `matrix_str` to parse the matrix and calculates the max sum.
+
+    >>> solution(["1 2", "3 4"])
+    5
+    >>> solution(MATRIX_1)
+    3315
+    """
+
+    n = len(matrix_str)
+    arr = np.empty(shape=(n, n), dtype=int)
+    for row, matrix_row_str in enumerate(matrix_str):
+        matrix_row_list_str = matrix_row_str.split()
+        for col, elem_str in enumerate(matrix_row_list_str):
+            arr[row, col] = int(elem_str)
+
+    cache: dict[str, int] = {}
+    return solve(arr=arr, row=0, cols=set(range(n)), cache=cache)
+
+
+if __name__ == "__main__":
+    print(f"{solution() = }")

From b720f24b89c328944f8a0d6c18db0e09d9bcffba Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Sat, 10 May 2025 19:13:07 +0800
Subject: [PATCH 103/135] Add solution for the Euler project problem 95.
 (#12669)

* Add documentation and tests for the Euler project problem 95 solution.

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

* Update sol1.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 project_euler/problem_095/__init__.py |   0
 project_euler/problem_095/sol1.py     | 164 ++++++++++++++++++++++++++
 2 files changed, 164 insertions(+)
 create mode 100644 project_euler/problem_095/__init__.py
 create mode 100644 project_euler/problem_095/sol1.py

diff --git a/project_euler/problem_095/__init__.py b/project_euler/problem_095/__init__.py
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/project_euler/problem_095/sol1.py b/project_euler/problem_095/sol1.py
new file mode 100644
index 000000000000..82d84c5544de
--- /dev/null
+++ b/project_euler/problem_095/sol1.py
@@ -0,0 +1,164 @@
+"""
+Project Euler Problem 95: https://projecteuler.net/problem=95
+
+Amicable Chains
+
+The proper divisors of a number are all the divisors excluding the number itself.
+For example, the proper divisors of 28 are 1, 2, 4, 7, and 14.
+As the sum of these divisors is equal to 28, we call it a perfect number.
+
+Interestingly the sum of the proper divisors of 220 is 284 and
+the sum of the proper divisors of 284 is 220, forming a chain of two numbers.
+For this reason, 220 and 284 are called an amicable pair.
+
+Perhaps less well known are longer chains.
+For example, starting with 12496, we form a chain of five numbers:
+    12496 -> 14288 -> 15472 -> 14536 -> 14264 (-> 12496 -> ...)
+
+Since this chain returns to its starting point, it is called an amicable chain.
+
+Find the smallest member of the longest amicable chain with
+no element exceeding one million.
+
+Solution is doing the following:
+- Get relevant prime numbers
+- Iterate over product combination of prime numbers to generate all non-prime
+  numbers up to max number, by keeping track of prime factors
+- Calculate the sum of factors for each number
+- Iterate over found some factors to find longest chain
+"""
+
+from math import isqrt
+
+
+def generate_primes(max_num: int) -> list[int]:
+    """
+    Calculates the list of primes up to and including `max_num`.
+
+    >>> generate_primes(6)
+    [2, 3, 5]
+    """
+    are_primes = [True] * (max_num + 1)
+    are_primes[0] = are_primes[1] = False
+    for i in range(2, isqrt(max_num) + 1):
+        if are_primes[i]:
+            for j in range(i * i, max_num + 1, i):
+                are_primes[j] = False
+
+    return [prime for prime, is_prime in enumerate(are_primes) if is_prime]
+
+
+def multiply(
+    chain: list[int],
+    primes: list[int],
+    min_prime_idx: int,
+    prev_num: int,
+    max_num: int,
+    prev_sum: int,
+    primes_degrees: dict[int, int],
+) -> None:
+    """
+    Run over all prime combinations to generate non-prime numbers.
+
+    >>> chain = [0] * 3
+    >>> primes_degrees = {}
+    >>> multiply(
+    ...     chain=chain,
+    ...     primes=[2],
+    ...     min_prime_idx=0,
+    ...     prev_num=1,
+    ...     max_num=2,
+    ...     prev_sum=0,
+    ...     primes_degrees=primes_degrees,
+    ... )
+    >>> chain
+    [0, 0, 1]
+    >>> primes_degrees
+    {2: 1}
+    """
+
+    min_prime = primes[min_prime_idx]
+    num = prev_num * min_prime
+
+    min_prime_degree = primes_degrees.get(min_prime, 0)
+    min_prime_degree += 1
+    primes_degrees[min_prime] = min_prime_degree
+
+    new_sum = prev_sum * min_prime + (prev_sum + prev_num) * (min_prime - 1) // (
+        min_prime**min_prime_degree - 1
+    )
+    chain[num] = new_sum
+
+    for prime_idx in range(min_prime_idx, len(primes)):
+        if primes[prime_idx] * num > max_num:
+            break
+
+        multiply(
+            chain=chain,
+            primes=primes,
+            min_prime_idx=prime_idx,
+            prev_num=num,
+            max_num=max_num,
+            prev_sum=new_sum,
+            primes_degrees=primes_degrees.copy(),
+        )
+
+
+def find_longest_chain(chain: list[int], max_num: int) -> int:
+    """
+    Finds the smallest element of longest chain
+
+    >>> find_longest_chain(chain=[0, 0, 0, 0, 0, 0, 6], max_num=6)
+    6
+    """
+
+    max_len = 0
+    min_elem = 0
+    for start in range(2, len(chain)):
+        visited = {start}
+        elem = chain[start]
+        length = 1
+
+        while elem > 1 and elem <= max_num and elem not in visited:
+            visited.add(elem)
+            elem = chain[elem]
+            length += 1
+
+        if elem == start and length > max_len:
+            max_len = length
+            min_elem = start
+
+    return min_elem
+
+
+def solution(max_num: int = 1000000) -> int:
+    """
+    Runs the calculation for numbers <= `max_num`.
+
+    >>> solution(10)
+    6
+    >>> solution(200000)
+    12496
+    """
+
+    primes = generate_primes(max_num)
+    chain = [0] * (max_num + 1)
+    for prime_idx, prime in enumerate(primes):
+        if prime**2 > max_num:
+            break
+
+        multiply(
+            chain=chain,
+            primes=primes,
+            min_prime_idx=prime_idx,
+            prev_num=1,
+            max_num=max_num,
+            prev_sum=0,
+            primes_degrees={},
+        )
+
+    return find_longest_chain(chain=chain, max_num=max_num)
+
+
+if __name__ == "__main__":
+    print(f"{solution() = }")

From a728cc96ab4f05248ac3389365a26f01dfaf6f8e Mon Sep 17 00:00:00 2001
From: NidhaNureen <165757787+NidhaNureen@users.noreply.github.com>
Date: Sat, 10 May 2025 23:32:45 +1200
Subject: [PATCH 104/135] Added/Improved doctests for lowest_common_ancestor.py
 (#12673)

* added doctests to functions in lowest_common_ancestor.py

* fixed doctests to be less excessive

* Update lowest_common_ancestor.py

* Update lowest_common_ancestor.py

* Update lowest_common_ancestor.py

* Update lowest_common_ancestor.py

* Update lowest_common_ancestor.py

* Update lowest_common_ancestor.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 .../binary_tree/lowest_common_ancestor.py     | 54 +++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/data_structures/binary_tree/lowest_common_ancestor.py b/data_structures/binary_tree/lowest_common_ancestor.py
index 651037703b95..ea0e31256903 100644
--- a/data_structures/binary_tree/lowest_common_ancestor.py
+++ b/data_structures/binary_tree/lowest_common_ancestor.py
@@ -15,6 +15,8 @@ def swap(a: int, b: int) -> tuple[int, int]:
     (4, 3)
     >>> swap(67, 12)
     (12, 67)
+    >>> swap(3,-4)
+    (-4, 3)
     """
     a ^= b
     b ^= a
@@ -25,6 +27,23 @@ def swap(a: int, b: int) -> tuple[int, int]:
 def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]:
     """
     creating sparse table which saves each nodes 2^i-th parent
+    >>> max_node = 6
+    >>> parent = [[0, 0, 1, 1, 2, 2, 3]] + [[0] * 7 for _ in range(19)]
+    >>> parent = create_sparse(max_node=max_node, parent=parent)
+    >>> parent[0]
+    [0, 0, 1, 1, 2, 2, 3]
+    >>> parent[1]
+    [0, 0, 0, 0, 1, 1, 1]
+    >>> parent[2]
+    [0, 0, 0, 0, 0, 0, 0]
+
+    >>> max_node = 1
+    >>> parent = [[0, 0]] + [[0] * 2 for _ in range(19)]
+    >>> parent = create_sparse(max_node=max_node, parent=parent)
+    >>> parent[0]
+    [0, 0]
+    >>> parent[1]
+    [0, 0]
     """
     j = 1
     while (1 << j) < max_node:
@@ -38,6 +57,21 @@ def create_sparse(max_node: int, parent: list[list[int]]) -> list[list[int]]:
 def lowest_common_ancestor(
     u: int, v: int, level: list[int], parent: list[list[int]]
 ) -> int:
+    """
+    Return the lowest common ancestor between u and v
+
+    >>> level = [-1, 0, 1, 1, 2, 2, 2]
+    >>> parent = [[0, 0, 1, 1, 2, 2, 3],[0, 0, 0, 0, 1, 1, 1]] + \
+                    [[0] * 7 for _ in range(17)]
+    >>> lowest_common_ancestor(u=4, v=5, level=level, parent=parent)
+    2
+    >>> lowest_common_ancestor(u=4, v=6, level=level, parent=parent)
+    1
+    >>> lowest_common_ancestor(u=2, v=3, level=level, parent=parent)
+    1
+    >>> lowest_common_ancestor(u=6, v=6, level=level, parent=parent)
+    6
+    """
     # u must be deeper in the tree than v
     if level[u] < level[v]:
         u, v = swap(u, v)
@@ -68,6 +102,26 @@ def breadth_first_search(
     sets every nodes direct parent
     parent of root node is set to 0
     calculates depth of each node from root node
+    >>> level = [-1] * 7
+    >>> parent = [[0] * 7 for _ in range(20)]
+    >>> graph = {1: [2, 3], 2: [4, 5], 3: [6], 4: [], 5: [], 6: []}
+    >>> level, parent = breadth_first_search(
+    ...     level=level, parent=parent, max_node=6, graph=graph, root=1)
+    >>> level
+    [-1, 0, 1, 1, 2, 2, 2]
+    >>> parent[0]
+    [0, 0, 1, 1, 2, 2, 3]
+
+
+    >>> level = [-1] * 2
+    >>> parent = [[0] * 2 for _ in range(20)]
+    >>> graph = {1: []}
+    >>> level, parent = breadth_first_search(
+    ...     level=level, parent=parent, max_node=1, graph=graph, root=1)
+    >>> level
+    [-1, 0]
+    >>> parent[0]
+    [0, 0]
     """
     level[root] = 0
     q: Queue[int] = Queue(maxsize=max_node)

From 59c3c8bbf384ef05d3cb9862a41bba39bb098fe9 Mon Sep 17 00:00:00 2001
From: robohie <rogerbosolinndo34@gmail.com>
Date: Sat, 10 May 2025 12:47:22 +0100
Subject: [PATCH 105/135] Add N Input AND Gate (#12717)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update and_gate.py

J'ai nourri ce programme en ajoutant une porte And à n entrées.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update and_gate.py

Commentaires en anglais

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update and_gate.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 boolean_algebra/and_gate.py | 20 ++++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/boolean_algebra/and_gate.py b/boolean_algebra/and_gate.py
index 6ae66b5b0a77..650017b7ae10 100644
--- a/boolean_algebra/and_gate.py
+++ b/boolean_algebra/and_gate.py
@@ -1,8 +1,8 @@
 """
-An AND Gate is a logic gate in boolean algebra which results to 1 (True) if both the
-inputs are 1, and 0 (False) otherwise.
+An AND Gate is a logic gate in boolean algebra which results to 1 (True) if all the
+inputs are 1 (True), and 0 (False) otherwise.
 
-Following is the truth table of an AND Gate:
+Following is the truth table of a Two Input AND Gate:
     ------------------------------
     | Input 1 | Input 2 | Output |
     ------------------------------
@@ -12,7 +12,7 @@
     |    1    |    1    |    1   |
     ------------------------------
 
-Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
+Refer - https://www.geeksforgeeks.org/logic-gates/
 """
 
 
@@ -32,6 +32,18 @@ def and_gate(input_1: int, input_2: int) -> int:
     return int(input_1 and input_2)
 
 
+def n_input_and_gate(inputs: list[int]) -> int:
+    """
+    Calculate AND of a list of input values
+
+    >>> n_input_and_gate([1, 0, 1, 1, 0])
+    0
+    >>> n_input_and_gate([1, 1, 1, 1, 1])
+    1
+    """
+    return int(all(inputs))
+
+
 if __name__ == "__main__":
     import doctest
 

From 47a44abe23870ca0f7c8062601278645039b1c70 Mon Sep 17 00:00:00 2001
From: Alfredo Hernandez Baeza <alf_baza@hotmail.com>
Date: Sat, 10 May 2025 07:57:43 -0400
Subject: [PATCH 106/135] Improve longest_common_substring.py (#12705)

* Update longest_common_substring.py

- Combined the ans_index and ans_length into a single tuple to track the best match (position + length) more cleanly.

- Early exit for empty strings.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update longest_common_substring.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 dynamic_programming/longest_common_substring.py | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/dynamic_programming/longest_common_substring.py b/dynamic_programming/longest_common_substring.py
index ea5233eb2d17..3ba83f3d9f03 100644
--- a/dynamic_programming/longest_common_substring.py
+++ b/dynamic_programming/longest_common_substring.py
@@ -43,22 +43,25 @@ def longest_common_substring(text1: str, text2: str) -> str:
     if not (isinstance(text1, str) and isinstance(text2, str)):
         raise ValueError("longest_common_substring() takes two strings for inputs")
 
+    if not text1 or not text2:
+        return ""
+
     text1_length = len(text1)
     text2_length = len(text2)
 
     dp = [[0] * (text2_length + 1) for _ in range(text1_length + 1)]
-    ans_index = 0
-    ans_length = 0
+    end_pos = 0
+    max_length = 0
 
     for i in range(1, text1_length + 1):
         for j in range(1, text2_length + 1):
             if text1[i - 1] == text2[j - 1]:
                 dp[i][j] = 1 + dp[i - 1][j - 1]
-                if dp[i][j] > ans_length:
-                    ans_index = i
-                    ans_length = dp[i][j]
+                if dp[i][j] > max_length:
+                    end_pos = i
+                    max_length = dp[i][j]
 
-    return text1[ans_index - ans_length : ans_index]
+    return text1[end_pos - max_length : end_pos]
 
 
 if __name__ == "__main__":

From 131765574f5ccfaff4214a6f848412ce2fe4ab20 Mon Sep 17 00:00:00 2001
From: robohie <rogerbosolinndo34@gmail.com>
Date: Sat, 10 May 2025 21:18:02 +0100
Subject: [PATCH 107/135]  Fix error messages for
 horizontal_projectile_motion.py (#12722)

* Update horizontal_projectile_motion.py

This commit is about logic of this program. Changes made aim to allow a good understanding of what is done.

* Update horizontal_projectile_motion.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 physics/horizontal_projectile_motion.py | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/physics/horizontal_projectile_motion.py b/physics/horizontal_projectile_motion.py
index 60f21c2b39c4..32dc5eb36f35 100644
--- a/physics/horizontal_projectile_motion.py
+++ b/physics/horizontal_projectile_motion.py
@@ -17,7 +17,7 @@
 """
 
 # Importing packages
-from math import radians as angle_to_radians
+from math import radians as deg_to_rad
 from math import sin
 
 # Acceleration Constant on Earth (unit m/s^2)
@@ -31,10 +31,10 @@ def check_args(init_velocity: float, angle: float) -> None:
 
     # Ensure valid instance
     if not isinstance(init_velocity, (int, float)):
-        raise TypeError("Invalid velocity. Should be a positive number.")
+        raise TypeError("Invalid velocity. Should be an integer or float.")
 
     if not isinstance(angle, (int, float)):
-        raise TypeError("Invalid angle. Range is 1-90 degrees.")
+        raise TypeError("Invalid angle. Should be an integer or float.")
 
     # Ensure valid angle
     if angle > 90 or angle < 1:
@@ -71,7 +71,7 @@ def horizontal_distance(init_velocity: float, angle: float) -> float:
     ValueError: Invalid angle. Range is 1-90 degrees.
     """
     check_args(init_velocity, angle)
-    radians = angle_to_radians(2 * angle)
+    radians = deg_to_rad(2 * angle)
     return round(init_velocity**2 * sin(radians) / g, 2)
 
 
@@ -94,14 +94,14 @@ def max_height(init_velocity: float, angle: float) -> float:
     >>> max_height("a", 20)
     Traceback (most recent call last):
         ...
-    TypeError: Invalid velocity. Should be a positive number.
+    TypeError: Invalid velocity. Should be an integer or float.
     >>> horizontal_distance(30, "b")
     Traceback (most recent call last):
         ...
-    TypeError: Invalid angle. Range is 1-90 degrees.
+    TypeError: Invalid angle. Should be an integer or float.
     """
     check_args(init_velocity, angle)
-    radians = angle_to_radians(angle)
+    radians = deg_to_rad(angle)
     return round(init_velocity**2 * sin(radians) ** 2 / (2 * g), 2)
 
 
@@ -128,10 +128,10 @@ def total_time(init_velocity: float, angle: float) -> float:
     >>> total_time(30, "b")
     Traceback (most recent call last):
         ...
-    TypeError: Invalid angle. Range is 1-90 degrees.
+    TypeError: Invalid angle. Should be an integer or float.
     """
     check_args(init_velocity, angle)
-    radians = angle_to_radians(angle)
+    radians = deg_to_rad(angle)
     return round(2 * init_velocity * sin(radians) / g, 2)
 
 

From 95fb181f5a944427fdbc5766cbf4e1cb699d4a6d Mon Sep 17 00:00:00 2001
From: S Sajeev <167018420+SajeevSenthil@users.noreply.github.com>
Date: Sun, 11 May 2025 02:13:39 +0530
Subject: [PATCH 108/135] Add escape velocity calculator using standard physics
 formula (#12721)

* Added iterative solution for power calculation

* Added iterative solution for power calculation

* Added iterative solution for power calculation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added iterative solution for power calculation fixes #12709

* Added iterative solution for power calculation  FIXES NUMBER 12709

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Escape velocity is the minimum speed an object must have to break free from a celestial body's gravitational pull without further propulsion.
Takes input as the Mass of the Celestial body (M) and Radius fron the center of mass (M)

* Fix: added header comment to escape_velocity.py

* Trigger re-PR with a minor change

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix: resolve Ruff linter errors and add Wikipedia reference

* Delete maths/power_using_iteration.py

* Test doctests

* Update escape_velocity.py

* Update escape_velocity.py

* Update escape_velocity.py

* Update escape_velocity.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 physics/escape_velocity.py | 67 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)
 create mode 100644 physics/escape_velocity.py

diff --git a/physics/escape_velocity.py b/physics/escape_velocity.py
new file mode 100644
index 000000000000..e54ed5e50798
--- /dev/null
+++ b/physics/escape_velocity.py
@@ -0,0 +1,67 @@
+import math
+
+
+def escape_velocity(mass: float, radius: float) -> float:
+    """
+    Calculates the escape velocity needed to break free from a celestial body's
+    gravitational field.
+
+    The formula used is:
+        v = sqrt(2 * G * M / R)
+
+    where:
+        v = escape velocity (m/s)
+        G = gravitational constant (6.67430 * 10^-11 m^3 kg^-1 s^-2)
+        M = mass of the celestial body (kg)
+        R = radius from the center of mass (m)
+
+    Source:
+        https://en.wikipedia.org/wiki/Escape_velocity
+
+    Args:
+        mass (float): Mass of the celestial body in kilograms.
+        radius (float): Radius from the center of mass in meters.
+
+    Returns:
+        float: Escape velocity in meters per second, rounded to 3 decimal places.
+
+    Examples:
+        >>> escape_velocity(mass=5.972e24, radius=6.371e6)  # Earth
+        11185.978
+        >>> escape_velocity(mass=7.348e22, radius=1.737e6)  # Moon
+        2376.307
+        >>> escape_velocity(mass=1.898e27, radius=6.9911e7)  # Jupiter
+        60199.545
+        >>> escape_velocity(mass=0, radius=1.0)
+        0.0
+        >>> escape_velocity(mass=1.0, radius=0)
+        Traceback (most recent call last):
+            ...
+        ZeroDivisionError: Radius cannot be zero.
+    """
+    gravitational_constant = 6.67430e-11  # m^3 kg^-1 s^-2
+
+    if radius == 0:
+        raise ZeroDivisionError("Radius cannot be zero.")
+
+    velocity = math.sqrt(2 * gravitational_constant * mass / radius)
+    return round(velocity, 3)
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
+    print("Calculate escape velocity of a celestial body...\n")
+
+    try:
+        mass = float(input("Enter mass of the celestial body (in kgs): ").strip())
+        radius = float(input("Enter radius from the center of mass (in ms): ").strip())
+
+        velocity = escape_velocity(mass=mass, radius=radius)
+        print(f"Escape velocity is {velocity} m/s")
+
+    except ValueError:
+        print("Invalid input. Please enter valid numeric values.")
+    except ZeroDivisionError as e:
+        print(e)

From 1ea05feffec48250a3d6d0ccbd1378242d680cbc Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Mon, 12 May 2025 11:59:19 +0200
Subject: [PATCH 109/135] Prep for Python 3.14: Rename compression to
 data_compression (#12725)

* Prep for Python 3.14: Rename compression to data_compression

* updating DIRECTORY.md

---------

Co-authored-by: cclauss <cclauss@users.noreply.github.com>
---
 DIRECTORY.md                                  |  23 +++++++++++-------
 compression/image_data/__init__.py            |   0
 {compression => data_compression}/README.md   |   0
 {compression => data_compression}/__init__.py |   0
 .../burrows_wheeler.py                        |   0
 {compression => data_compression}/huffman.py  |   0
 .../image_data/PSNR-example-base.png          | Bin
 .../image_data/PSNR-example-comp-10.jpg       | Bin
 .../image_data/compressed_image.png           | Bin
 .../image_data/example_image.jpg              | Bin
 .../image_data/example_wikipedia_image.jpg    | Bin
 .../image_data/original_image.png             | Bin
 .../lempel_ziv.py                             |   0
 .../lempel_ziv_decompress.py                  |   0
 {compression => data_compression}/lz77.py     |   0
 .../peak_signal_to_noise_ratio.py             |   0
 .../run_length_encoding.py                    |   0
 pyproject.toml                                |   2 +-
 18 files changed, 15 insertions(+), 10 deletions(-)
 delete mode 100644 compression/image_data/__init__.py
 rename {compression => data_compression}/README.md (100%)
 rename {compression => data_compression}/__init__.py (100%)
 rename {compression => data_compression}/burrows_wheeler.py (100%)
 rename {compression => data_compression}/huffman.py (100%)
 rename {compression => data_compression}/image_data/PSNR-example-base.png (100%)
 rename {compression => data_compression}/image_data/PSNR-example-comp-10.jpg (100%)
 rename {compression => data_compression}/image_data/compressed_image.png (100%)
 rename {compression => data_compression}/image_data/example_image.jpg (100%)
 rename {compression => data_compression}/image_data/example_wikipedia_image.jpg (100%)
 rename {compression => data_compression}/image_data/original_image.png (100%)
 rename {compression => data_compression}/lempel_ziv.py (100%)
 rename {compression => data_compression}/lempel_ziv_decompress.py (100%)
 rename {compression => data_compression}/lz77.py (100%)
 rename {compression => data_compression}/peak_signal_to_noise_ratio.py (100%)
 rename {compression => data_compression}/run_length_encoding.py (100%)

diff --git a/DIRECTORY.md b/DIRECTORY.md
index 04e09c29de97..85dcc243462e 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -128,15 +128,6 @@
   * [Vigenere Cipher](ciphers/vigenere_cipher.py)
   * [Xor Cipher](ciphers/xor_cipher.py)
 
-## Compression
-  * [Burrows Wheeler](compression/burrows_wheeler.py)
-  * [Huffman](compression/huffman.py)
-  * [Lempel Ziv](compression/lempel_ziv.py)
-  * [Lempel Ziv Decompress](compression/lempel_ziv_decompress.py)
-  * [Lz77](compression/lz77.py)
-  * [Peak Signal To Noise Ratio](compression/peak_signal_to_noise_ratio.py)
-  * [Run Length Encoding](compression/run_length_encoding.py)
-
 ## Computer Vision
   * [Cnn Classification](computer_vision/cnn_classification.py)
   * [Flip Augmentation](computer_vision/flip_augmentation.py)
@@ -181,6 +172,15 @@
   * [Volume Conversions](conversions/volume_conversions.py)
   * [Weight Conversion](conversions/weight_conversion.py)
 
+## Data Compression
+  * [Burrows Wheeler](data_compression/burrows_wheeler.py)
+  * [Huffman](data_compression/huffman.py)
+  * [Lempel Ziv](data_compression/lempel_ziv.py)
+  * [Lempel Ziv Decompress](data_compression/lempel_ziv_decompress.py)
+  * [Lz77](data_compression/lz77.py)
+  * [Peak Signal To Noise Ratio](data_compression/peak_signal_to_noise_ratio.py)
+  * [Run Length Encoding](data_compression/run_length_encoding.py)
+
 ## Data Structures
   * Arrays
     * [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py)
@@ -884,6 +884,7 @@
   * [Centripetal Force](physics/centripetal_force.py)
   * [Coulombs Law](physics/coulombs_law.py)
   * [Doppler Frequency](physics/doppler_frequency.py)
+  * [Escape Velocity](physics/escape_velocity.py)
   * [Grahams Law](physics/grahams_law.py)
   * [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py)
   * [Hubble Parameter](physics/hubble_parameter.py)
@@ -1122,6 +1123,8 @@
     * [Sol1](project_euler/problem_092/sol1.py)
   * Problem 094
     * [Sol1](project_euler/problem_094/sol1.py)
+  * Problem 095
+    * [Sol1](project_euler/problem_095/sol1.py)
   * Problem 097
     * [Sol1](project_euler/problem_097/sol1.py)
   * Problem 099
@@ -1202,6 +1205,8 @@
     * [Sol1](project_euler/problem_234/sol1.py)
   * Problem 301
     * [Sol1](project_euler/problem_301/sol1.py)
+  * Problem 345
+    * [Sol1](project_euler/problem_345/sol1.py)
   * Problem 493
     * [Sol1](project_euler/problem_493/sol1.py)
   * Problem 551
diff --git a/compression/image_data/__init__.py b/compression/image_data/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000
diff --git a/compression/README.md b/data_compression/README.md
similarity index 100%
rename from compression/README.md
rename to data_compression/README.md
diff --git a/compression/__init__.py b/data_compression/__init__.py
similarity index 100%
rename from compression/__init__.py
rename to data_compression/__init__.py
diff --git a/compression/burrows_wheeler.py b/data_compression/burrows_wheeler.py
similarity index 100%
rename from compression/burrows_wheeler.py
rename to data_compression/burrows_wheeler.py
diff --git a/compression/huffman.py b/data_compression/huffman.py
similarity index 100%
rename from compression/huffman.py
rename to data_compression/huffman.py
diff --git a/compression/image_data/PSNR-example-base.png b/data_compression/image_data/PSNR-example-base.png
similarity index 100%
rename from compression/image_data/PSNR-example-base.png
rename to data_compression/image_data/PSNR-example-base.png
diff --git a/compression/image_data/PSNR-example-comp-10.jpg b/data_compression/image_data/PSNR-example-comp-10.jpg
similarity index 100%
rename from compression/image_data/PSNR-example-comp-10.jpg
rename to data_compression/image_data/PSNR-example-comp-10.jpg
diff --git a/compression/image_data/compressed_image.png b/data_compression/image_data/compressed_image.png
similarity index 100%
rename from compression/image_data/compressed_image.png
rename to data_compression/image_data/compressed_image.png
diff --git a/compression/image_data/example_image.jpg b/data_compression/image_data/example_image.jpg
similarity index 100%
rename from compression/image_data/example_image.jpg
rename to data_compression/image_data/example_image.jpg
diff --git a/compression/image_data/example_wikipedia_image.jpg b/data_compression/image_data/example_wikipedia_image.jpg
similarity index 100%
rename from compression/image_data/example_wikipedia_image.jpg
rename to data_compression/image_data/example_wikipedia_image.jpg
diff --git a/compression/image_data/original_image.png b/data_compression/image_data/original_image.png
similarity index 100%
rename from compression/image_data/original_image.png
rename to data_compression/image_data/original_image.png
diff --git a/compression/lempel_ziv.py b/data_compression/lempel_ziv.py
similarity index 100%
rename from compression/lempel_ziv.py
rename to data_compression/lempel_ziv.py
diff --git a/compression/lempel_ziv_decompress.py b/data_compression/lempel_ziv_decompress.py
similarity index 100%
rename from compression/lempel_ziv_decompress.py
rename to data_compression/lempel_ziv_decompress.py
diff --git a/compression/lz77.py b/data_compression/lz77.py
similarity index 100%
rename from compression/lz77.py
rename to data_compression/lz77.py
diff --git a/compression/peak_signal_to_noise_ratio.py b/data_compression/peak_signal_to_noise_ratio.py
similarity index 100%
rename from compression/peak_signal_to_noise_ratio.py
rename to data_compression/peak_signal_to_noise_ratio.py
diff --git a/compression/run_length_encoding.py b/data_compression/run_length_encoding.py
similarity index 100%
rename from compression/run_length_encoding.py
rename to data_compression/run_length_encoding.py
diff --git a/pyproject.toml b/pyproject.toml
index 60f8d4ffc96f..c320a2f2bbfe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -187,9 +187,9 @@ autoapi_dirs = [
   "boolean_algebra",
   "cellular_automata",
   "ciphers",
-  "compression",
   "computer_vision",
   "conversions",
+  "data_compression",
   "data_structures",
   "digital_image_processing",
   "divide_and_conquer",

From 088c74e84026fcb77b3e3e0c15c83111a6979ae7 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Mon, 12 May 2025 12:45:53 +0200
Subject: [PATCH 110/135] Delete empty source directory (#12730)

---
 source/__init__.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 source/__init__.py

diff --git a/source/__init__.py b/source/__init__.py
deleted file mode 100644
index e69de29bb2d1..000000000000

From 485f688d0683ca026959e1b68f12e5d221724860 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Mon, 12 May 2025 12:52:27 +0200
Subject: [PATCH 111/135] Add PEP723 header to scripts/validate_solutions.py
 (#12731)

Enable `uv run scripts/validate_solutions.py` or `pipx run scripts/validate_solutions.py`
* https://peps.python.org/pep-0723
* https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies
---
 scripts/validate_solutions.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py
index df5d01086bbe..c3f872203591 100755
--- a/scripts/validate_solutions.py
+++ b/scripts/validate_solutions.py
@@ -1,4 +1,13 @@
 #!/usr/bin/env python3
+
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "pytest",
+#     "requests",
+# ]
+# ///
+
 import hashlib
 import importlib.util
 import json

From f721e598e5677ed368b7dbd119092eb409ab2fb0 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Mon, 12 May 2025 13:33:33 +0200
Subject: [PATCH 112/135] Add a proper shebang line to
 scripts/validate_filenames.py (#12733)

---
 scripts/validate_filenames.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py
index 80399673cced..a7328e099dde 100755
--- a/scripts/validate_filenames.py
+++ b/scripts/validate_filenames.py
@@ -1,4 +1,5 @@
-#!python
+#!/usr/bin/env python3
+
 import os
 
 try:

From ee3a1732e007d32b5835635ac7c7fbb2b5464f53 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 12 May 2025 19:59:33 +0200
Subject: [PATCH 113/135] [pre-commit.ci] pre-commit autoupdate (#12736)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.8 → v0.11.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.8...v0.11.9)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 9e13416dc78d..288e3f591403 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.8
+    rev: v0.11.9
     hooks:
       - id: ruff
       - id: ruff-format

From 6e4d1b376546cdf347b6513ff5f493510b956309 Mon Sep 17 00:00:00 2001
From: S Sajeev <167018420+SajeevSenthil@users.noreply.github.com>
Date: Tue, 13 May 2025 14:44:05 +0530
Subject: [PATCH 114/135] Physics orbital_transfer_work (#12728)

* Added iterative solution for power calculation

* Added iterative solution for power calculation

* Added iterative solution for power calculation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added iterative solution for power calculation fixes #12709

* Added iterative solution for power calculation  FIXES NUMBER 12709

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Escape velocity is the minimum speed an object must have to break free from a celestial body's gravitational pull without further propulsion.
Takes input as the Mass of the Celestial body (M) and Radius fron the center of mass (M)

* Fix: added header comment to escape_velocity.py

* Trigger re-PR with a minor change

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix: resolve Ruff linter errors and add Wikipedia reference

* Add: work done calculation for orbital transfer between orbits

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update escape_velocity.py

* Delete maths/power_using_iteration.py

* Update and rename workdone.py to orbital_transfer_work.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 physics/orbital_transfer_work.py | 73 ++++++++++++++++++++++++++++++++
 1 file changed, 73 insertions(+)
 create mode 100644 physics/orbital_transfer_work.py

diff --git a/physics/orbital_transfer_work.py b/physics/orbital_transfer_work.py
new file mode 100644
index 000000000000..7fb90fe9be0e
--- /dev/null
+++ b/physics/orbital_transfer_work.py
@@ -0,0 +1,73 @@
+def orbital_transfer_work(
+    mass_central: float, mass_object: float, r_initial: float, r_final: float
+) -> str:
+    """
+    Calculates the work required to move an object from one orbit to another in a
+    gravitational field based on the change in total mechanical energy.
+
+    The formula used is:
+        W = (G * M * m / 2) * (1/r_initial - 1/r_final)
+
+    where:
+        W = work done (Joules)
+        G = gravitational constant (6.67430 * 10^-11 m^3 kg^-1 s^-2)
+        M = mass of the central body (kg)
+        m = mass of the orbiting object (kg)
+        r_initial = initial orbit radius (m)
+        r_final = final orbit radius (m)
+
+    Args:
+        mass_central (float): Mass of the central body (kg)
+        mass_object (float): Mass of the object being moved (kg)
+        r_initial (float): Initial orbital radius (m)
+        r_final (float): Final orbital radius (m)
+
+    Returns:
+        str: Work done in Joules as a string in scientific notation (3 decimals)
+
+    Examples:
+        >>> orbital_transfer_work(5.972e24, 1000, 6.371e6, 7e6)
+        '2.811e+09'
+        >>> orbital_transfer_work(5.972e24, 500, 7e6, 6.371e6)
+        '-1.405e+09'
+        >>> orbital_transfer_work(1.989e30, 1000, 1.5e11, 2.28e11)
+        '1.514e+11'
+    """
+    gravitational_constant = 6.67430e-11
+
+    if r_initial <= 0 or r_final <= 0:
+        raise ValueError("Orbital radii must be greater than zero.")
+
+    work = (gravitational_constant * mass_central * mass_object / 2) * (
+        1 / r_initial - 1 / r_final
+    )
+    return f"{work:.3e}"
+
+
+if __name__ == "__main__":
+    import doctest
+
+    doctest.testmod()
+    print("Orbital transfer work calculator\n")
+
+    try:
+        M = float(input("Enter mass of central body (kg): ").strip())
+        if M <= 0:
+            r1 = float(input("Enter initial orbit radius (m): ").strip())
+        if r1 <= 0:
+            raise ValueError("Initial orbit radius must be greater than zero.")
+
+        r2 = float(input("Enter final orbit radius (m): ").strip())
+        if r2 <= 0:
+            raise ValueError("Final orbit radius must be greater than zero.")
+        m = float(input("Enter mass of orbiting object (kg): ").strip())
+        if m <= 0:
+            raise ValueError("Mass of the orbiting object must be greater than zero.")
+        r1 = float(input("Enter initial orbit radius (m): ").strip())
+        r2 = float(input("Enter final orbit radius (m): ").strip())
+
+        result = orbital_transfer_work(M, m, r1, r2)
+        print(f"Work done in orbital transfer: {result} Joules")
+
+    except ValueError as e:
+        print(f"Input error: {e}")

From a2fa32c7ad8085c49021b28578440fc167627e8e Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Wed, 14 May 2025 03:42:11 +0200
Subject: [PATCH 115/135] Lukazlim: Replace dependency `requests` with `httpx`
 (#12744)

* Replace dependency `requests` with `httpx`

Fixes #12742
Signed-off-by: Lim, Lukaz Wei Hwang <lukaz.wei.hwang.lim@intel.com>

* updating DIRECTORY.md

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Signed-off-by: Lim, Lukaz Wei Hwang <lukaz.wei.hwang.lim@intel.com>
Co-authored-by: Lim, Lukaz Wei Hwang <lukaz.wei.hwang.lim@intel.com>
Co-authored-by: cclauss <cclauss@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 DIRECTORY.md                                  |    1 +
 machine_learning/linear_regression.py         |   12 +-
 physics/speeds_of_gas_molecules.py            |    8 +-
 pyproject.toml                                |    4 +-
 requirements.txt                              |    2 +-
 scripts/validate_solutions.py                 |    6 +-
 uv.lock                                       | 1108 +++++++++--------
 web_programming/co2_emission.py               |   13 +-
 web_programming/covid_stats_via_xpath.py      |   12 +-
 web_programming/crawl_google_results.py       |   18 +-
 .../crawl_google_scholar_citation.py          |   12 +-
 web_programming/currency_converter.py         |   11 +-
 web_programming/current_stock_price.py        |   14 +-
 web_programming/current_weather.py            |   13 +-
 web_programming/daily_horoscope.py            |   12 +-
 .../download_images_from_google_query.py      |   12 +-
 web_programming/emails_from_url.py            |   21 +-
 web_programming/fetch_anime_and_play.py       |   21 +-
 web_programming/fetch_bbc_news.py             |   11 +-
 web_programming/fetch_github_info.py          |   13 +-
 web_programming/fetch_jobs.py                 |   14 +-
 web_programming/fetch_quotes.py               |   13 +-
 web_programming/get_amazon_product_data.py    |   13 +-
 .../get_imdb_top_250_movies_csv.py            |   16 +-
 web_programming/get_ip_geolocation.py         |   13 +-
 web_programming/get_top_billionaires.py       |   12 +-
 web_programming/get_top_hn_posts.py           |   13 +-
 web_programming/giphy.py                      |   12 +-
 web_programming/instagram_crawler.py          |   14 +-
 web_programming/instagram_pic.py              |   20 +-
 web_programming/instagram_video.py            |   15 +-
 web_programming/nasa_data.py                  |   17 +-
 web_programming/open_google_results.py        |   25 +-
 web_programming/random_anime_character.py     |   15 +-
 web_programming/recaptcha_verification.py     |   11 +-
 web_programming/reddit.py                     |   16 +-
 web_programming/search_books_by_isbn.py       |   21 +-
 web_programming/slack_message.py              |   11 +-
 web_programming/test_fetch_github_info.py     |    4 +-
 web_programming/world_covid19_stats.py        |   26 +-
 40 files changed, 971 insertions(+), 654 deletions(-)

diff --git a/DIRECTORY.md b/DIRECTORY.md
index 85dcc243462e..4129f9c1a5e2 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -899,6 +899,7 @@
   * [N Body Simulation](physics/n_body_simulation.py)
   * [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py)
   * [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
+  * [Orbital Transfer Work](physics/orbital_transfer_work.py)
   * [Period Of Pendulum](physics/period_of_pendulum.py)
   * [Photoelectric Effect](physics/photoelectric_effect.py)
   * [Potential Energy](physics/potential_energy.py)
diff --git a/machine_learning/linear_regression.py b/machine_learning/linear_regression.py
index 1d11e5a9cc2b..5b1e663116cc 100644
--- a/machine_learning/linear_regression.py
+++ b/machine_learning/linear_regression.py
@@ -8,8 +8,16 @@
 Rating). We try to best fit a line through dataset and estimate the parameters.
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+#     "numpy",
+# ]
+# ///
+
+import httpx
 import numpy as np
-import requests
 
 
 def collect_dataset():
@@ -17,7 +25,7 @@ def collect_dataset():
     The dataset contains ADR vs Rating of a Player
     :return : dataset obtained from the link, as matrix
     """
-    response = requests.get(
+    response = httpx.get(
         "https://raw.githubusercontent.com/yashLadha/The_Math_of_Intelligence/"
         "master/Week1/ADRvsRating.csv",
         timeout=10,
diff --git a/physics/speeds_of_gas_molecules.py b/physics/speeds_of_gas_molecules.py
index 42f90a9fd6f3..3f50ffa5b45d 100644
--- a/physics/speeds_of_gas_molecules.py
+++ b/physics/speeds_of_gas_molecules.py
@@ -59,9 +59,9 @@ def avg_speed_of_molecule(temperature: float, molar_mass: float) -> float:
     Examples:
 
     >>> avg_speed_of_molecule(273, 0.028) # nitrogen at 273 K
-    454.3488755020387
+    454.3488755062257
     >>> avg_speed_of_molecule(300, 0.032) # oxygen at 300 K
-    445.52572733919885
+    445.5257273433045
     >>> avg_speed_of_molecule(-273, 0.028) # invalid temperature
     Traceback (most recent call last):
         ...
@@ -87,9 +87,9 @@ def mps_speed_of_molecule(temperature: float, molar_mass: float) -> float:
     Examples:
 
     >>> mps_speed_of_molecule(273, 0.028) # nitrogen at 273 K
-    402.65620701908966
+    402.65620702280023
     >>> mps_speed_of_molecule(300, 0.032) # oxygen at 300 K
-    394.836895549922
+    394.8368955535605
     >>> mps_speed_of_molecule(-273, 0.028) # invalid temperature
     Traceback (most recent call last):
         ...
diff --git a/pyproject.toml b/pyproject.toml
index c320a2f2bbfe..2ead5cd51ae8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,6 +11,7 @@ classifiers = [
 dependencies = [
   "beautifulsoup4>=4.12.3",
   "fake-useragent>=1.5.1",
+  "httpx>=0.28.1",
   "imageio>=2.36.1",
   "keras>=3.7",
   "lxml>=5.3",
@@ -19,7 +20,6 @@ dependencies = [
   "opencv-python>=4.10.0.84",
   "pandas>=2.2.3",
   "pillow>=11",
-  "requests>=2.32.3",
   "rich>=13.9.4",
   "scikit-learn>=1.5.2",
   "sphinx-pyproject>=0.3",
@@ -42,8 +42,8 @@ docs = [
   "sphinx-pyproject>=0.3",
 ]
 euler-validate = [
+  "httpx>=0.28.1",
   "numpy>=2.1.3",
-  "requests>=2.32.3",
 ]
 
 [tool.ruff]
diff --git a/requirements.txt b/requirements.txt
index b104505e01bc..66b5d8a6b94e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,6 @@
 beautifulsoup4
 fake-useragent
+httpx
 imageio
 keras
 lxml
@@ -8,7 +9,6 @@ numpy
 opencv-python
 pandas
 pillow
-requests
 rich
 scikit-learn
 sphinx-pyproject
diff --git a/scripts/validate_solutions.py b/scripts/validate_solutions.py
index c3f872203591..f426153b5683 100755
--- a/scripts/validate_solutions.py
+++ b/scripts/validate_solutions.py
@@ -3,8 +3,8 @@
 # /// script
 # requires-python = ">=3.13"
 # dependencies = [
+#     "httpx",
 #     "pytest",
-#     "requests",
 # ]
 # ///
 
@@ -15,8 +15,8 @@
 import pathlib
 from types import ModuleType
 
+import httpx
 import pytest
-import requests
 
 PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler")
 PROJECT_EULER_ANSWERS_PATH = pathlib.Path.cwd().joinpath(
@@ -66,7 +66,7 @@ def added_solution_file_path() -> list[pathlib.Path]:
         "Accept": "application/vnd.github.v3+json",
         "Authorization": "token " + os.environ["GITHUB_TOKEN"],
     }
-    files = requests.get(get_files_url(), headers=headers, timeout=10).json()
+    files = httpx.get(get_files_url(), headers=headers, timeout=10).json()
     for file in files:
         filepath = pathlib.Path.cwd().joinpath(file["filename"])
         if (
diff --git a/uv.lock b/uv.lock
index 077288f041a1..c75a1070745b 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,319 +1,361 @@
 version = 1
+revision = 2
 requires-python = ">=3.13"
 resolution-markers = [
-    "platform_system == 'Darwin'",
-    "platform_machine == 'aarch64' and platform_system == 'Linux'",
-    "(platform_machine != 'aarch64' and platform_system != 'Darwin') or (platform_system != 'Darwin' and platform_system != 'Linux')",
+    "sys_platform == 'darwin'",
+    "platform_machine == 'aarch64' and sys_platform == 'linux'",
+    "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')",
 ]
 
 [[package]]
 name = "absl-py"
-version = "2.1.0"
+version = "2.2.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055 }
+sdist = { url = "https://files.pythonhosted.org/packages/b5/f0/e6342091061ed3a46aadc116b13edd7bb5249c3ab1b3ef07f24b0c248fc3/absl_py-2.2.2.tar.gz", hash = "sha256:bf25b2c2eed013ca456918c453d687eab4e8309fba81ee2f4c1a6aa2494175eb", size = 119982, upload-time = "2025-04-03T12:41:04.55Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706 },
+    { url = "https://files.pythonhosted.org/packages/f6/d4/349f7f4bd5ea92dab34f5bb0fe31775ef6c311427a14d5a5b31ecb442341/absl_py-2.2.2-py3-none-any.whl", hash = "sha256:e5797bc6abe45f64fd95dc06394ca3f2bedf3b5d895e9da691c9ee3397d70092", size = 135565, upload-time = "2025-04-03T12:41:03.172Z" },
 ]
 
 [[package]]
 name = "alabaster"
 version = "1.0.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210 }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "idna" },
+    { name = "sniffio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929 },
+    { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" },
 ]
 
 [[package]]
 name = "astroid"
-version = "3.3.5"
+version = "3.3.10"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/38/1e/326fb1d3d83a3bb77c9f9be29d31f2901e35acb94b0605c3f2e5085047f9/astroid-3.3.5.tar.gz", hash = "sha256:5cfc40ae9f68311075d27ef68a4841bdc5cc7f6cf86671b49f00607d30188e2d", size = 397229 }
+sdist = { url = "https://files.pythonhosted.org/packages/00/c2/9b2de9ed027f9fe5734a6c0c0a601289d796b3caaf1e372e23fa88a73047/astroid-3.3.10.tar.gz", hash = "sha256:c332157953060c6deb9caa57303ae0d20b0fbdb2e59b4a4f2a6ba49d0a7961ce", size = 398941, upload-time = "2025-05-10T13:33:10.405Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/41/30/624365383fa4a40329c0f0bbbc151abc4a64e30dfc110fc8f6e2afcd02bb/astroid-3.3.5-py3-none-any.whl", hash = "sha256:a9d1c946ada25098d790e079ba2a1b112157278f3fb7e718ae6a9252f5835dc8", size = 274586 },
+    { url = "https://files.pythonhosted.org/packages/15/58/5260205b9968c20b6457ed82f48f9e3d6edf2f1f95103161798b73aeccf0/astroid-3.3.10-py3-none-any.whl", hash = "sha256:104fb9cb9b27ea95e847a94c003be03a9e039334a8ebca5ee27dafaf5c5711eb", size = 275388, upload-time = "2025-05-10T13:33:08.391Z" },
 ]
 
 [[package]]
 name = "babel"
-version = "2.16.0"
+version = "2.17.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/2a/74/f1bc80f23eeba13393b7222b11d95ca3af2c1e28edca18af487137eefed9/babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316", size = 9348104 }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ed/20/bc79bc575ba2e2a7f70e8a1155618bb1301eaa5132a8271373a6903f73f8/babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", size = 9587599 },
+    { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" },
 ]
 
 [[package]]
 name = "beautifulsoup4"
-version = "4.12.3"
+version = "4.13.4"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "soupsieve" },
+    { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/824b1195773ce6166d388573fc106ce56d4a805bd7427b624e063596ec58/beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", size = 581181 }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/b1/fe/e8c672695b37eecc5cbf43e1d0638d88d66ba3a44c4d321c796f4e59167f/beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed", size = 147925 },
+    { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" },
 ]
 
 [[package]]
 name = "certifi"
-version = "2024.8.30"
+version = "2025.4.26"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
+    { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" },
 ]
 
 [[package]]
 name = "charset-normalizer"
-version = "3.4.0"
+version = "3.4.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 }
+sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 },
-    { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 },
-    { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 },
-    { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 },
-    { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 },
-    { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 },
-    { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 },
-    { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 },
-    { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 },
-    { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 },
-    { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 },
-    { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 },
-    { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 },
-    { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 },
-    { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 },
-    { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 },
-]
-
-[[package]]
-name = "codespell"
-version = "2.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a0/a9/98353dfc7afcdf18cffd2dd3e959a25eaaf2728cf450caa59af89648a8e4/codespell-2.3.0.tar.gz", hash = "sha256:360c7d10f75e65f67bad720af7007e1060a5d395670ec11a7ed1fed9dd17471f", size = 329791 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/0e/20/b6019add11e84f821184234cea0ad91442373489ef7ccfa3d73a71b908fa/codespell-2.3.0-py3-none-any.whl", hash = "sha256:a9c7cef2501c9cfede2110fd6d4e5e62296920efe9abfb84648df866e47f58d1", size = 329167 },
+    { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" },
+    { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" },
+    { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" },
+    { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" },
+    { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" },
+    { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" },
+    { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" },
+    { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" },
+    { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" },
+    { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" },
+    { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" },
+    { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
 ]
 
 [[package]]
 name = "colorama"
 version = "0.4.6"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
+    { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
 ]
 
 [[package]]
 name = "contourpy"
-version = "1.3.1"
+version = "1.3.2"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/25/c2/fc7193cc5383637ff390a712e88e4ded0452c9fbcf84abe3de5ea3df1866/contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699", size = 13465753 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/9a/e7/de62050dce687c5e96f946a93546910bc67e483fe05324439e329ff36105/contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2", size = 271548 },
-    { url = "https://files.pythonhosted.org/packages/78/4d/c2a09ae014ae984c6bdd29c11e74d3121b25eaa117eca0bb76340efd7e1c/contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5", size = 255576 },
-    { url = "https://files.pythonhosted.org/packages/ab/8a/915380ee96a5638bda80cd061ccb8e666bfdccea38d5741cb69e6dbd61fc/contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81", size = 306635 },
-    { url = "https://files.pythonhosted.org/packages/29/5c/c83ce09375428298acd4e6582aeb68b1e0d1447f877fa993d9bf6cd3b0a0/contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2", size = 345925 },
-    { url = "https://files.pythonhosted.org/packages/29/63/5b52f4a15e80c66c8078a641a3bfacd6e07106835682454647aca1afc852/contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7", size = 318000 },
-    { url = "https://files.pythonhosted.org/packages/9a/e2/30ca086c692691129849198659bf0556d72a757fe2769eb9620a27169296/contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c", size = 322689 },
-    { url = "https://files.pythonhosted.org/packages/6b/77/f37812ef700f1f185d348394debf33f22d531e714cf6a35d13d68a7003c7/contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3", size = 1268413 },
-    { url = "https://files.pythonhosted.org/packages/3f/6d/ce84e79cdd128542ebeb268f84abb4b093af78e7f8ec504676673d2675bc/contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1", size = 1326530 },
-    { url = "https://files.pythonhosted.org/packages/72/22/8282f4eae20c73c89bee7a82a19c4e27af9b57bb602ecaa00713d5bdb54d/contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82", size = 175315 },
-    { url = "https://files.pythonhosted.org/packages/e3/d5/28bca491f65312b438fbf076589dcde7f6f966b196d900777f5811b9c4e2/contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd", size = 220987 },
-    { url = "https://files.pythonhosted.org/packages/2f/24/a4b285d6adaaf9746e4700932f579f1a7b6f9681109f694cfa233ae75c4e/contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30", size = 285001 },
-    { url = "https://files.pythonhosted.org/packages/48/1d/fb49a401b5ca4f06ccf467cd6c4f1fd65767e63c21322b29b04ec40b40b9/contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751", size = 268553 },
-    { url = "https://files.pythonhosted.org/packages/79/1e/4aef9470d13fd029087388fae750dccb49a50c012a6c8d1d634295caa644/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342", size = 310386 },
-    { url = "https://files.pythonhosted.org/packages/b0/34/910dc706ed70153b60392b5305c708c9810d425bde12499c9184a1100888/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c", size = 349806 },
-    { url = "https://files.pythonhosted.org/packages/31/3c/faee6a40d66d7f2a87f7102236bf4780c57990dd7f98e5ff29881b1b1344/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f", size = 321108 },
-    { url = "https://files.pythonhosted.org/packages/17/69/390dc9b20dd4bb20585651d7316cc3054b7d4a7b4f8b710b2b698e08968d/contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda", size = 327291 },
-    { url = "https://files.pythonhosted.org/packages/ef/74/7030b67c4e941fe1e5424a3d988080e83568030ce0355f7c9fc556455b01/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242", size = 1263752 },
-    { url = "https://files.pythonhosted.org/packages/f0/ed/92d86f183a8615f13f6b9cbfc5d4298a509d6ce433432e21da838b4b63f4/contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1", size = 1318403 },
-    { url = "https://files.pythonhosted.org/packages/b3/0e/c8e4950c77dcfc897c71d61e56690a0a9df39543d2164040301b5df8e67b/contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1", size = 185117 },
-    { url = "https://files.pythonhosted.org/packages/c1/31/1ae946f11dfbd229222e6d6ad8e7bd1891d3d48bde5fbf7a0beb9491f8e3/contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546", size = 236668 },
+sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" },
+    { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" },
+    { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" },
+    { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" },
+    { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" },
+    { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" },
+    { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" },
+    { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" },
+    { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" },
+    { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" },
+    { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" },
+    { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" },
+    { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" },
+    { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" },
+    { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" },
+    { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" },
+    { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" },
 ]
 
 [[package]]
 name = "coverage"
-version = "7.6.8"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ab/75/aecfd0a3adbec6e45753976bc2a9fed62b42cea9a206d10fd29244a77953/coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", size = 801425 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/9a/84/6f0ccf94a098ac3d6d6f236bd3905eeac049a9e0efcd9a63d4feca37ac4b/coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", size = 207313 },
-    { url = "https://files.pythonhosted.org/packages/db/2b/e3b3a3a12ebec738c545897ac9f314620470fcbc368cdac88cf14974ba20/coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", size = 207574 },
-    { url = "https://files.pythonhosted.org/packages/db/c0/5bf95d42b6a8d21dfce5025ce187f15db57d6460a59b67a95fe8728162f1/coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", size = 240090 },
-    { url = "https://files.pythonhosted.org/packages/57/b8/d6fd17d1a8e2b0e1a4e8b9cb1f0f261afd422570735899759c0584236916/coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", size = 237237 },
-    { url = "https://files.pythonhosted.org/packages/d4/e4/a91e9bb46809c8b63e68fc5db5c4d567d3423b6691d049a4f950e38fbe9d/coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", size = 239225 },
-    { url = "https://files.pythonhosted.org/packages/31/9c/9b99b0591ec4555b7292d271e005f27b465388ce166056c435b288db6a69/coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", size = 238888 },
-    { url = "https://files.pythonhosted.org/packages/a6/85/285c2df9a04bc7c31f21fd9d4a24d19e040ec5e2ff06e572af1f6514c9e7/coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", size = 236974 },
-    { url = "https://files.pythonhosted.org/packages/cb/a1/95ec8522206f76cdca033bf8bb61fff56429fb414835fc4d34651dfd29fc/coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", size = 238815 },
-    { url = "https://files.pythonhosted.org/packages/8d/ac/687e9ba5e6d0979e9dab5c02e01c4f24ac58260ef82d88d3b433b3f84f1e/coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", size = 209957 },
-    { url = "https://files.pythonhosted.org/packages/2f/a3/b61cc8e3fcf075293fb0f3dee405748453c5ba28ac02ceb4a87f52bdb105/coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", size = 210711 },
-    { url = "https://files.pythonhosted.org/packages/ee/4b/891c8b9acf1b62c85e4a71dac142ab9284e8347409b7355de02e3f38306f/coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", size = 208053 },
-    { url = "https://files.pythonhosted.org/packages/18/a9/9e330409b291cc002723d339346452800e78df1ce50774ca439ade1d374f/coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", size = 208329 },
-    { url = "https://files.pythonhosted.org/packages/9c/0d/33635fd429f6589c6e1cdfc7bf581aefe4c1792fbff06383f9d37f59db60/coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", size = 251052 },
-    { url = "https://files.pythonhosted.org/packages/23/32/8a08da0e46f3830bbb9a5b40614241b2e700f27a9c2889f53122486443ed/coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", size = 246765 },
-    { url = "https://files.pythonhosted.org/packages/56/3f/3b86303d2c14350fdb1c6c4dbf9bc76000af2382f42ca1d4d99c6317666e/coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", size = 249125 },
-    { url = "https://files.pythonhosted.org/packages/36/cb/c4f081b9023f9fd8646dbc4ef77be0df090263e8f66f4ea47681e0dc2cff/coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", size = 248615 },
-    { url = "https://files.pythonhosted.org/packages/32/ee/53bdbf67760928c44b57b2c28a8c0a4bf544f85a9ee129a63ba5c78fdee4/coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", size = 246507 },
-    { url = "https://files.pythonhosted.org/packages/57/49/5a57910bd0af6d8e802b4ca65292576d19b54b49f81577fd898505dee075/coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", size = 247785 },
-    { url = "https://files.pythonhosted.org/packages/bd/37/e450c9f6b297c79bb9858407396ed3e084dcc22990dd110ab01d5ceb9770/coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", size = 210605 },
-    { url = "https://files.pythonhosted.org/packages/44/79/7d0c7dd237c6905018e2936cd1055fe1d42e7eba2ebab3c00f4aad2a27d7/coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", size = 211777 },
+version = "7.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" },
+    { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" },
+    { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" },
+    { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" },
+    { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" },
+    { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" },
+    { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" },
+    { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" },
+    { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" },
+    { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" },
+    { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" },
+    { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" },
+    { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" },
+    { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" },
+    { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" },
+    { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" },
+    { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" },
 ]
 
 [[package]]
 name = "cycler"
 version = "0.12.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615 }
+sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321 },
+    { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" },
 ]
 
 [[package]]
 name = "docutils"
 version = "0.21.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444 }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408 },
+    { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
 ]
 
 [[package]]
 name = "dom-toml"
-version = "2.0.0"
+version = "2.0.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "domdf-python-tools" },
     { name = "tomli" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/70/34/f7690cf288eaa86b55c8f1b890d0834e6df44a026a88eca12274fcd624ab/dom_toml-2.0.0.tar.gz", hash = "sha256:3c07e8436538994974127b1ae037661d1a779ac915c44fd06b3ab5fe140ff589", size = 11133 }
+sdist = { url = "https://files.pythonhosted.org/packages/fd/38/5df2387f84efce3c7ef8d211cae6ca2736a730efc13b7d15a9584bb81b30/dom_toml-2.0.1.tar.gz", hash = "sha256:31c5874595c777f41cc191c34c66dbea215c8289a7b148b425fb7a10c3f4fbc8", size = 11180, upload-time = "2025-02-05T11:59:36.552Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/22/99/b6fc87dff3138491d81676bdcbf1531080925ba41486ec1dafd86e33fdbc/dom_toml-2.0.0-py3-none-any.whl", hash = "sha256:0b6d02a72bcbc6be8175c61afc30623bbb6b74c4650f2a806fbc3fb7fe86935d", size = 13376 },
+    { url = "https://files.pythonhosted.org/packages/33/c6/57f64b0e93da6f43393dfcc1281ee1ce8eafbeb940d3861056caa28ae818/dom_toml-2.0.1-py3-none-any.whl", hash = "sha256:6681a793c94a5d1fb7b59690009cc3b1dfb1bcaae108fb8054c712a8f128efc7", size = 13459, upload-time = "2025-02-05T11:59:34.706Z" },
 ]
 
 [[package]]
 name = "domdf-python-tools"
-version = "3.9.0"
+version = "3.10.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "natsort" },
     { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/6b/78/974e10c583ba9d2302e748c9585313a7f2c7ba00e4f600324f432e38fe68/domdf_python_tools-3.9.0.tar.gz", hash = "sha256:1f8a96971178333a55e083e35610d7688cd7620ad2b99790164e1fc1a3614c18", size = 103792 }
+sdist = { url = "https://files.pythonhosted.org/packages/36/8b/ab2d8a292bba8fe3135cacc8bfd3576710a14b8f2d0a8cde19130d5c9d21/domdf_python_tools-3.10.0.tar.gz", hash = "sha256:2ae308d2f4f1e9145f5f4ba57f840fbfd1c2983ee26e4824347789649d3ae298", size = 100458, upload-time = "2025-02-12T17:34:05.747Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/de/e9/7447a88b217650a74927d3444a89507986479a69b83741900eddd34167fe/domdf_python_tools-3.9.0-py3-none-any.whl", hash = "sha256:4e1ef365cbc24627d6d1e90cf7d46d8ab8df967e1237f4a26885f6986c78872e", size = 127106 },
+    { url = "https://files.pythonhosted.org/packages/5b/11/208f72084084d3f6a2ed5ebfdfc846692c3f7ad6dce65e400194924f7eed/domdf_python_tools-3.10.0-py3-none-any.whl", hash = "sha256:5e71c1be71bbcc1f881d690c8984b60e64298ec256903b3147f068bc33090c36", size = 126860, upload-time = "2025-02-12T17:34:04.093Z" },
 ]
 
 [[package]]
 name = "fake-useragent"
-version = "1.5.1"
+version = "2.2.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/24/a1/1f662631ab153975fa8dbf09296324ecbaf53370dce922054e8de6b57370/fake-useragent-1.5.1.tar.gz", hash = "sha256:6387269f5a2196b5ba7ed8935852f75486845a1c95c50e72460e6a8e762f5c49", size = 22631 }
+sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/e4/99/60d8cf1b26938c2e0a57e232f7f15641dfcd6f8deda454d73e4145910ff6/fake_useragent-1.5.1-py3-none-any.whl", hash = "sha256:57415096557c8a4e23b62a375c21c55af5fd4ba30549227f562d2c4f5b60e3b3", size = 17190 },
+    { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" },
 ]
 
 [[package]]
 name = "fonttools"
-version = "4.55.0"
+version = "4.58.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d7/4e/053fe1b5c0ce346c0a9d0557492c654362bafb14f026eae0d3ee98009152/fonttools-4.55.0.tar.gz", hash = "sha256:7636acc6ab733572d5e7eec922b254ead611f1cdad17be3f0be7418e8bfaca71", size = 3490431 }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/cf/4d037663e2a1fe30fddb655d755d76e18624be44ad467c07412c2319ab97/fonttools-4.58.0.tar.gz", hash = "sha256:27423d0606a2c7b336913254bf0b1193ebd471d5f725d665e875c5e88a011a43", size = 3514522, upload-time = "2025-05-10T17:36:35.886Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/c3/87/a669ac26c6077e37ffb06abf29c5571789eefe518d06c52df392181ee694/fonttools-4.55.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8118dc571921dc9e4b288d9cb423ceaf886d195a2e5329cc427df82bba872cd9", size = 2752519 },
-    { url = "https://files.pythonhosted.org/packages/0c/e9/4822ad238fe215133c7df20f1cdb1a58cfb634a31523e77ff0fb2033970a/fonttools-4.55.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01124f2ca6c29fad4132d930da69158d3f49b2350e4a779e1efbe0e82bd63f6c", size = 2286819 },
-    { url = "https://files.pythonhosted.org/packages/3e/a4/d7941c3897129e60fe68d20e4819fda4d0c4858d77badae0e80ca6440b36/fonttools-4.55.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ffd58d2691f11f7c8438796e9f21c374828805d33e83ff4b76e4635633674c", size = 4770382 },
-    { url = "https://files.pythonhosted.org/packages/31/cf/c51ea1348f9fba9c627439afad9dee0090040809ab431f4422b5bfdda34c/fonttools-4.55.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5435e5f1eb893c35c2bc2b9cd3c9596b0fcb0a59e7a14121562986dd4c47b8dd", size = 4858336 },
-    { url = "https://files.pythonhosted.org/packages/73/be/36c1fe0e5c9a96b068ddd7e82001243bbe7fe12549c8d14e1bd025bf40c9/fonttools-4.55.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d12081729280c39d001edd0f4f06d696014c26e6e9a0a55488fabc37c28945e4", size = 4756072 },
-    { url = "https://files.pythonhosted.org/packages/5c/18/6dd381c29f215a017f79aa9fea0722424a0046b47991c4390a78ff87ce0c/fonttools-4.55.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7ad1f1b98ab6cb927ab924a38a8649f1ffd7525c75fe5b594f5dab17af70e18", size = 5008668 },
-    { url = "https://files.pythonhosted.org/packages/b8/95/316f20092b389b927dba1d1dccd3f541853f96e707e210f1b9f4e7bacdd5/fonttools-4.55.0-cp313-cp313-win32.whl", hash = "sha256:abe62987c37630dca69a104266277216de1023cf570c1643bb3a19a9509e7a1b", size = 2155841 },
-    { url = "https://files.pythonhosted.org/packages/35/ca/b4638aa3e446184892e2f9cc8ef44bb506f47fea04580df7fb84f5a4363d/fonttools-4.55.0-cp313-cp313-win_amd64.whl", hash = "sha256:2863555ba90b573e4201feaf87a7e71ca3b97c05aa4d63548a4b69ea16c9e998", size = 2200587 },
-    { url = "https://files.pythonhosted.org/packages/b4/4a/786589606d4989cb34d8bc766cd687d955aaf3039c367fe7104bcf82dc98/fonttools-4.55.0-py3-none-any.whl", hash = "sha256:12db5888cd4dd3fcc9f0ee60c6edd3c7e1fd44b7dd0f31381ea03df68f8a153f", size = 1100249 },
+    { url = "https://files.pythonhosted.org/packages/0c/d7/d77cae11c445916d767cace93ba8283b3f360197d95d7470b90a9e984e10/fonttools-4.58.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4809790f2371d8a08e59e1ce2b734c954cf09742e75642d7f4c46cfdac488fdd", size = 2728320, upload-time = "2025-05-10T17:35:56.455Z" },
+    { url = "https://files.pythonhosted.org/packages/77/48/7d8b3c519ef4b48081d40310262224a38785e39a8610ccb92a229a6f085d/fonttools-4.58.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b00f240280f204ce4546b05ff3515bf8ff47a9cae914c718490025ea2bb9b324", size = 2302570, upload-time = "2025-05-10T17:35:58.794Z" },
+    { url = "https://files.pythonhosted.org/packages/2c/48/156b83eb8fb7261056e448bfda1b495b90e761b28ec23cee10e3e19f1967/fonttools-4.58.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a62015ad463e1925544e9159dd6eefe33ebfb80938d5ab15d8b1c4b354ff47b", size = 4790066, upload-time = "2025-05-10T17:36:01.174Z" },
+    { url = "https://files.pythonhosted.org/packages/60/49/aaecb1b3cea2b9b9c7cea6240d6bc8090feb5489a6fbf93cb68003be979b/fonttools-4.58.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ceef6f6ab58061a811967e3e32e630747fcb823dcc33a9a2c80e2d0d17cb292", size = 4861076, upload-time = "2025-05-10T17:36:03.663Z" },
+    { url = "https://files.pythonhosted.org/packages/dc/c8/97cbb41bee81ea9daf6109e0f3f70a274a3c69418e5ac6b0193f5dacf506/fonttools-4.58.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c7be21ac52370b515cdbdd0f400803fd29432a4fa4ddb4244ac8b322e54f36c0", size = 4858394, upload-time = "2025-05-10T17:36:06.087Z" },
+    { url = "https://files.pythonhosted.org/packages/4d/23/c2c231457361f869a7d7374a557208e303b469d48a4a697c0fb249733ea1/fonttools-4.58.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:85836be4c3c4aacf6fcb7a6f263896d0e9ce431da9fa6fe9213d70f221f131c9", size = 5002160, upload-time = "2025-05-10T17:36:08.178Z" },
+    { url = "https://files.pythonhosted.org/packages/a9/e0/c2262f941a43b810c5c192db94b5d1ce8eda91bec2757f7e2416398f4072/fonttools-4.58.0-cp313-cp313-win32.whl", hash = "sha256:2b32b7130277bd742cb8c4379a6a303963597d22adea77a940343f3eadbcaa4c", size = 2171919, upload-time = "2025-05-10T17:36:10.644Z" },
+    { url = "https://files.pythonhosted.org/packages/8f/ee/e4aa7bb4ce510ad57a808d321df1bbed1eeb6e1dfb20aaee1a5d9c076849/fonttools-4.58.0-cp313-cp313-win_amd64.whl", hash = "sha256:75e68ee2ec9aaa173cf5e33f243da1d51d653d5e25090f2722bc644a78db0f1a", size = 2222972, upload-time = "2025-05-10T17:36:12.495Z" },
+    { url = "https://files.pythonhosted.org/packages/9b/1f/4417c26e26a1feab85a27e927f7a73d8aabc84544be8ba108ce4aa90eb1e/fonttools-4.58.0-py3-none-any.whl", hash = "sha256:c96c36880be2268be409df7b08c5b5dacac1827083461a6bc2cb07b8cbcec1d7", size = 1111440, upload-time = "2025-05-10T17:36:33.607Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
 ]
 
 [[package]]
 name = "h5py"
-version = "3.12.1"
+version = "3.13.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 }
+sdist = { url = "https://files.pythonhosted.org/packages/03/2e/a22d6a8bfa6f8be33e7febd985680fba531562795f0a9077ed1eb047bfb0/h5py-3.13.0.tar.gz", hash = "sha256:1870e46518720023da85d0895a1960ff2ce398c5671eac3b1a41ec696b7105c3", size = 414876, upload-time = "2025-02-18T16:04:01.824Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/60/43/f276f27921919a9144074320ce4ca40882fc67b3cfee81c3f5c7df083e97/h5py-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e520ec76de00943dd017c8ea3f354fa1d2f542eac994811943a8faedf2a7d5cb", size = 3358040, upload-time = "2025-02-18T16:03:20.579Z" },
+    { url = "https://files.pythonhosted.org/packages/1b/86/ad4a4cf781b08d4572be8bbdd8f108bb97b266a14835c640dc43dafc0729/h5py-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e79d8368cd9295045956bfb436656bea3f915beaa11d342e9f79f129f5178763", size = 2892766, upload-time = "2025-02-18T16:03:26.831Z" },
+    { url = "https://files.pythonhosted.org/packages/69/84/4c6367d6b58deaf0fa84999ec819e7578eee96cea6cbd613640d0625ed5e/h5py-3.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56dd172d862e850823c4af02dc4ddbc308f042b85472ffdaca67f1598dff4a57", size = 4664255, upload-time = "2025-02-18T16:03:31.903Z" },
+    { url = "https://files.pythonhosted.org/packages/fd/41/bc2df86b72965775f6d621e0ee269a5f3ac23e8f870abf519de9c7d93b4d/h5py-3.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be949b46b7388074c5acae017fbbe3e5ba303fd9daaa52157fdfef30bbdacadd", size = 4927580, upload-time = "2025-02-18T16:03:36.429Z" },
+    { url = "https://files.pythonhosted.org/packages/97/34/165b87ea55184770a0c1fcdb7e017199974ad2e271451fd045cfe35f3add/h5py-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:4f97ecde7ac6513b21cd95efdfc38dc6d19f96f6ca6f2a30550e94e551458e0a", size = 2940890, upload-time = "2025-02-18T16:03:41.037Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "certifi" },
+    { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "anyio" },
+    { name = "certifi" },
+    { name = "httpcore" },
+    { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 },
-    { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 },
-    { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 },
-    { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 },
-    { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 },
+    { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
 ]
 
 [[package]]
 name = "idna"
 version = "3.10"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
+sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
+    { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
 ]
 
 [[package]]
 name = "imageio"
-version = "2.36.1"
+version = "2.37.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
     { name = "pillow" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/70/aa/2e7a49259339e691ff2b477ae0696b1784a09313c5872700bbbdd00a3030/imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62", size = 389522 }
+sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/5c/f9/f78e7f5ac8077c481bf6b43b8bc736605363034b3d5eb3ce8eb79f53f5f1/imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf", size = 315435 },
+    { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" },
 ]
 
 [[package]]
 name = "imagesize"
 version = "1.4.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026, upload-time = "2022-07-01T12:21:05.687Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 },
+    { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769, upload-time = "2022-07-01T12:21:02.467Z" },
 ]
 
 [[package]]
 name = "iniconfig"
-version = "2.0.0"
+version = "2.1.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
+sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
+    { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
 ]
 
 [[package]]
 name = "jinja2"
-version = "3.1.4"
+version = "3.1.6"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "markupsafe" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/ed/55/39036716d19cab0747a5020fc7e907f362fbf48c984b14e62127f7e68e5d/jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", size = 240245 }
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/31/80/3a54838c3fb461f6fec263ebf3a3a41771bd05190238de3486aae8540c36/jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d", size = 133271 },
+    { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
 ]
 
 [[package]]
 name = "joblib"
-version = "1.4.2"
+version = "1.5.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/64/33/60135848598c076ce4b231e1b1895170f45fbcaeaa2c9d5e38b04db70c35/joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e", size = 2116621 }
+sdist = { url = "https://files.pythonhosted.org/packages/30/08/8bd4a0250247861420a040b33ccf42f43c426ac91d99405374ef117e5872/joblib-1.5.0.tar.gz", hash = "sha256:d8757f955389a3dd7a23152e43bc297c2e0c2d3060056dad0feefc88a06939b5", size = 330234, upload-time = "2025-05-03T21:09:39.553Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/91/29/df4b9b42f2be0b623cbd5e2140cafcaa2bef0759a00b7b70104dcfe2fb51/joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", size = 301817 },
+    { url = "https://files.pythonhosted.org/packages/da/d3/13ee227a148af1c693654932b8b0b02ed64af5e1f7406d56b088b57574cd/joblib-1.5.0-py3-none-any.whl", hash = "sha256:206144b320246485b712fc8cc51f017de58225fa8b414a1fe1764a7231aca491", size = 307682, upload-time = "2025-05-03T21:09:37.892Z" },
 ]
 
 [[package]]
 name = "keras"
-version = "3.7.0"
+version = "3.9.2"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "absl-py" },
@@ -325,58 +367,70 @@ dependencies = [
     { name = "packaging" },
     { name = "rich" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/c9/c3/56fc6800c5eab94bd0f5e930751bd4c0fa1ee0aee272fad4a72723ffae87/keras-3.7.0.tar.gz", hash = "sha256:a4451a5591e75dfb414d0b84a3fd2fb9c0240cc87ebe7e397f547ce10b0e67b7", size = 924719 }
+sdist = { url = "https://files.pythonhosted.org/packages/28/4e/82a1176f1ea70982ade0f7e360c6f259b68203f64a108cc1967500d4b7ff/keras-3.9.2.tar.gz", hash = "sha256:322aab6418ee3de1e2bd0871b60a07f0e444e744a7e8cba79af8b42408879ecf", size = 1002482, upload-time = "2025-04-02T19:03:08.953Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/8a/bf/9e3f10e55df30b0fb4bf6c2ee7d50bda2e070599b86f62ea3f9954af172b/keras-3.7.0-py3-none-any.whl", hash = "sha256:546a64f302e4779c129c06d9826fa586de752cdfd43d7dc4010c31b282587969", size = 1228365 },
+    { url = "https://files.pythonhosted.org/packages/3f/0a/678ebcf4b6dad6ad63dfc2445d190f79a97fa7bc7150f57a6c505459e2bc/keras-3.9.2-py3-none-any.whl", hash = "sha256:404427856c2dc30e38c9fa6fa6a13ffb1844a8c35af312ca32a8e7dea9840f1e", size = 1341966, upload-time = "2025-04-02T19:03:07.502Z" },
 ]
 
 [[package]]
 name = "kiwisolver"
-version = "1.4.7"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/85/4d/2255e1c76304cbd60b48cee302b66d1dde4468dc5b1160e4b7cb43778f2a/kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60", size = 97286 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/c4/06/7da99b04259b0f18b557a4effd1b9c901a747f7fdd84cf834ccf520cb0b2/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e", size = 121913 },
-    { url = "https://files.pythonhosted.org/packages/97/f5/b8a370d1aa593c17882af0a6f6755aaecd643640c0ed72dcfd2eafc388b9/kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6", size = 65627 },
-    { url = "https://files.pythonhosted.org/packages/2a/fc/6c0374f7503522539e2d4d1b497f5ebad3f8ed07ab51aed2af988dd0fb65/kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750", size = 63888 },
-    { url = "https://files.pythonhosted.org/packages/bf/3e/0b7172793d0f41cae5c923492da89a2ffcd1adf764c16159ca047463ebd3/kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d", size = 1369145 },
-    { url = "https://files.pythonhosted.org/packages/77/92/47d050d6f6aced2d634258123f2688fbfef8ded3c5baf2c79d94d91f1f58/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379", size = 1461448 },
-    { url = "https://files.pythonhosted.org/packages/9c/1b/8f80b18e20b3b294546a1adb41701e79ae21915f4175f311a90d042301cf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c", size = 1578750 },
-    { url = "https://files.pythonhosted.org/packages/a4/fe/fe8e72f3be0a844f257cadd72689c0848c6d5c51bc1d60429e2d14ad776e/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34", size = 1507175 },
-    { url = "https://files.pythonhosted.org/packages/39/fa/cdc0b6105d90eadc3bee525fecc9179e2b41e1ce0293caaf49cb631a6aaf/kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1", size = 1463963 },
-    { url = "https://files.pythonhosted.org/packages/6e/5c/0c03c4e542720c6177d4f408e56d1c8315899db72d46261a4e15b8b33a41/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f", size = 2248220 },
-    { url = "https://files.pythonhosted.org/packages/3d/ee/55ef86d5a574f4e767df7da3a3a7ff4954c996e12d4fbe9c408170cd7dcc/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b", size = 2404463 },
-    { url = "https://files.pythonhosted.org/packages/0f/6d/73ad36170b4bff4825dc588acf4f3e6319cb97cd1fb3eb04d9faa6b6f212/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27", size = 2352842 },
-    { url = "https://files.pythonhosted.org/packages/0b/16/fa531ff9199d3b6473bb4d0f47416cdb08d556c03b8bc1cccf04e756b56d/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a", size = 2501635 },
-    { url = "https://files.pythonhosted.org/packages/78/7e/aa9422e78419db0cbe75fb86d8e72b433818f2e62e2e394992d23d23a583/kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee", size = 2314556 },
-    { url = "https://files.pythonhosted.org/packages/a8/b2/15f7f556df0a6e5b3772a1e076a9d9f6c538ce5f05bd590eca8106508e06/kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07", size = 46364 },
-    { url = "https://files.pythonhosted.org/packages/0b/db/32e897e43a330eee8e4770bfd2737a9584b23e33587a0812b8e20aac38f7/kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76", size = 55887 },
-    { url = "https://files.pythonhosted.org/packages/c8/a4/df2bdca5270ca85fd25253049eb6708d4127be2ed0e5c2650217450b59e9/kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650", size = 48530 },
+version = "1.4.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/79/b3/e62464a652f4f8cd9006e13d07abad844a47df1e6537f73ddfbf1bc997ec/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1c8ceb754339793c24aee1c9fb2485b5b1f5bb1c2c214ff13368431e51fc9a09", size = 124156, upload-time = "2024-12-24T18:29:45.368Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/2d/f13d06998b546a2ad4f48607a146e045bbe48030774de29f90bdc573df15/kiwisolver-1.4.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a62808ac74b5e55a04a408cda6156f986cefbcf0ada13572696b507cc92fa1", size = 66555, upload-time = "2024-12-24T18:29:46.37Z" },
+    { url = "https://files.pythonhosted.org/packages/59/e3/b8bd14b0a54998a9fd1e8da591c60998dc003618cb19a3f94cb233ec1511/kiwisolver-1.4.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:68269e60ee4929893aad82666821aaacbd455284124817af45c11e50a4b42e3c", size = 65071, upload-time = "2024-12-24T18:29:47.333Z" },
+    { url = "https://files.pythonhosted.org/packages/f0/1c/6c86f6d85ffe4d0ce04228d976f00674f1df5dc893bf2dd4f1928748f187/kiwisolver-1.4.8-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34d142fba9c464bc3bbfeff15c96eab0e7310343d6aefb62a79d51421fcc5f1b", size = 1378053, upload-time = "2024-12-24T18:29:49.636Z" },
+    { url = "https://files.pythonhosted.org/packages/4e/b9/1c6e9f6dcb103ac5cf87cb695845f5fa71379021500153566d8a8a9fc291/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc373e0eef45b59197de815b1b28ef89ae3955e7722cc9710fb91cd77b7f47", size = 1472278, upload-time = "2024-12-24T18:29:51.164Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/81/aca1eb176de671f8bda479b11acdc42c132b61a2ac861c883907dde6debb/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:77e6f57a20b9bd4e1e2cedda4d0b986ebd0216236f0106e55c28aea3d3d69b16", size = 1478139, upload-time = "2024-12-24T18:29:52.594Z" },
+    { url = "https://files.pythonhosted.org/packages/49/f4/e081522473671c97b2687d380e9e4c26f748a86363ce5af48b4a28e48d06/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08e77738ed7538f036cd1170cbed942ef749137b1311fa2bbe2a7fda2f6bf3cc", size = 1413517, upload-time = "2024-12-24T18:29:53.941Z" },
+    { url = "https://files.pythonhosted.org/packages/8f/e9/6a7d025d8da8c4931522922cd706105aa32b3291d1add8c5427cdcd66e63/kiwisolver-1.4.8-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5ce1e481a74b44dd5e92ff03ea0cb371ae7a0268318e202be06c8f04f4f1246", size = 1474952, upload-time = "2024-12-24T18:29:56.523Z" },
+    { url = "https://files.pythonhosted.org/packages/82/13/13fa685ae167bee5d94b415991c4fc7bb0a1b6ebea6e753a87044b209678/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc2ace710ba7c1dfd1a3b42530b62b9ceed115f19a1656adefce7b1782a37794", size = 2269132, upload-time = "2024-12-24T18:29:57.989Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/92/bb7c9395489b99a6cb41d502d3686bac692586db2045adc19e45ee64ed23/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3452046c37c7692bd52b0e752b87954ef86ee2224e624ef7ce6cb21e8c41cc1b", size = 2425997, upload-time = "2024-12-24T18:29:59.393Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/12/87f0e9271e2b63d35d0d8524954145837dd1a6c15b62a2d8c1ebe0f182b4/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e9a60b50fe8b2ec6f448fe8d81b07e40141bfced7f896309df271a0b92f80f3", size = 2376060, upload-time = "2024-12-24T18:30:01.338Z" },
+    { url = "https://files.pythonhosted.org/packages/02/6e/c8af39288edbce8bf0fa35dee427b082758a4b71e9c91ef18fa667782138/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:918139571133f366e8362fa4a297aeba86c7816b7ecf0bc79168080e2bd79957", size = 2520471, upload-time = "2024-12-24T18:30:04.574Z" },
+    { url = "https://files.pythonhosted.org/packages/13/78/df381bc7b26e535c91469f77f16adcd073beb3e2dd25042efd064af82323/kiwisolver-1.4.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e063ef9f89885a1d68dd8b2e18f5ead48653176d10a0e324e3b0030e3a69adeb", size = 2338793, upload-time = "2024-12-24T18:30:06.25Z" },
+    { url = "https://files.pythonhosted.org/packages/d0/dc/c1abe38c37c071d0fc71c9a474fd0b9ede05d42f5a458d584619cfd2371a/kiwisolver-1.4.8-cp313-cp313-win_amd64.whl", hash = "sha256:a17b7c4f5b2c51bb68ed379defd608a03954a1845dfed7cc0117f1cc8a9b7fd2", size = 71855, upload-time = "2024-12-24T18:30:07.535Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/b6/21529d595b126ac298fdd90b705d87d4c5693de60023e0efcb4f387ed99e/kiwisolver-1.4.8-cp313-cp313-win_arm64.whl", hash = "sha256:3cd3bc628b25f74aedc6d374d5babf0166a92ff1317f46267f12d2ed54bc1d30", size = 65430, upload-time = "2024-12-24T18:30:08.504Z" },
+    { url = "https://files.pythonhosted.org/packages/34/bd/b89380b7298e3af9b39f49334e3e2a4af0e04819789f04b43d560516c0c8/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370fd2df41660ed4e26b8c9d6bbcad668fbe2560462cba151a721d49e5b6628c", size = 126294, upload-time = "2024-12-24T18:30:09.508Z" },
+    { url = "https://files.pythonhosted.org/packages/83/41/5857dc72e5e4148eaac5aa76e0703e594e4465f8ab7ec0fc60e3a9bb8fea/kiwisolver-1.4.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:84a2f830d42707de1d191b9490ac186bf7997a9495d4e9072210a1296345f7dc", size = 67736, upload-time = "2024-12-24T18:30:11.039Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/d1/be059b8db56ac270489fb0b3297fd1e53d195ba76e9bbb30e5401fa6b759/kiwisolver-1.4.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7a3ad337add5148cf51ce0b55642dc551c0b9d6248458a757f98796ca7348712", size = 66194, upload-time = "2024-12-24T18:30:14.886Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/83/4b73975f149819eb7dcf9299ed467eba068ecb16439a98990dcb12e63fdd/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7506488470f41169b86d8c9aeff587293f530a23a23a49d6bc64dab66bedc71e", size = 1465942, upload-time = "2024-12-24T18:30:18.927Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/2c/30a5cdde5102958e602c07466bce058b9d7cb48734aa7a4327261ac8e002/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f0121b07b356a22fb0414cec4666bbe36fd6d0d759db3d37228f496ed67c880", size = 1595341, upload-time = "2024-12-24T18:30:22.102Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/9b/1e71db1c000385aa069704f5990574b8244cce854ecd83119c19e83c9586/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6d6bd87df62c27d4185de7c511c6248040afae67028a8a22012b010bc7ad062", size = 1598455, upload-time = "2024-12-24T18:30:24.947Z" },
+    { url = "https://files.pythonhosted.org/packages/85/92/c8fec52ddf06231b31cbb779af77e99b8253cd96bd135250b9498144c78b/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:291331973c64bb9cce50bbe871fb2e675c4331dab4f31abe89f175ad7679a4d7", size = 1522138, upload-time = "2024-12-24T18:30:26.286Z" },
+    { url = "https://files.pythonhosted.org/packages/0b/51/9eb7e2cd07a15d8bdd976f6190c0164f92ce1904e5c0c79198c4972926b7/kiwisolver-1.4.8-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:893f5525bb92d3d735878ec00f781b2de998333659507d29ea4466208df37bed", size = 1582857, upload-time = "2024-12-24T18:30:28.86Z" },
+    { url = "https://files.pythonhosted.org/packages/0f/95/c5a00387a5405e68ba32cc64af65ce881a39b98d73cc394b24143bebc5b8/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b47a465040146981dc9db8647981b8cb96366fbc8d452b031e4f8fdffec3f26d", size = 2293129, upload-time = "2024-12-24T18:30:30.34Z" },
+    { url = "https://files.pythonhosted.org/packages/44/83/eeb7af7d706b8347548313fa3a3a15931f404533cc54fe01f39e830dd231/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:99cea8b9dd34ff80c521aef46a1dddb0dcc0283cf18bde6d756f1e6f31772165", size = 2421538, upload-time = "2024-12-24T18:30:33.334Z" },
+    { url = "https://files.pythonhosted.org/packages/05/f9/27e94c1b3eb29e6933b6986ffc5fa1177d2cd1f0c8efc5f02c91c9ac61de/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:151dffc4865e5fe6dafce5480fab84f950d14566c480c08a53c663a0020504b6", size = 2390661, upload-time = "2024-12-24T18:30:34.939Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/d4/3c9735faa36ac591a4afcc2980d2691000506050b7a7e80bcfe44048daa7/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:577facaa411c10421314598b50413aa1ebcf5126f704f1e5d72d7e4e9f020d90", size = 2546710, upload-time = "2024-12-24T18:30:37.281Z" },
+    { url = "https://files.pythonhosted.org/packages/4c/fa/be89a49c640930180657482a74970cdcf6f7072c8d2471e1babe17a222dc/kiwisolver-1.4.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:be4816dc51c8a471749d664161b434912eee82f2ea66bd7628bd14583a833e85", size = 2349213, upload-time = "2024-12-24T18:30:40.019Z" },
 ]
 
 [[package]]
 name = "lxml"
-version = "5.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e7/6b/20c3a4b24751377aaa6307eb230b66701024012c29dd374999cc92983269/lxml-5.3.0.tar.gz", hash = "sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f", size = 3679318 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/94/6a/42141e4d373903bfea6f8e94b2f554d05506dfda522ada5343c651410dc8/lxml-5.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a", size = 8156284 },
-    { url = "https://files.pythonhosted.org/packages/91/5e/fa097f0f7d8b3d113fb7312c6308af702f2667f22644441715be961f2c7e/lxml-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd", size = 4432407 },
-    { url = "https://files.pythonhosted.org/packages/2d/a1/b901988aa6d4ff937f2e5cfc114e4ec561901ff00660c3e56713642728da/lxml-5.3.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51", size = 5048331 },
-    { url = "https://files.pythonhosted.org/packages/30/0f/b2a54f48e52de578b71bbe2a2f8160672a8a5e103df3a78da53907e8c7ed/lxml-5.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b", size = 4744835 },
-    { url = "https://files.pythonhosted.org/packages/82/9d/b000c15538b60934589e83826ecbc437a1586488d7c13f8ee5ff1f79a9b8/lxml-5.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002", size = 5316649 },
-    { url = "https://files.pythonhosted.org/packages/e3/ee/ffbb9eaff5e541922611d2c56b175c45893d1c0b8b11e5a497708a6a3b3b/lxml-5.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4", size = 4812046 },
-    { url = "https://files.pythonhosted.org/packages/15/ff/7ff89d567485c7b943cdac316087f16b2399a8b997007ed352a1248397e5/lxml-5.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492", size = 4918597 },
-    { url = "https://files.pythonhosted.org/packages/c6/a3/535b6ed8c048412ff51268bdf4bf1cf052a37aa7e31d2e6518038a883b29/lxml-5.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3", size = 4738071 },
-    { url = "https://files.pythonhosted.org/packages/7a/8f/cbbfa59cb4d4fd677fe183725a76d8c956495d7a3c7f111ab8f5e13d2e83/lxml-5.3.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4", size = 5342213 },
-    { url = "https://files.pythonhosted.org/packages/5c/fb/db4c10dd9958d4b52e34d1d1f7c1f434422aeaf6ae2bbaaff2264351d944/lxml-5.3.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367", size = 4893749 },
-    { url = "https://files.pythonhosted.org/packages/f2/38/bb4581c143957c47740de18a3281a0cab7722390a77cc6e610e8ebf2d736/lxml-5.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832", size = 4945901 },
-    { url = "https://files.pythonhosted.org/packages/fc/d5/18b7de4960c731e98037bd48fa9f8e6e8f2558e6fbca4303d9b14d21ef3b/lxml-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff", size = 4815447 },
-    { url = "https://files.pythonhosted.org/packages/97/a8/cd51ceaad6eb849246559a8ef60ae55065a3df550fc5fcd27014361c1bab/lxml-5.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd", size = 5411186 },
-    { url = "https://files.pythonhosted.org/packages/89/c3/1e3dabab519481ed7b1fdcba21dcfb8832f57000733ef0e71cf6d09a5e03/lxml-5.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb", size = 5324481 },
-    { url = "https://files.pythonhosted.org/packages/b6/17/71e9984cf0570cd202ac0a1c9ed5c1b8889b0fc8dc736f5ef0ffb181c284/lxml-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b", size = 5011053 },
-    { url = "https://files.pythonhosted.org/packages/69/68/9f7e6d3312a91e30829368c2b3217e750adef12a6f8eb10498249f4e8d72/lxml-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957", size = 3485634 },
-    { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
+version = "5.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" },
+    { url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" },
+    { url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" },
+    { url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" },
+    { url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" },
+    { url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" },
+    { url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" },
+    { url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" },
+    { url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" },
+    { url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" },
+    { url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" },
+    { url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" },
+    { url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" },
 ]
 
 [[package]]
@@ -386,42 +440,42 @@ source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "mdurl" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
+sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
+    { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
 ]
 
 [[package]]
 name = "markupsafe"
 version = "3.0.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
-    { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
-    { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
-    { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
-    { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
-    { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
-    { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
-    { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
-    { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
-    { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
-    { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
-    { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
-    { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
-    { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
-    { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
-    { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
-    { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
-    { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
-    { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
-    { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
+sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" },
+    { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" },
+    { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" },
+    { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" },
+    { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" },
+    { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" },
+    { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" },
+    { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" },
+    { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" },
+    { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" },
+    { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" },
+    { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" },
+    { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" },
+    { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" },
+    { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
 ]
 
 [[package]]
 name = "matplotlib"
-version = "3.9.3"
+version = "3.10.3"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "contourpy" },
@@ -434,20 +488,20 @@ dependencies = [
     { name = "pyparsing" },
     { name = "python-dateutil" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/75/9f/562ed484b11ac9f4bb4f9d2d7546954ec106a8c0f06cc755d6f63e519274/matplotlib-3.9.3.tar.gz", hash = "sha256:cd5dbbc8e25cad5f706845c4d100e2c8b34691b412b93717ce38d8ae803bcfa5", size = 36113438 }
+sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/60/04/949640040982822416c471d9ebe4e9e6c69ca9f9bb6ba82ed30808863c02/matplotlib-3.9.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:203d18df84f5288973b2d56de63d4678cc748250026ca9e1ad8f8a0fd8a75d83", size = 7883417 },
-    { url = "https://files.pythonhosted.org/packages/9f/90/ebd37143cd3150b6c650ee1580024df3dd649d176e68d346f826b8d24e37/matplotlib-3.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b651b0d3642991259109dc0351fc33ad44c624801367bb8307be9bfc35e427ad", size = 7768720 },
-    { url = "https://files.pythonhosted.org/packages/dc/84/6591e6b55d755d16dacdc113205067031867c1f5e3c08b32c01aad831420/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66d7b171fecf96940ce069923a08ba3df33ef542de82c2ff4fe8caa8346fa95a", size = 8192723 },
-    { url = "https://files.pythonhosted.org/packages/29/09/146a17d37e32313507f11ac984e65311f2d5805d731eb981d4f70eb928dc/matplotlib-3.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be0ba61f6ff2e6b68e4270fb63b6813c9e7dec3d15fc3a93f47480444fd72f0", size = 8305801 },
-    { url = "https://files.pythonhosted.org/packages/85/cb/d2690572c08f19ca7c0f44b1fb4d11c121d63467a57b508cc3656ff80b43/matplotlib-3.9.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d6b2e8856dec3a6db1ae51aec85c82223e834b228c1d3228aede87eee2b34f9", size = 9086564 },
-    { url = "https://files.pythonhosted.org/packages/28/dd/0a5176027c1cb94fe75f69f76cb274180c8abf740df6fc0e6a1e4cbaec3f/matplotlib-3.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:90a85a004fefed9e583597478420bf904bb1a065b0b0ee5b9d8d31b04b0f3f70", size = 7833257 },
-    { url = "https://files.pythonhosted.org/packages/42/d4/e477d50a8e4b437c2afbb5c665cb8e5d79b06abe6fe3c6915d6f7f0c2ef2/matplotlib-3.9.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3119b2f16de7f7b9212ba76d8fe6a0e9f90b27a1e04683cd89833a991682f639", size = 7911906 },
-    { url = "https://files.pythonhosted.org/packages/ae/a1/ba5ab89666c42ace8e31b4ff5a2c76a17e4d6f91aefce476b064c56ff61d/matplotlib-3.9.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87ad73763d93add1b6c1f9fcd33af662fd62ed70e620c52fcb79f3ac427cf3a6", size = 7801336 },
-    { url = "https://files.pythonhosted.org/packages/77/59/4dcdb3a6695af6c698a95aec13016a550ef2f85144d22f61f81d1e064148/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026bdf3137ab6022c866efa4813b6bbeddc2ed4c9e7e02f0e323a7bca380dfa0", size = 8218178 },
-    { url = "https://files.pythonhosted.org/packages/4f/27/7c72db0d0ee35d9237572565ffa3c0eb25fc46a3f47e0f16412a587bc9d8/matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760a5e89ebbb172989e8273024a1024b0f084510b9105261b3b00c15e9c9f006", size = 8327768 },
-    { url = "https://files.pythonhosted.org/packages/de/ad/213eee624feadba7b77e881c9d2c04c1e036efe69d19031e3fa927fdb5dc/matplotlib-3.9.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a42b9dc42de2cfe357efa27d9c50c7833fc5ab9b2eb7252ccd5d5f836a84e1e4", size = 9094075 },
-    { url = "https://files.pythonhosted.org/packages/19/1b/cb8e99a5fe2e2b14e3b8234cb1649a675be63f74a5224a648ae4ab61f60c/matplotlib-3.9.3-cp313-cp313t-win_amd64.whl", hash = "sha256:e0fcb7da73fbf67b5f4bdaa57d85bb585a4e913d4a10f3e15b32baea56a67f0a", size = 7888937 },
+    { url = "https://files.pythonhosted.org/packages/3b/c1/23cfb566a74c696a3b338d8955c549900d18fe2b898b6e94d682ca21e7c2/matplotlib-3.10.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9f2efccc8dcf2b86fc4ee849eea5dcaecedd0773b30f47980dc0cbeabf26ec84", size = 8180318, upload-time = "2025-05-08T19:10:20.426Z" },
+    { url = "https://files.pythonhosted.org/packages/6c/0c/02f1c3b66b30da9ee343c343acbb6251bef5b01d34fad732446eaadcd108/matplotlib-3.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ddbba06a6c126e3301c3d272a99dcbe7f6c24c14024e80307ff03791a5f294e", size = 8051132, upload-time = "2025-05-08T19:10:22.569Z" },
+    { url = "https://files.pythonhosted.org/packages/b4/ab/8db1a5ac9b3a7352fb914133001dae889f9fcecb3146541be46bed41339c/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748302b33ae9326995b238f606e9ed840bf5886ebafcb233775d946aa8107a15", size = 8457633, upload-time = "2025-05-08T19:10:24.749Z" },
+    { url = "https://files.pythonhosted.org/packages/f5/64/41c4367bcaecbc03ef0d2a3ecee58a7065d0a36ae1aa817fe573a2da66d4/matplotlib-3.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a80fcccbef63302c0efd78042ea3c2436104c5b1a4d3ae20f864593696364ac7", size = 8601031, upload-time = "2025-05-08T19:10:27.03Z" },
+    { url = "https://files.pythonhosted.org/packages/12/6f/6cc79e9e5ab89d13ed64da28898e40fe5b105a9ab9c98f83abd24e46d7d7/matplotlib-3.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55e46cbfe1f8586adb34f7587c3e4f7dedc59d5226719faf6cb54fc24f2fd52d", size = 9406988, upload-time = "2025-05-08T19:10:29.056Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/0f/eed564407bd4d935ffabf561ed31099ed609e19287409a27b6d336848653/matplotlib-3.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:151d89cb8d33cb23345cd12490c76fd5d18a56581a16d950b48c6ff19bb2ab93", size = 8068034, upload-time = "2025-05-08T19:10:31.221Z" },
+    { url = "https://files.pythonhosted.org/packages/3e/e5/2f14791ff69b12b09e9975e1d116d9578ac684460860ce542c2588cb7a1c/matplotlib-3.10.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c26dd9834e74d164d06433dc7be5d75a1e9890b926b3e57e74fa446e1a62c3e2", size = 8218223, upload-time = "2025-05-08T19:10:33.114Z" },
+    { url = "https://files.pythonhosted.org/packages/5c/08/30a94afd828b6e02d0a52cae4a29d6e9ccfcf4c8b56cc28b021d3588873e/matplotlib-3.10.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:24853dad5b8c84c8c2390fc31ce4858b6df504156893292ce8092d190ef8151d", size = 8094985, upload-time = "2025-05-08T19:10:35.337Z" },
+    { url = "https://files.pythonhosted.org/packages/89/44/f3bc6b53066c889d7a1a3ea8094c13af6a667c5ca6220ec60ecceec2dabe/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68f7878214d369d7d4215e2a9075fef743be38fa401d32e6020bab2dfabaa566", size = 8483109, upload-time = "2025-05-08T19:10:37.611Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/c7/473bc559beec08ebee9f86ca77a844b65747e1a6c2691e8c92e40b9f42a8/matplotlib-3.10.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6929fc618cb6db9cb75086f73b3219bbb25920cb24cee2ea7a12b04971a4158", size = 8618082, upload-time = "2025-05-08T19:10:39.892Z" },
+    { url = "https://files.pythonhosted.org/packages/d8/e9/6ce8edd264c8819e37bbed8172e0ccdc7107fe86999b76ab5752276357a4/matplotlib-3.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c7818292a5cc372a2dc4c795e5c356942eb8350b98ef913f7fda51fe175ac5d", size = 9413699, upload-time = "2025-05-08T19:10:42.376Z" },
+    { url = "https://files.pythonhosted.org/packages/1b/92/9a45c91089c3cf690b5badd4be81e392ff086ccca8a1d4e3a08463d8a966/matplotlib-3.10.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4f23ffe95c5667ef8a2b56eea9b53db7f43910fa4a2d5472ae0f72b64deab4d5", size = 8139044, upload-time = "2025-05-08T19:10:44.551Z" },
 ]
 
 [[package]]
@@ -457,47 +511,50 @@ source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "markdown-it-py" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 }
+sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542, upload-time = "2024-09-09T20:27:49.564Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 },
+    { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316, upload-time = "2024-09-09T20:27:48.397Z" },
 ]
 
 [[package]]
 name = "mdurl"
 version = "0.1.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
+    { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
 ]
 
 [[package]]
 name = "ml-dtypes"
-version = "0.5.0"
+version = "0.5.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "numpy", marker = "python_full_version >= '3.13'" },
+    { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/79/717c5e22ad25d63ce3acdfe8ff8d64bdedec18914256c59b838218708b16/ml_dtypes-0.5.0.tar.gz", hash = "sha256:3e7d3a380fe73a63c884f06136f8baa7a5249cc8e9fdec677997dd78549f8128", size = 699367 }
+sdist = { url = "https://files.pythonhosted.org/packages/32/49/6e67c334872d2c114df3020e579f3718c333198f8312290e09ec0216703a/ml_dtypes-0.5.1.tar.gz", hash = "sha256:ac5b58559bb84a95848ed6984eb8013249f90b6bab62aa5acbad876e256002c9", size = 698772, upload-time = "2025-01-07T03:34:55.613Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/b3/4a/18f670a2703e771a6775fbc354208e597ff062a88efb0cecc220a282210b/ml_dtypes-0.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f", size = 753345 },
-    { url = "https://files.pythonhosted.org/packages/ed/c6/358d85e274e22d53def0c85f3cbe0933475fa3cf6922e9dca66eb25cb22f/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599", size = 4424962 },
-    { url = "https://files.pythonhosted.org/packages/4c/b4/d766586e24e7a073333c8eb8bd9275f3c6fe0569b509ae7b1699d4f00c74/ml_dtypes-0.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab", size = 4475201 },
-    { url = "https://files.pythonhosted.org/packages/14/87/30323ad2e52f56262019a4493fe5f5e71067c5561ce7e2f9c75de520f5e8/ml_dtypes-0.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07", size = 213195 },
+    { url = "https://files.pythonhosted.org/packages/0f/92/bb6a3d18e16fddd18ce6d5f480e1919b33338c70e18cba831c6ae59812ee/ml_dtypes-0.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:12651420130ee7cc13059fc56dac6ad300c3af3848b802d475148c9defd27c23", size = 667696, upload-time = "2025-01-07T03:34:27.526Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/29/cfc89d842767e9a51146043b0fa18332c2b38f8831447e6cb1160e3c6102/ml_dtypes-0.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9945669d3dadf8acb40ec2e57d38c985d8c285ea73af57fc5b09872c516106d", size = 4638365, upload-time = "2025-01-07T03:34:30.43Z" },
+    { url = "https://files.pythonhosted.org/packages/be/26/adc36e3ea09603d9f6d114894e1c1b7b8e8a9ef6d0b031cc270c6624a37c/ml_dtypes-0.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf9975bda82a99dc935f2ae4c83846d86df8fd6ba179614acac8e686910851da", size = 4702722, upload-time = "2025-01-07T03:34:33.813Z" },
+    { url = "https://files.pythonhosted.org/packages/da/8a/a2b9375c94077e5a488a624a195621407846f504068ce22ccf805c674156/ml_dtypes-0.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:fd918d4e6a4e0c110e2e05be7a7814d10dc1b95872accbf6512b80a109b71ae1", size = 210850, upload-time = "2025-01-07T03:34:36.897Z" },
+    { url = "https://files.pythonhosted.org/packages/52/38/703169100fdde27957f061d4d0ea3e00525775a09acaccf7e655d9609d55/ml_dtypes-0.5.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:05f23447a1c20ddf4dc7c2c661aa9ed93fcb2658f1017c204d1e758714dc28a8", size = 693043, upload-time = "2025-01-07T03:34:38.457Z" },
+    { url = "https://files.pythonhosted.org/packages/28/ff/4e234c9c23e0d456f5da5a326c103bf890c746d93351524d987e41f438b3/ml_dtypes-0.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b7fbe5571fdf28fd3aaab3ef4aafc847de9ebf263be959958c1ca58ec8eadf5", size = 4903946, upload-time = "2025-01-07T03:34:40.236Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/45/c1a1ccfdd02bc4173ca0f4a2d327683a27df85797b885eb1da1ca325b85c/ml_dtypes-0.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d13755f8e8445b3870114e5b6240facaa7cb0c3361e54beba3e07fa912a6e12b", size = 5052731, upload-time = "2025-01-07T03:34:45.308Z" },
 ]
 
 [[package]]
 name = "mpmath"
 version = "1.3.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
+    { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" },
 ]
 
 [[package]]
 name = "myst-parser"
-version = "4.0.0"
+version = "4.0.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "docutils" },
@@ -507,130 +564,130 @@ dependencies = [
     { name = "pyyaml" },
     { name = "sphinx" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/85/55/6d1741a1780e5e65038b74bce6689da15f620261c490c3511eb4c12bac4b/myst_parser-4.0.0.tar.gz", hash = "sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531", size = 93858 }
+sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985, upload-time = "2025-02-12T10:53:03.833Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ca/b4/b036f8fdb667587bb37df29dc6644681dd78b7a2a6321a34684b79412b28/myst_parser-4.0.0-py3-none-any.whl", hash = "sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d", size = 84563 },
+    { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" },
 ]
 
 [[package]]
 name = "namex"
-version = "0.0.8"
+version = "0.0.9"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/9d/48/d275cdb6216c6bb4f9351675795a0b48974e138f16b1ffe0252c1f8faa28/namex-0.0.8.tar.gz", hash = "sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b", size = 6623 }
+sdist = { url = "https://files.pythonhosted.org/packages/ca/03/00afe7f32962d0778d757aed89cd7f970179e5f7fbe42e883fd494ebbbc7/namex-0.0.9.tar.gz", hash = "sha256:8adfea9da5cea5be8f4e632349b4669e30172c7859e1fd97459fdf3b17469253", size = 6569, upload-time = "2025-04-22T18:00:15.808Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/73/59/7854fbfb59f8ae35483ce93493708be5942ebb6328cd85b3a609df629736/namex-0.0.8-py3-none-any.whl", hash = "sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487", size = 5806 },
+    { url = "https://files.pythonhosted.org/packages/46/16/7f616b55a147d9d4e9eb910a7eacde96cfa7ce3109d4c57ac32b91348ef3/namex-0.0.9-py3-none-any.whl", hash = "sha256:7bd4e4a2cc3876592111609fdf4cbe6ff19883adbe6b3b40d842fd340f77025e", size = 5847, upload-time = "2025-04-22T18:00:14.558Z" },
 ]
 
 [[package]]
 name = "natsort"
 version = "8.4.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575 }
+sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268 },
+    { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" },
 ]
 
 [[package]]
 name = "numpy"
-version = "2.1.3"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 },
-    { url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 },
-    { url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 },
-    { url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 },
-    { url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 },
-    { url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 },
-    { url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 },
-    { url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 },
-    { url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 },
-    { url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 },
-    { url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 },
-    { url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 },
-    { url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 },
-    { url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 },
-    { url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 },
-    { url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 },
-    { url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 },
-    { url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 },
-    { url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 },
-    { url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 },
+version = "2.2.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920, upload-time = "2025-04-19T23:27:42.561Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102, upload-time = "2025-04-19T22:41:16.234Z" },
+    { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709, upload-time = "2025-04-19T22:41:38.472Z" },
+    { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173, upload-time = "2025-04-19T22:41:47.823Z" },
+    { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502, upload-time = "2025-04-19T22:41:58.689Z" },
+    { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417, upload-time = "2025-04-19T22:42:19.897Z" },
+    { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807, upload-time = "2025-04-19T22:42:44.433Z" },
+    { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611, upload-time = "2025-04-19T22:43:09.928Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747, upload-time = "2025-04-19T22:43:36.983Z" },
+    { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594, upload-time = "2025-04-19T22:47:10.523Z" },
+    { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356, upload-time = "2025-04-19T22:47:30.253Z" },
+    { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778, upload-time = "2025-04-19T22:44:09.251Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279, upload-time = "2025-04-19T22:44:31.383Z" },
+    { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247, upload-time = "2025-04-19T22:44:40.361Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087, upload-time = "2025-04-19T22:44:51.188Z" },
+    { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964, upload-time = "2025-04-19T22:45:12.451Z" },
+    { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214, upload-time = "2025-04-19T22:45:37.734Z" },
+    { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788, upload-time = "2025-04-19T22:46:01.908Z" },
+    { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672, upload-time = "2025-04-19T22:46:28.585Z" },
+    { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102, upload-time = "2025-04-19T22:46:39.949Z" },
+    { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096, upload-time = "2025-04-19T22:47:00.147Z" },
 ]
 
 [[package]]
 name = "nvidia-nccl-cu12"
-version = "2.23.4"
+version = "2.26.5"
 source = { registry = "https://pypi.org/simple" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/c8/3a/0112397396dec37ffc8edd7836d48261b4d14ca60ec8ed7bc857cce1d916/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:aa946c8327e22ced28e7cef508a334673abc42064ec85f02d005ba1785ea4cec", size = 198953892 },
-    { url = "https://files.pythonhosted.org/packages/ed/1f/6482380ec8dcec4894e7503490fc536d846b0d59694acad9cf99f27d0e7d/nvidia_nccl_cu12-2.23.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:b097258d9aab2fa9f686e33c6fe40ae57b27df60cedbd15d139701bb5509e0c1", size = 198954603 },
+    { url = "https://files.pythonhosted.org/packages/55/66/ed9d28946ead0fe1322df2f4fc6ea042340c0fe73b79a1419dc1fdbdd211/nvidia_nccl_cu12-2.26.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adb1bf4adcc5a47f597738a0700da6aef61f8ea4251b375540ae138c7d239588", size = 318058262, upload-time = "2025-05-02T23:32:43.197Z" },
+    { url = "https://files.pythonhosted.org/packages/48/fb/ec4ac065d9b0d56f72eaf1d9b0df601e33da28197b32ca351dc05b342611/nvidia_nccl_cu12-2.26.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea5ed3e053c735f16809bee7111deac62ac35b10128a8c102960a0462ce16cbe", size = 318069637, upload-time = "2025-05-02T23:33:18.306Z" },
 ]
 
 [[package]]
 name = "oauthlib"
 version = "3.2.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 }
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352, upload-time = "2022-10-17T20:04:27.471Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 },
+    { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688, upload-time = "2022-10-17T20:04:24.037Z" },
 ]
 
 [[package]]
 name = "opencv-python"
-version = "4.10.0.84"
+version = "4.11.0.86"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "numpy", marker = "python_full_version >= '3.13'" },
+    { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 }
+sdist = { url = "https://files.pythonhosted.org/packages/17/06/68c27a523103dad5837dc5b87e71285280c4f098c60e4fe8a8db6486ab09/opencv-python-4.11.0.86.tar.gz", hash = "sha256:03d60ccae62304860d232272e4a4fda93c39d595780cb40b161b310244b736a4", size = 95171956, upload-time = "2025-01-16T13:52:24.737Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 },
-    { url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 },
-    { url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 },
-    { url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 },
-    { url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 },
-    { url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 },
+    { url = "https://files.pythonhosted.org/packages/05/4d/53b30a2a3ac1f75f65a59eb29cf2ee7207ce64867db47036ad61743d5a23/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:432f67c223f1dc2824f5e73cdfcd9db0efc8710647d4e813012195dc9122a52a", size = 37326322, upload-time = "2025-01-16T13:52:25.887Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/84/0a67490741867eacdfa37bc18df96e08a9d579583b419010d7f3da8ff503/opencv_python-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:9d05ef13d23fe97f575153558653e2d6e87103995d54e6a35db3f282fe1f9c66", size = 56723197, upload-time = "2025-01-16T13:55:21.222Z" },
+    { url = "https://files.pythonhosted.org/packages/f3/bd/29c126788da65c1fb2b5fb621b7fed0ed5f9122aa22a0868c5e2c15c6d23/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b92ae2c8852208817e6776ba1ea0d6b1e0a1b5431e971a2a0ddd2a8cc398202", size = 42230439, upload-time = "2025-01-16T13:51:35.822Z" },
+    { url = "https://files.pythonhosted.org/packages/2c/8b/90eb44a40476fa0e71e05a0283947cfd74a5d36121a11d926ad6f3193cc4/opencv_python-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b02611523803495003bd87362db3e1d2a0454a6a63025dc6658a9830570aa0d", size = 62986597, upload-time = "2025-01-16T13:52:08.836Z" },
+    { url = "https://files.pythonhosted.org/packages/fb/d7/1d5941a9dde095468b288d989ff6539dd69cd429dbf1b9e839013d21b6f0/opencv_python-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:810549cb2a4aedaa84ad9a1c92fbfdfc14090e2749cedf2c1589ad8359aa169b", size = 29384337, upload-time = "2025-01-16T13:52:13.549Z" },
+    { url = "https://files.pythonhosted.org/packages/a4/7d/f1c30a92854540bf789e9cd5dde7ef49bbe63f855b85a2e6b3db8135c591/opencv_python-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:085ad9b77c18853ea66283e98affefe2de8cc4c1f43eda4c100cf9b2721142ec", size = 39488044, upload-time = "2025-01-16T13:52:21.928Z" },
 ]
 
 [[package]]
 name = "optree"
-version = "0.13.1"
+version = "0.15.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "typing-extensions" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/f7/f2/56afdaeaae36b076659be7db8e72be0924dd64ebd1c131675c77f7e704a6/optree-0.13.1.tar.gz", hash = "sha256:af67856aa8073d237fe67313d84f8aeafac32c1cef7239c628a2768d02679c43", size = 155738 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/3f/53/f3727cad24f16a06666f328f1212476988cadac9b9e7919ddfb2c22eb662/optree-0.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f788b2ad120deb73b4908a74473cd6de79cfb9f33bbe9dcb59cea2e2477d4e28", size = 608270 },
-    { url = "https://files.pythonhosted.org/packages/64/f2/68beb9da2dd52baa50e7a589ed2bd8434fdd70cdba06754aa5910263da06/optree-0.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2909cb42add6bb1a5a2b0243bdd8c4b861bf072f3741e26239481907ac8ad4e6", size = 325703 },
-    { url = "https://files.pythonhosted.org/packages/45/db/08921e56f3425bf649eb593eb28775263c935d029985d35572dc5690cc1a/optree-0.13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc5fa2ff5090389f3a906567446f01d692bd6fe5cfcc5ae2d5861f24e8e0e4d", size = 355813 },
-    { url = "https://files.pythonhosted.org/packages/e5/e3/587e0d28dc2cee064902adfebca97db124e12b275dbe9c2b05a70a22345f/optree-0.13.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4711f5cac5a2a49c3d6c9f0eca7b77c22b452170bb33ea01c3214ebb17931db9", size = 402566 },
-    { url = "https://files.pythonhosted.org/packages/8a/1d/0d5bbab8c99580b732b89ef2c5fcdd6ef410478295949fdf2984fa1bfc28/optree-0.13.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c4ab1d391b89cb88eb3c63383d5eb0930bc21141de9d5acd277feed9e38eb65", size = 397005 },
-    { url = "https://files.pythonhosted.org/packages/16/fa/fc2a8183e14f0d195d25824bf65095ff32b34bd469614a6c30d0a596a30f/optree-0.13.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5e5f09c85ae558a6bdaea57e63168082e728e777391393e9e2792f0d15b7b59", size = 369400 },
-    { url = "https://files.pythonhosted.org/packages/9f/42/8c08ce4ebb3d9a6e4415f1a97830c84879e2d1a43710a7c8a18b2c3e169d/optree-0.13.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c8ee1e988c634a451146b87d9ebdbf650a75dc1f52a9cffcd89fabb7289321c", size = 390179 },
-    { url = "https://files.pythonhosted.org/packages/06/02/3a701d6307fdfefe4fcecbac644803e2a4314ab2406ff465e03129cc85f6/optree-0.13.1-cp313-cp313-win32.whl", hash = "sha256:5b6531cd4eb23fadbbf77faf834e1119da06d7af3154f55786b59953cd87bb8a", size = 264264 },
-    { url = "https://files.pythonhosted.org/packages/ef/f9/8a1421181c5eb0c0f81d1423a900baeb3faba68a48747bbdffb7581239ac/optree-0.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:27d81dc43b522ba47ba7d2e7d91dbb486940348b1bf85caeb0afc2815c0aa492", size = 293682 },
-    { url = "https://files.pythonhosted.org/packages/80/34/d1b1849a6240385c4a3af5da9425b11912204d0b1cf142d802815319b73a/optree-0.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:f39c7174a3f3cdc3f5fe6fb4b832f608c40ac174d7567ed6734b2ee952094631", size = 293670 },
-    { url = "https://files.pythonhosted.org/packages/0d/d6/f81e6748bcc3f35a2f570a814014e3418b0ed425d7cbc2b42d88d12863d5/optree-0.13.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3010ae24e994f6e00071098d34e98e78eb995b7454a2ef629a0bf7df17441b24", size = 702861 },
-    { url = "https://files.pythonhosted.org/packages/08/7f/70a2d02110ccb245bc57bd9ad57668acfea0ff364c27d7dfe1735ede79ed/optree-0.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5b5626c38d4a18a144063db5c1dbb558431d83ca10682324f74665a12214801f", size = 370740 },
-    { url = "https://files.pythonhosted.org/packages/63/37/4ddf05267467809236203e2007e9443519c4d55e0744ce7eea1aa74dffee/optree-0.13.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1935639dd498a42367633e3877797e1330e39d44d48bbca1a136bb4dbe4c1bc9", size = 374695 },
-    { url = "https://files.pythonhosted.org/packages/19/f2/51a63a799f6dce31813d7e02a7547394aebcb39f407e62038ecbd999d490/optree-0.13.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01819c3df950696f32c91faf8d376ae6b695ffdba18f330f1cab6b8e314e4612", size = 418671 },
-    { url = "https://files.pythonhosted.org/packages/f0/7c/a08191e0c9202f2be9c415057eea3cf3a5af18e9a6d81f4c7b0e6faf0a1f/optree-0.13.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48c29d9c6c64c8dc48c8ee97f7c1d5cdb83e37320f0be0857c06ce4b97994aea", size = 414966 },
-    { url = "https://files.pythonhosted.org/packages/8f/37/7bf815f4da7234e387863228b17246b42b8c02553882581a4013a64a88d0/optree-0.13.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:025d23400b8b579462a251420f0a9ae77d3d3593f84276f3465985731d79d722", size = 389219 },
-    { url = "https://files.pythonhosted.org/packages/3d/84/bb521a66d3a84fe2f1500ef67d245c2cc1a26277fcaaf4bc70b22c06e99b/optree-0.13.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55e82426bef151149cfa41d68ac957730fcd420996c0db8324fca81aa6a810ba", size = 405377 },
-    { url = "https://files.pythonhosted.org/packages/06/99/3eb53829c4c0b6dc20115d957d2d8e945630ddf40c656dc4e39c5a6e51f2/optree-0.13.1-cp313-cp313t-win32.whl", hash = "sha256:e40f018f522fcfd244688d1b3a360518e636ba7f636385aae0566eae3e7d29bc", size = 292734 },
-    { url = "https://files.pythonhosted.org/packages/2f/59/d7601959ad0b90d309794c0975a256304488b4c5671f24e3e12101ade7ef/optree-0.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d580f1bf23bb352c4db6b3544f282f1ac08dcb0d9ab537d25e56220353438cf7", size = 331457 },
-    { url = "https://files.pythonhosted.org/packages/8b/36/c01a5bc34660d46c6a3b1fe090bbdc8c76af7b5c1a6613cc671aa6df8349/optree-0.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:c4d13f55dbd509d27be3af54d53b4ca0751bc518244ced6d0567e518e51452a2", size = 331470 },
+sdist = { url = "https://files.pythonhosted.org/packages/4c/10/37411ac8cf8cb07b9db9ddc3e36f84869fd1cabcee3d6af8d347c28744f2/optree-0.15.0.tar.gz", hash = "sha256:d00a45e3b192093ef2cd32bf0d541ecbfc93c1bd73a5f3fe36293499f28a50cf", size = 171403, upload-time = "2025-04-06T16:13:18.435Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/70/e3/99135565340ac34857b6edbb3df6e15eb35aa7160f900f12d83f71d38eeb/optree-0.15.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4ab606720ae319cb43da47c71d7d5fa7cfbb6a02e6da4857331e6f93800c970e", size = 647666, upload-time = "2025-04-06T16:12:14.072Z" },
+    { url = "https://files.pythonhosted.org/packages/93/fc/c9c04494d2ab54f98f8d8c18cb5095a81ad23fb64105cb05e926bb3d1a0c/optree-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cfc5771115f85b0bfa8f72cce1599186fd6a0ea71c8154d8b2751d9170be428", size = 346205, upload-time = "2025-04-06T16:12:15.91Z" },
+    { url = "https://files.pythonhosted.org/packages/b8/c8/25484ec6784435d63e64e87a7dca32760eed4ca60ddd4be1ca14ed421e08/optree-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f958a20a311854aaab8bdd0f124aab5b9848f07976b54da3e95526a491aa860", size = 372427, upload-time = "2025-04-06T16:12:17.064Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/27/615a2987137fd8add27e62217527e49f7fd2ec391bbec354dbc59a0cd0af/optree-0.15.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47ce7e9d81eaed5a05004df1fa279d2608e063dd5eb236e9c95803b4fa0a286c", size = 421611, upload-time = "2025-04-06T16:12:18.219Z" },
+    { url = "https://files.pythonhosted.org/packages/98/e7/0106ee7ebec2e4cfa09f0a3b857e03c80bc80521227f4d64c289ac9601e8/optree-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6d6ab3717d48e0e747d9e348e23be1fa0f8a812f73632face6303c438d259ba", size = 415804, upload-time = "2025-04-06T16:12:19.611Z" },
+    { url = "https://files.pythonhosted.org/packages/47/0e/a40ccedb0bac4a894b2b0d17d915b7e8cd3fdc44a5104026cec4102a53fb/optree-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c7d101a15be39a9c7c4afae9f0bb85f682eb7d719117e2f9e5fb39c9f6f2c92", size = 386208, upload-time = "2025-04-06T16:12:20.784Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/f6/1d98163b283048d80cb0c8e67d086a1e0ecabe35004d7180d0f28303f611/optree-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aae337ab30b45a096eb5b4ffc3ad8909731617543a7eb288e0b297b9d10a241f", size = 409276, upload-time = "2025-04-06T16:12:22.067Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/65/4275cd763bafb22c41443b7c7bfd5986b9c97f984537669c356e2c7008a1/optree-0.15.0-cp313-cp313-win32.whl", hash = "sha256:eb9c51d728485f5908111191b5403a3f9bc310d121a981f29fad45750b9ff89c", size = 277924, upload-time = "2025-04-06T16:12:23.638Z" },
+    { url = "https://files.pythonhosted.org/packages/b2/fe/3d9f5463609ac6cb5857d0040739ae7709e1fbc134c58f0dd7cb3d2d93d5/optree-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:7f00e6f011f021ae470efe070ec4d2339fb1a8cd0dcdd16fe3dab782a47aba45", size = 309617, upload-time = "2025-04-06T16:12:24.766Z" },
+    { url = "https://files.pythonhosted.org/packages/7d/bb/b7a443be665a91307cd78196018bc45bb0c6ab04fe265f009698311c7bc7/optree-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:17990fbc7f4c461de7ae546fc5661f6a248c3dcee966c89c2e2e5ad7f6228bae", size = 303390, upload-time = "2025-04-06T16:12:25.798Z" },
+    { url = "https://files.pythonhosted.org/packages/30/8d/c6977786cc40626f24068d9bd6acbff377b9bed0cd64b7faff1bc3f2d60c/optree-0.15.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b31c88af70e3f5c14ff2aacd38c4076e6cde98f75169fe0bb59543f01bfb9719", size = 747468, upload-time = "2025-04-06T16:12:27.472Z" },
+    { url = "https://files.pythonhosted.org/packages/5c/3f/e5b50c7af3712a97d0341744cd8ebd8ab19cd38561e26caa7a8a10fdf702/optree-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bc440f81f738d9c822030c3b4f53b6dec9ceb52410f02fd06b9338dc25a8447f", size = 392638, upload-time = "2025-04-06T16:12:28.535Z" },
+    { url = "https://files.pythonhosted.org/packages/54/3b/1aab1a9dba5c8eb85810ba2683b42b057b1ce1de9bb7ca08c5d35448979e/optree-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ffc2dd8c754e95495163dde55b38dc37e6712b6a3bc7f2190b0547a2c403bb", size = 390030, upload-time = "2025-04-06T16:12:29.687Z" },
+    { url = "https://files.pythonhosted.org/packages/fc/95/195be32055ce48cdf8beb31eda74f4643c6a443f1367346c6a8f6606c9eb/optree-0.15.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9fa9fb0197cd7b5f2b1fa7e05d30946b3b79bcfc3608fe54dbfc67969895cac9", size = 437350, upload-time = "2025-04-06T16:12:30.84Z" },
+    { url = "https://files.pythonhosted.org/packages/02/01/9eefcdd9112aebc52db668a87f1df2f45c784e34e5a35182d3dad731fc92/optree-0.15.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6828639b01ba1177c04875dd9529d938d7b28122c97e7ae14ec41c68ec22826c", size = 434500, upload-time = "2025-04-06T16:12:33.168Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/b3/0749c3a752b4921d54aa88eb1246315a008b5199c6d0e6127bd672a21c87/optree-0.15.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:93c74eed0f52818c30212dba4867f5672e498567bad49dcdffbe8db6703a0d65", size = 404306, upload-time = "2025-04-06T16:12:34.258Z" },
+    { url = "https://files.pythonhosted.org/packages/04/91/1538085ace361f99280a26f9ccdc6023b22e7d0e23fdb849e2ef104fb54f/optree-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12188f6832c29dac37385a2f42fce961e303349909cff6d40e21cb27a8d09023", size = 423468, upload-time = "2025-04-06T16:12:35.325Z" },
+    { url = "https://files.pythonhosted.org/packages/c8/ec/c4d2203a6f1206790ea8b794df943a16918649b23cd03ec6beb7f75a81df/optree-0.15.0-cp313-cp313t-win32.whl", hash = "sha256:d7b8ce7d13580985922dcfbda515da3f004cd7cb1b03320b96ea32d8cfd76392", size = 308837, upload-time = "2025-04-06T16:12:36.889Z" },
+    { url = "https://files.pythonhosted.org/packages/66/db/567ea8d2874eb15e9a8a82598ab84a6b809ca16df083b7650deffb8a6171/optree-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:daccdb583abaab14346f0af316ee570152a5c058e7b9fb09d8f8171fe751f2b3", size = 344774, upload-time = "2025-04-06T16:12:38.396Z" },
+    { url = "https://files.pythonhosted.org/packages/ae/67/26804ab0222febadab6e27af4f4e607fad2d9520bb32422842509b79cf63/optree-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:e0162a36a6cedb0829efe980d0b370d4e5970fdb28a6609daa2c906d547add5f", size = 337502, upload-time = "2025-04-06T16:12:39.711Z" },
 ]
 
 [[package]]
 name = "packaging"
-version = "24.2"
+version = "25.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
+    { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
 ]
 
 [[package]]
@@ -638,26 +695,26 @@ name = "pandas"
 version = "2.2.3"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "numpy", marker = "python_full_version >= '3.13'" },
+    { name = "numpy" },
     { name = "python-dateutil" },
     { name = "pytz" },
     { name = "tzdata" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
-    { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
-    { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
-    { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 },
-    { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 },
-    { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 },
-    { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 },
-    { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 },
-    { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 },
-    { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 },
-    { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 },
-    { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 },
-    { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
+    { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" },
+    { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" },
+    { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" },
+    { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" },
+    { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" },
+    { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" },
+    { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" },
+    { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" },
+    { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" },
+    { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" },
+    { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" },
+    { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" },
+    { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" },
 ]
 
 [[package]]
@@ -667,68 +724,71 @@ source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010 }
+sdist = { url = "https://files.pythonhosted.org/packages/d1/81/74f6a65b848ffd16c18f920620ce999fe45fe27f01ab3911260ce4ed85e4/patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4", size = 396010, upload-time = "2024-11-12T14:10:54.642Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923 },
+    { url = "https://files.pythonhosted.org/packages/87/2b/b50d3d08ea0fc419c183a84210571eba005328efa62b6b98bc28e9ead32a/patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c", size = 232923, upload-time = "2024-11-12T14:10:52.85Z" },
 ]
 
 [[package]]
 name = "pillow"
-version = "11.0.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/a5/26/0d95c04c868f6bdb0c447e3ee2de5564411845e36a858cfd63766bc7b563/pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", size = 46737780 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/63/24/e2e15e392d00fcf4215907465d8ec2a2f23bcec1481a8ebe4ae760459995/pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", size = 3147300 },
-    { url = "https://files.pythonhosted.org/packages/43/72/92ad4afaa2afc233dc44184adff289c2e77e8cd916b3ddb72ac69495bda3/pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", size = 2978742 },
-    { url = "https://files.pythonhosted.org/packages/9e/da/c8d69c5bc85d72a8523fe862f05ababdc52c0a755cfe3d362656bb86552b/pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", size = 4194349 },
-    { url = "https://files.pythonhosted.org/packages/cd/e8/686d0caeed6b998351d57796496a70185376ed9c8ec7d99e1d19ad591fc6/pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", size = 4298714 },
-    { url = "https://files.pythonhosted.org/packages/ec/da/430015cec620d622f06854be67fd2f6721f52fc17fca8ac34b32e2d60739/pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", size = 4208514 },
-    { url = "https://files.pythonhosted.org/packages/44/ae/7e4f6662a9b1cb5f92b9cc9cab8321c381ffbee309210940e57432a4063a/pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", size = 4380055 },
-    { url = "https://files.pythonhosted.org/packages/74/d5/1a807779ac8a0eeed57f2b92a3c32ea1b696e6140c15bd42eaf908a261cd/pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", size = 4296751 },
-    { url = "https://files.pythonhosted.org/packages/38/8c/5fa3385163ee7080bc13026d59656267daaaaf3c728c233d530e2c2757c8/pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", size = 4430378 },
-    { url = "https://files.pythonhosted.org/packages/ca/1d/ad9c14811133977ff87035bf426875b93097fb50af747793f013979facdb/pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", size = 2249588 },
-    { url = "https://files.pythonhosted.org/packages/fb/01/3755ba287dac715e6afdb333cb1f6d69740a7475220b4637b5ce3d78cec2/pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", size = 2567509 },
-    { url = "https://files.pythonhosted.org/packages/c0/98/2c7d727079b6be1aba82d195767d35fcc2d32204c7a5820f822df5330152/pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", size = 2254791 },
-    { url = "https://files.pythonhosted.org/packages/eb/38/998b04cc6f474e78b563716b20eecf42a2fa16a84589d23c8898e64b0ffd/pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", size = 3150854 },
-    { url = "https://files.pythonhosted.org/packages/13/8e/be23a96292113c6cb26b2aa3c8b3681ec62b44ed5c2bd0b258bd59503d3c/pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", size = 2982369 },
-    { url = "https://files.pythonhosted.org/packages/97/8a/3db4eaabb7a2ae8203cd3a332a005e4aba00067fc514aaaf3e9721be31f1/pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", size = 4333703 },
-    { url = "https://files.pythonhosted.org/packages/28/ac/629ffc84ff67b9228fe87a97272ab125bbd4dc462745f35f192d37b822f1/pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", size = 4412550 },
-    { url = "https://files.pythonhosted.org/packages/d6/07/a505921d36bb2df6868806eaf56ef58699c16c388e378b0dcdb6e5b2fb36/pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", size = 4461038 },
-    { url = "https://files.pythonhosted.org/packages/d6/b9/fb620dd47fc7cc9678af8f8bd8c772034ca4977237049287e99dda360b66/pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", size = 2253197 },
-    { url = "https://files.pythonhosted.org/packages/df/86/25dde85c06c89d7fc5db17940f07aae0a56ac69aa9ccb5eb0f09798862a8/pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", size = 2572169 },
-    { url = "https://files.pythonhosted.org/packages/51/85/9c33f2517add612e17f3381aee7c4072779130c634921a756c97bc29fb49/pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", size = 2256828 },
+version = "11.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/cb/bb5c01fcd2a69335b86c22142b2bccfc3464087efb7fd382eee5ffc7fdf7/pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6", size = 47026707, upload-time = "2025-04-12T17:50:03.289Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/36/9c/447528ee3776e7ab8897fe33697a7ff3f0475bb490c5ac1456a03dc57956/pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28", size = 3190098, upload-time = "2025-04-12T17:48:23.915Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/09/29d5cd052f7566a63e5b506fac9c60526e9ecc553825551333e1e18a4858/pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830", size = 3030166, upload-time = "2025-04-12T17:48:25.738Z" },
+    { url = "https://files.pythonhosted.org/packages/71/5d/446ee132ad35e7600652133f9c2840b4799bbd8e4adba881284860da0a36/pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0", size = 4408674, upload-time = "2025-04-12T17:48:27.908Z" },
+    { url = "https://files.pythonhosted.org/packages/69/5f/cbe509c0ddf91cc3a03bbacf40e5c2339c4912d16458fcb797bb47bcb269/pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1", size = 4496005, upload-time = "2025-04-12T17:48:29.888Z" },
+    { url = "https://files.pythonhosted.org/packages/f9/b3/dd4338d8fb8a5f312021f2977fb8198a1184893f9b00b02b75d565c33b51/pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f", size = 4518707, upload-time = "2025-04-12T17:48:31.874Z" },
+    { url = "https://files.pythonhosted.org/packages/13/eb/2552ecebc0b887f539111c2cd241f538b8ff5891b8903dfe672e997529be/pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155", size = 4610008, upload-time = "2025-04-12T17:48:34.422Z" },
+    { url = "https://files.pythonhosted.org/packages/72/d1/924ce51bea494cb6e7959522d69d7b1c7e74f6821d84c63c3dc430cbbf3b/pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14", size = 4585420, upload-time = "2025-04-12T17:48:37.641Z" },
+    { url = "https://files.pythonhosted.org/packages/43/ab/8f81312d255d713b99ca37479a4cb4b0f48195e530cdc1611990eb8fd04b/pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b", size = 4667655, upload-time = "2025-04-12T17:48:39.652Z" },
+    { url = "https://files.pythonhosted.org/packages/94/86/8f2e9d2dc3d308dfd137a07fe1cc478df0a23d42a6c4093b087e738e4827/pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2", size = 2332329, upload-time = "2025-04-12T17:48:41.765Z" },
+    { url = "https://files.pythonhosted.org/packages/6d/ec/1179083b8d6067a613e4d595359b5fdea65d0a3b7ad623fee906e1b3c4d2/pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691", size = 2676388, upload-time = "2025-04-12T17:48:43.625Z" },
+    { url = "https://files.pythonhosted.org/packages/23/f1/2fc1e1e294de897df39fa8622d829b8828ddad938b0eaea256d65b84dd72/pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c", size = 2414950, upload-time = "2025-04-12T17:48:45.475Z" },
+    { url = "https://files.pythonhosted.org/packages/c4/3e/c328c48b3f0ead7bab765a84b4977acb29f101d10e4ef57a5e3400447c03/pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22", size = 3192759, upload-time = "2025-04-12T17:48:47.866Z" },
+    { url = "https://files.pythonhosted.org/packages/18/0e/1c68532d833fc8b9f404d3a642991441d9058eccd5606eab31617f29b6d4/pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7", size = 3033284, upload-time = "2025-04-12T17:48:50.189Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/cb/6faf3fb1e7705fd2db74e070f3bf6f88693601b0ed8e81049a8266de4754/pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16", size = 4445826, upload-time = "2025-04-12T17:48:52.346Z" },
+    { url = "https://files.pythonhosted.org/packages/07/94/8be03d50b70ca47fb434a358919d6a8d6580f282bbb7af7e4aa40103461d/pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b", size = 4527329, upload-time = "2025-04-12T17:48:54.403Z" },
+    { url = "https://files.pythonhosted.org/packages/fd/a4/bfe78777076dc405e3bd2080bc32da5ab3945b5a25dc5d8acaa9de64a162/pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406", size = 4549049, upload-time = "2025-04-12T17:48:56.383Z" },
+    { url = "https://files.pythonhosted.org/packages/65/4d/eaf9068dc687c24979e977ce5677e253624bd8b616b286f543f0c1b91662/pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91", size = 4635408, upload-time = "2025-04-12T17:48:58.782Z" },
+    { url = "https://files.pythonhosted.org/packages/1d/26/0fd443365d9c63bc79feb219f97d935cd4b93af28353cba78d8e77b61719/pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751", size = 4614863, upload-time = "2025-04-12T17:49:00.709Z" },
+    { url = "https://files.pythonhosted.org/packages/49/65/dca4d2506be482c2c6641cacdba5c602bc76d8ceb618fd37de855653a419/pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9", size = 4692938, upload-time = "2025-04-12T17:49:02.946Z" },
+    { url = "https://files.pythonhosted.org/packages/b3/92/1ca0c3f09233bd7decf8f7105a1c4e3162fb9142128c74adad0fb361b7eb/pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd", size = 2335774, upload-time = "2025-04-12T17:49:04.889Z" },
+    { url = "https://files.pythonhosted.org/packages/a5/ac/77525347cb43b83ae905ffe257bbe2cc6fd23acb9796639a1f56aa59d191/pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e", size = 2681895, upload-time = "2025-04-12T17:49:06.635Z" },
+    { url = "https://files.pythonhosted.org/packages/67/32/32dc030cfa91ca0fc52baebbba2e009bb001122a1daa8b6a79ad830b38d3/pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681", size = 2417234, upload-time = "2025-04-12T17:49:08.399Z" },
 ]
 
 [[package]]
 name = "pluggy"
 version = "1.5.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 }
+sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
+    { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" },
 ]
 
 [[package]]
 name = "pygments"
-version = "2.18.0"
+version = "2.19.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
+sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
+    { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
 ]
 
 [[package]]
 name = "pyparsing"
-version = "3.2.0"
+version = "3.2.3"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/8c/d5/e5aeee5387091148a19e1145f63606619cb5f20b83fccb63efae6474e7b2/pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c", size = 920984 }
+sdist = { url = "https://files.pythonhosted.org/packages/bb/22/f1129e69d94ffff626bdb5c835506b3a5b4f3d070f17ea295e12c2c6f60f/pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be", size = 1088608, upload-time = "2025-03-25T05:01:28.114Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/be/ec/2eb3cd785efd67806c46c13a17339708ddc346cbb684eade7a6e6f79536a/pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", size = 106921 },
+    { url = "https://files.pythonhosted.org/packages/05/e7/df2285f3d08fee213f2d041540fa4fc9ca6c2d44cf36d3a035bf2a8d2bcc/pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf", size = 111120, upload-time = "2025-03-25T05:01:24.908Z" },
 ]
 
 [[package]]
 name = "pytest"
-version = "8.3.4"
+version = "8.3.5"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -736,22 +796,22 @@ dependencies = [
     { name = "packaging" },
     { name = "pluggy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
+    { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
 ]
 
 [[package]]
 name = "pytest-cov"
-version = "6.0.0"
+version = "6.1.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "coverage" },
     { name = "pytest" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 }
+sdist = { url = "https://files.pythonhosted.org/packages/25/69/5f1e57f6c5a39f81411b550027bf72842c4567ff5fd572bed1edc9e4b5d9/pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a", size = 66857, upload-time = "2025-04-05T14:07:51.592Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 },
+    { url = "https://files.pythonhosted.org/packages/28/d0/def53b4a790cfb21483016430ed828f64830dd981ebe1089971cd10cab25/pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde", size = 23841, upload-time = "2025-04-05T14:07:49.641Z" },
 ]
 
 [[package]]
@@ -761,35 +821,35 @@ source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "six" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
+    { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
 ]
 
 [[package]]
 name = "pytz"
-version = "2024.2"
+version = "2025.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
+    { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" },
 ]
 
 [[package]]
 name = "pyyaml"
 version = "6.0.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
+sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
-    { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
-    { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
-    { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
-    { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
-    { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
-    { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
-    { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
-    { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
+    { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" },
+    { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" },
+    { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" },
+    { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" },
+    { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" },
+    { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" },
+    { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" },
+    { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" },
+    { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" },
 ]
 
 [[package]]
@@ -802,65 +862,49 @@ dependencies = [
     { name = "idna" },
     { name = "urllib3" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
+sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
+    { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
 ]
 
 [[package]]
 name = "requests-oauthlib"
-version = "1.3.1"
+version = "2.0.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "oauthlib" },
     { name = "requests" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/95/52/531ef197b426646f26b53815a7d2a67cb7a331ef098bb276db26a68ac49f/requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a", size = 52027 }
+sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/6f/bb/5deac77a9af870143c684ab46a7934038a53eb4aa975bc0687ed6ca2c610/requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5", size = 23892 },
+    { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" },
 ]
 
 [[package]]
 name = "rich"
-version = "13.9.4"
+version = "14.0.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "markdown-it-py" },
     { name = "pygments" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
+sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
+    { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" },
 ]
 
 [[package]]
-name = "ruff"
-version = "0.8.1"
+name = "roman-numerals-py"
+version = "3.1.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 }
+sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 },
-    { url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 },
-    { url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 },
-    { url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 },
-    { url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 },
-    { url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 },
-    { url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 },
-    { url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 },
-    { url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 },
-    { url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 },
-    { url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 },
-    { url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 },
-    { url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 },
-    { url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 },
-    { url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 },
-    { url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 },
-    { url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 },
+    { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" },
 ]
 
 [[package]]
 name = "scikit-learn"
-version = "1.5.2"
+version = "1.6.1"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "joblib" },
@@ -868,64 +912,87 @@ dependencies = [
     { name = "scipy" },
     { name = "threadpoolctl" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/37/59/44985a2bdc95c74e34fef3d10cb5d93ce13b0e2a7baefffe1b53853b502d/scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d", size = 7001680 }
+sdist = { url = "https://files.pythonhosted.org/packages/9e/a5/4ae3b3a0755f7b35a280ac90b28817d1f380318973cff14075ab41ef50d9/scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e", size = 7068312, upload-time = "2025-01-10T08:07:55.348Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a4/50/8891028437858cc510e13578fe7046574a60c2aaaa92b02d64aac5b1b412/scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5", size = 12025584 },
-    { url = "https://files.pythonhosted.org/packages/d2/79/17feef8a1c14149436083bec0e61d7befb4812e272d5b20f9d79ea3e9ab1/scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908", size = 10959795 },
-    { url = "https://files.pythonhosted.org/packages/b1/c8/f08313f9e2e656bd0905930ae8bf99a573ea21c34666a813b749c338202f/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3", size = 12077302 },
-    { url = "https://files.pythonhosted.org/packages/a7/48/fbfb4dc72bed0fe31fe045fb30e924909ad03f717c36694351612973b1a9/scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12", size = 13002811 },
-    { url = "https://files.pythonhosted.org/packages/a5/e7/0c869f9e60d225a77af90d2aefa7a4a4c0e745b149325d1450f0f0ce5399/scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f", size = 10951354 },
+    { url = "https://files.pythonhosted.org/packages/2e/59/8eb1872ca87009bdcdb7f3cdc679ad557b992c12f4b61f9250659e592c63/scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322", size = 12010001, upload-time = "2025-01-10T08:06:58.613Z" },
+    { url = "https://files.pythonhosted.org/packages/9d/05/f2fc4effc5b32e525408524c982c468c29d22f828834f0625c5ef3d601be/scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1", size = 11096360, upload-time = "2025-01-10T08:07:01.556Z" },
+    { url = "https://files.pythonhosted.org/packages/c8/e4/4195d52cf4f113573fb8ebc44ed5a81bd511a92c0228889125fac2f4c3d1/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348", size = 12209004, upload-time = "2025-01-10T08:07:06.931Z" },
+    { url = "https://files.pythonhosted.org/packages/94/be/47e16cdd1e7fcf97d95b3cb08bde1abb13e627861af427a3651fcb80b517/scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97", size = 13171776, upload-time = "2025-01-10T08:07:11.715Z" },
+    { url = "https://files.pythonhosted.org/packages/34/b0/ca92b90859070a1487827dbc672f998da95ce83edce1270fc23f96f1f61a/scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb", size = 11071865, upload-time = "2025-01-10T08:07:16.088Z" },
+    { url = "https://files.pythonhosted.org/packages/12/ae/993b0fb24a356e71e9a894e42b8a9eec528d4c70217353a1cd7a48bc25d4/scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236", size = 11955804, upload-time = "2025-01-10T08:07:20.385Z" },
+    { url = "https://files.pythonhosted.org/packages/d6/54/32fa2ee591af44507eac86406fa6bba968d1eb22831494470d0a2e4a1eb1/scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35", size = 11100530, upload-time = "2025-01-10T08:07:23.675Z" },
+    { url = "https://files.pythonhosted.org/packages/3f/58/55856da1adec655bdce77b502e94a267bf40a8c0b89f8622837f89503b5a/scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691", size = 12433852, upload-time = "2025-01-10T08:07:26.817Z" },
+    { url = "https://files.pythonhosted.org/packages/ff/4f/c83853af13901a574f8f13b645467285a48940f185b690936bb700a50863/scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f", size = 11337256, upload-time = "2025-01-10T08:07:31.084Z" },
 ]
 
 [[package]]
 name = "scipy"
-version = "1.14.1"
+version = "1.15.3"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/62/11/4d44a1f274e002784e4dbdb81e0ea96d2de2d1045b2132d5af62cc31fd28/scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417", size = 58620554 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/50/ef/ac98346db016ff18a6ad7626a35808f37074d25796fd0234c2bb0ed1e054/scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79", size = 39091068 },
-    { url = "https://files.pythonhosted.org/packages/b9/cc/70948fe9f393b911b4251e96b55bbdeaa8cca41f37c26fd1df0232933b9e/scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e", size = 29875417 },
-    { url = "https://files.pythonhosted.org/packages/3b/2e/35f549b7d231c1c9f9639f9ef49b815d816bf54dd050da5da1c11517a218/scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73", size = 23084508 },
-    { url = "https://files.pythonhosted.org/packages/3f/d6/b028e3f3e59fae61fb8c0f450db732c43dd1d836223a589a8be9f6377203/scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e", size = 25503364 },
-    { url = "https://files.pythonhosted.org/packages/a7/2f/6c142b352ac15967744d62b165537a965e95d557085db4beab2a11f7943b/scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d", size = 35292639 },
-    { url = "https://files.pythonhosted.org/packages/56/46/2449e6e51e0d7c3575f289f6acb7f828938eaab8874dbccfeb0cd2b71a27/scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e", size = 40798288 },
-    { url = "https://files.pythonhosted.org/packages/32/cd/9d86f7ed7f4497c9fd3e39f8918dd93d9f647ba80d7e34e4946c0c2d1a7c/scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06", size = 42524647 },
-    { url = "https://files.pythonhosted.org/packages/f5/1b/6ee032251bf4cdb0cc50059374e86a9f076308c1512b61c4e003e241efb7/scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84", size = 44469524 },
+sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" },
+    { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" },
+    { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" },
+    { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" },
+    { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" },
+    { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" },
+    { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" },
+    { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" },
+    { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" },
+    { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" },
+    { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" },
+    { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" },
+    { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" },
+    { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" },
+    { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" },
+    { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" },
+    { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" },
 ]
 
 [[package]]
 name = "six"
-version = "1.16.0"
+version = "1.17.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
+    { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
 ]
 
 [[package]]
 name = "snowballstemmer"
-version = "2.2.0"
+version = "3.0.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 }
+sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 },
+    { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
 ]
 
 [[package]]
 name = "soupsieve"
-version = "2.6"
+version = "2.7"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 }
+sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 },
+    { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" },
 ]
 
 [[package]]
 name = "sphinx"
-version = "8.1.3"
+version = "8.2.3"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "alabaster" },
@@ -937,6 +1004,7 @@ dependencies = [
     { name = "packaging" },
     { name = "pygments" },
     { name = "requests" },
+    { name = "roman-numerals-py" },
     { name = "snowballstemmer" },
     { name = "sphinxcontrib-applehelp" },
     { name = "sphinxcontrib-devhelp" },
@@ -945,24 +1013,24 @@ dependencies = [
     { name = "sphinxcontrib-qthelp" },
     { name = "sphinxcontrib-serializinghtml" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611 }
+sdist = { url = "https://files.pythonhosted.org/packages/38/ad/4360e50ed56cb483667b8e6dadf2d3fda62359593faabbe749a27c4eaca6/sphinx-8.2.3.tar.gz", hash = "sha256:398ad29dee7f63a75888314e9424d40f52ce5a6a87ae88e7071e80af296ec348", size = 8321876, upload-time = "2025-03-02T22:31:59.658Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125 },
+    { url = "https://files.pythonhosted.org/packages/31/53/136e9eca6e0b9dc0e1962e2c908fbea2e5ac000c2a2fbd9a35797958c48b/sphinx-8.2.3-py3-none-any.whl", hash = "sha256:4405915165f13521d875a8c29c8970800a0141c14cc5416a38feca4ea5d9b9c3", size = 3589741, upload-time = "2025-03-02T22:31:56.836Z" },
 ]
 
 [[package]]
 name = "sphinx-autoapi"
-version = "3.4.0"
+version = "3.6.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
-    { name = "astroid", marker = "python_full_version >= '3.13'" },
+    { name = "astroid" },
     { name = "jinja2" },
     { name = "pyyaml" },
     { name = "sphinx" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/4a/eb/cc243583bb1d518ca3b10998c203d919a8ed90affd4831f2b61ad09043d2/sphinx_autoapi-3.4.0.tar.gz", hash = "sha256:e6d5371f9411bbb9fca358c00a9e57aef3ac94cbfc5df4bab285946462f69e0c", size = 29292 }
+sdist = { url = "https://files.pythonhosted.org/packages/7f/a8/22b379a2a75ccb881217d3d4ae56d7d35f2d1bb4c8c0c51d0253676746a1/sphinx_autoapi-3.6.0.tar.gz", hash = "sha256:c685f274e41d0842ae7e199460c322c4bd7fec816ccc2da8d806094b4f64af06", size = 55417, upload-time = "2025-02-18T01:50:55.241Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/de/d6/f2acdc2567337fd5f5dc091a4e58d8a0fb14927b9779fc1e5ecee96d9824/sphinx_autoapi-3.4.0-py3-none-any.whl", hash = "sha256:4027fef2875a22c5f2a57107c71641d82f6166bf55beb407a47aaf3ef14e7b92", size = 34095 },
+    { url = "https://files.pythonhosted.org/packages/58/17/0eda9dc80fcaf257222b506844207e71b5d59567c41bbdcca2a72da119b9/sphinx_autoapi-3.6.0-py3-none-any.whl", hash = "sha256:f3b66714493cab140b0e896d33ce7137654a16ac1edb6563edcbd47bf975f711", size = 35281, upload-time = "2025-02-18T01:50:52.789Z" },
 ]
 
 [[package]]
@@ -973,63 +1041,63 @@ dependencies = [
     { name = "dom-toml" },
     { name = "domdf-python-tools" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695 }
+sdist = { url = "https://files.pythonhosted.org/packages/39/97/aa8cec3da3e78f2c396b63332e2fe92fe43f7ff2ad19b3998735f28b0a7f/sphinx_pyproject-0.3.0.tar.gz", hash = "sha256:efc4ee9d96f579c4e4ed1ac273868c64565e88c8e37fe6ec2dc59fbcd57684ab", size = 7695, upload-time = "2023-08-18T21:43:45.473Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076 },
+    { url = "https://files.pythonhosted.org/packages/90/d5/89cb47c6399fd57ca451af15361499813c5d53e588cb6e00d89411ce724f/sphinx_pyproject-0.3.0-py3-none-any.whl", hash = "sha256:3aca968919f5ecd390f96874c3f64a43c9c7fcfdc2fd4191a781ad9228501b52", size = 23076, upload-time = "2023-08-18T21:43:43.808Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-applehelp"
 version = "2.0.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053 }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 },
+    { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-devhelp"
 version = "2.0.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967 }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 },
+    { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-htmlhelp"
 version = "2.1.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617 }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 },
+    { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-jsmath"
 version = "1.0.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787 }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071 },
+    { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-qthelp"
 version = "2.0.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165 }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 },
+    { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
 ]
 
 [[package]]
 name = "sphinxcontrib-serializinghtml"
 version = "2.0.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080 }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 },
+    { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
 ]
 
 [[package]]
@@ -1043,26 +1111,26 @@ dependencies = [
     { name = "patsy" },
     { name = "scipy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/1f/3b/963a015dd8ea17e10c7b0e2f14d7c4daec903baf60a017e756b57953a4bf/statsmodels-0.14.4.tar.gz", hash = "sha256:5d69e0f39060dc72c067f9bb6e8033b6dccdb0bae101d76a7ef0bcc94e898b67", size = 20354802 }
+sdist = { url = "https://files.pythonhosted.org/packages/1f/3b/963a015dd8ea17e10c7b0e2f14d7c4daec903baf60a017e756b57953a4bf/statsmodels-0.14.4.tar.gz", hash = "sha256:5d69e0f39060dc72c067f9bb6e8033b6dccdb0bae101d76a7ef0bcc94e898b67", size = 20354802, upload-time = "2024-10-03T16:15:36.273Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/31/f8/2662e6a101315ad336f75168fa9bac71f913ebcb92a6be84031d84a0f21f/statsmodels-0.14.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5a24f5d2c22852d807d2b42daf3a61740820b28d8381daaf59dcb7055bf1a79", size = 10186886 },
-    { url = "https://files.pythonhosted.org/packages/fa/c0/ee6e8ed35fc1ca9c7538c592f4974547bf72274bc98db1ae4a6e87481a83/statsmodels-0.14.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df4f7864606fa843d7e7c0e6af288f034a2160dba14e6ccc09020a3cf67cb092", size = 9880066 },
-    { url = "https://files.pythonhosted.org/packages/d1/97/3380ca6d8fd66cfb3d12941e472642f26e781a311c355a4e97aab2ed0216/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91341cbde9e8bea5fb419a76e09114e221567d03f34ca26e6d67ae2c27d8fe3c", size = 10283521 },
-    { url = "https://files.pythonhosted.org/packages/fe/2a/55c5b5c5e5124a202ea3fe0bcdbdeceaf91b4ec6164b8434acb9dd97409c/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1322286a7bfdde2790bf72d29698a1b76c20b8423a55bdcd0d457969d0041f72", size = 10723228 },
-    { url = "https://files.pythonhosted.org/packages/4f/76/67747e49dc758daae06f33aad8247b718cd7d224f091d2cd552681215bb2/statsmodels-0.14.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e31b95ac603415887c9f0d344cb523889cf779bc52d68e27e2d23c358958fec7", size = 10859503 },
-    { url = "https://files.pythonhosted.org/packages/1d/eb/cb8b01f5edf8f135eb3d0553d159db113a35b2948d0e51eeb735e7ae09ea/statsmodels-0.14.4-cp313-cp313-win_amd64.whl", hash = "sha256:81030108d27aecc7995cac05aa280cf8c6025f6a6119894eef648997936c2dd0", size = 9817574 },
+    { url = "https://files.pythonhosted.org/packages/31/f8/2662e6a101315ad336f75168fa9bac71f913ebcb92a6be84031d84a0f21f/statsmodels-0.14.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5a24f5d2c22852d807d2b42daf3a61740820b28d8381daaf59dcb7055bf1a79", size = 10186886, upload-time = "2024-10-03T17:10:44.074Z" },
+    { url = "https://files.pythonhosted.org/packages/fa/c0/ee6e8ed35fc1ca9c7538c592f4974547bf72274bc98db1ae4a6e87481a83/statsmodels-0.14.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df4f7864606fa843d7e7c0e6af288f034a2160dba14e6ccc09020a3cf67cb092", size = 9880066, upload-time = "2024-10-03T17:10:56.972Z" },
+    { url = "https://files.pythonhosted.org/packages/d1/97/3380ca6d8fd66cfb3d12941e472642f26e781a311c355a4e97aab2ed0216/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91341cbde9e8bea5fb419a76e09114e221567d03f34ca26e6d67ae2c27d8fe3c", size = 10283521, upload-time = "2024-10-03T17:14:06.216Z" },
+    { url = "https://files.pythonhosted.org/packages/fe/2a/55c5b5c5e5124a202ea3fe0bcdbdeceaf91b4ec6164b8434acb9dd97409c/statsmodels-0.14.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1322286a7bfdde2790bf72d29698a1b76c20b8423a55bdcd0d457969d0041f72", size = 10723228, upload-time = "2024-10-03T17:14:19.587Z" },
+    { url = "https://files.pythonhosted.org/packages/4f/76/67747e49dc758daae06f33aad8247b718cd7d224f091d2cd552681215bb2/statsmodels-0.14.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e31b95ac603415887c9f0d344cb523889cf779bc52d68e27e2d23c358958fec7", size = 10859503, upload-time = "2024-10-03T17:14:32.798Z" },
+    { url = "https://files.pythonhosted.org/packages/1d/eb/cb8b01f5edf8f135eb3d0553d159db113a35b2948d0e51eeb735e7ae09ea/statsmodels-0.14.4-cp313-cp313-win_amd64.whl", hash = "sha256:81030108d27aecc7995cac05aa280cf8c6025f6a6119894eef648997936c2dd0", size = 9817574, upload-time = "2024-10-03T16:14:37.461Z" },
 ]
 
 [[package]]
 name = "sympy"
-version = "1.13.3"
+version = "1.14.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "mpmath" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 }
+sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 },
+    { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" },
 ]
 
 [[package]]
@@ -1072,6 +1140,7 @@ source = { virtual = "." }
 dependencies = [
     { name = "beautifulsoup4" },
     { name = "fake-useragent" },
+    { name = "httpx" },
     { name = "imageio" },
     { name = "keras" },
     { name = "lxml" },
@@ -1080,7 +1149,6 @@ dependencies = [
     { name = "opencv-python" },
     { name = "pandas" },
     { name = "pillow" },
-    { name = "requests" },
     { name = "rich" },
     { name = "scikit-learn" },
     { name = "sphinx-pyproject" },
@@ -1092,30 +1160,25 @@ dependencies = [
 ]
 
 [package.dev-dependencies]
-dev = [
-    { name = "pytest" },
-    { name = "pytest-cov" },
-]
 docs = [
     { name = "myst-parser" },
     { name = "sphinx-autoapi" },
     { name = "sphinx-pyproject" },
 ]
 euler-validate = [
+    { name = "httpx" },
     { name = "numpy" },
+]
+test = [
     { name = "pytest" },
     { name = "pytest-cov" },
-    { name = "requests" },
-]
-lint = [
-    { name = "codespell" },
-    { name = "ruff" },
 ]
 
 [package.metadata]
 requires-dist = [
     { name = "beautifulsoup4", specifier = ">=4.12.3" },
     { name = "fake-useragent", specifier = ">=1.5.1" },
+    { name = "httpx", specifier = ">=0.28.1" },
     { name = "imageio", specifier = ">=2.36.1" },
     { name = "keras", specifier = ">=3.7" },
     { name = "lxml", specifier = ">=5.3" },
@@ -1124,7 +1187,6 @@ requires-dist = [
     { name = "opencv-python", specifier = ">=4.10.0.84" },
     { name = "pandas", specifier = ">=2.2.3" },
     { name = "pillow", specifier = ">=11" },
-    { name = "requests", specifier = ">=2.32.3" },
     { name = "rich", specifier = ">=13.9.4" },
     { name = "scikit-learn", specifier = ">=1.5.2" },
     { name = "sphinx-pyproject", specifier = ">=0.3" },
@@ -1136,111 +1198,105 @@ requires-dist = [
 ]
 
 [package.metadata.requires-dev]
-dev = [
-    { name = "pytest", specifier = ">=8.3.4" },
-    { name = "pytest-cov", specifier = ">=6" },
-]
 docs = [
-    { name = "myst-parser", specifier = ">=4.0.0" },
-    { name = "sphinx-autoapi", specifier = ">=3.4.0" },
-    { name = "sphinx-pyproject", specifier = ">=0.3.0" },
+    { name = "myst-parser", specifier = ">=4" },
+    { name = "sphinx-autoapi", specifier = ">=3.4" },
+    { name = "sphinx-pyproject", specifier = ">=0.3" },
 ]
 euler-validate = [
+    { name = "httpx", specifier = ">=0.28.1" },
     { name = "numpy", specifier = ">=2.1.3" },
-    { name = "pytest", specifier = ">=8.3.4" },
-    { name = "pytest-cov", specifier = ">=6.0.0" },
-    { name = "requests", specifier = ">=2.32.3" },
 ]
-lint = [
-    { name = "codespell", specifier = ">=2.3" },
-    { name = "ruff", specifier = ">=0.8.1" },
+test = [
+    { name = "pytest", specifier = ">=8.3.4" },
+    { name = "pytest-cov", specifier = ">=6" },
 ]
 
 [[package]]
 name = "threadpoolctl"
-version = "3.5.0"
+version = "3.6.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/bd/55/b5148dcbf72f5cde221f8bfe3b6a540da7aa1842f6b491ad979a6c8b84af/threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", size = 41936 }
+sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/4b/2c/ffbf7a134b9ab11a67b0cf0726453cedd9c5043a4fe7a35d1cefa9a1bcfb/threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467", size = 18414 },
+    { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" },
 ]
 
 [[package]]
 name = "tomli"
 version = "2.2.1"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 }
+sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 },
-    { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 },
-    { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 },
-    { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 },
-    { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 },
-    { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 },
-    { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 },
-    { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 },
-    { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 },
-    { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 },
-    { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
+    { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
+    { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
+    { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
+    { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
+    { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
+    { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
+    { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
+    { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
+    { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
+    { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
+    { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
 ]
 
 [[package]]
 name = "tweepy"
-version = "4.14.0"
+version = "4.15.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "oauthlib" },
     { name = "requests" },
     { name = "requests-oauthlib" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/75/1c/0db8c3cf9d31bf63853ff612d201060ae78e6db03468a70e063bef0eda62/tweepy-4.14.0.tar.gz", hash = "sha256:1f9f1707d6972de6cff6c5fd90dfe6a449cd2e0d70bd40043ffab01e07a06c8c", size = 88623 }
+sdist = { url = "https://files.pythonhosted.org/packages/99/05/7c2c01bd62900eff24534779f1e1531491dfd872edb6a9d432ae91e18b4b/tweepy-4.15.0.tar.gz", hash = "sha256:1345cbcdf0a75e2d89f424c559fd49fda4d8cd7be25cd5131e3b57bad8a21d76", size = 100268, upload-time = "2025-01-15T21:25:05.307Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/4d/78/ba0065d5636bbf4a35b78c4f81b74e7858b609cdf69e629d6da5c91b9d92/tweepy-4.14.0-py3-none-any.whl", hash = "sha256:db6d3844ccc0c6d27f339f12ba8acc89912a961da513c1ae50fa2be502a56afb", size = 98520 },
+    { url = "https://files.pythonhosted.org/packages/81/53/ca632ec02085b5c432e98ae1f872a21f2b6bb6c3d022dcf586809cc65cd0/tweepy-4.15.0-py3-none-any.whl", hash = "sha256:64adcea317158937059e4e2897b3ceb750b0c2dd5df58938c2da8f7eb3b88e6a", size = 99379, upload-time = "2025-01-15T21:25:02.856Z" },
 ]
 
 [[package]]
 name = "typing-extensions"
-version = "4.12.2"
+version = "4.13.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
+    { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
 ]
 
 [[package]]
 name = "tzdata"
-version = "2024.2"
+version = "2025.2"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
+sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
+    { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
 ]
 
 [[package]]
 name = "urllib3"
-version = "2.2.3"
+version = "2.4.0"
 source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 }
+sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672, upload-time = "2025-04-10T15:23:39.232Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 },
+    { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680, upload-time = "2025-04-10T15:23:37.377Z" },
 ]
 
 [[package]]
 name = "xgboost"
-version = "2.1.3"
+version = "3.0.0"
 source = { registry = "https://pypi.org/simple" }
 dependencies = [
     { name = "numpy" },
-    { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and platform_system == 'Linux'" },
+    { name = "nvidia-nccl-cu12", marker = "platform_machine != 'aarch64' and sys_platform == 'linux'" },
     { name = "scipy" },
 ]
-sdist = { url = "https://files.pythonhosted.org/packages/48/b0/131ffc4a15fd3acee9be3a7baa6b2fa6faa479799c51b880de9fc3ddf550/xgboost-2.1.3.tar.gz", hash = "sha256:7699ec4226156887d3afc665c63ab87469db9d46e361c702ba9fccd22535730c", size = 1090326 }
+sdist = { url = "https://files.pythonhosted.org/packages/82/18/f58f9dcbcca811b30be945fd7f890f87a0ee822f7316e2ddd7a3e3056f08/xgboost-3.0.0.tar.gz", hash = "sha256:45e95416df6f6f01d9a62e60cf09fc57e5ee34697f3858337c796fac9ce3b9ed", size = 1156620, upload-time = "2025-03-15T13:45:01.277Z" }
 wheels = [
-    { url = "https://files.pythonhosted.org/packages/cd/c6/773ebd84414879bd0566788868ae46a6574f6efaf81e694f01ea1fed3277/xgboost-2.1.3-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:c9b0c92f13e3650e1e1cf92ff9ecef3efc6f5dc3d10ce17858df2081a89976ef", size = 2139909 },
-    { url = "https://files.pythonhosted.org/packages/28/3c/ddf5d9eb742cdb7fbcd5c854bce07471bad01194ac37de91db64fbef0c58/xgboost-2.1.3-py3-none-macosx_12_0_arm64.whl", hash = "sha256:fcbf1912a852bd07a7007be350c8dc3a484c5e775b612f2b3cd082fc76240eb3", size = 1938631 },
-    { url = "https://files.pythonhosted.org/packages/4a/3a/8cd69a216993fd9d54ceb079d1b357b7ef50678b3c2695d8a71962b8d0aa/xgboost-2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:27af88df1162cee016c67f267a0a16c3db1c48f256e12f64c45c8f8edf9571cd", size = 4441261 },
-    { url = "https://files.pythonhosted.org/packages/48/bc/05d7db90d421c5e3d681a12fd1eb087e37bf2e9bbe2b105422d6319ecc92/xgboost-2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32a43526208fe676527c698cb852e0e9515e6d7294143780e476d335290a131b", size = 4532380 },
-    { url = "https://files.pythonhosted.org/packages/0f/c8/f679a816c06a4a6d23da3f4b448d5f0615b51de2886ad3e3e695d17121b3/xgboost-2.1.3-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5d33090880f3d474f8cf5dda557c7bf8dbceefb62f2fd655c77efcabb9cac222", size = 4207000 },
-    { url = "https://files.pythonhosted.org/packages/32/93/66826e2f50cefecbb0a44bd1e667316bf0a3c8e78cd1f0cdf52f5b2c5c6f/xgboost-2.1.3-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8d85d38553855a1f8c40b8fbccca86af19202f91b244e2c7f77afbb2a6d9d785", size = 153894508 },
-    { url = "https://files.pythonhosted.org/packages/70/58/2f94976df39470fb00eec2cb4f914dde44cd0df8d96483208bf7db4bc97e/xgboost-2.1.3-py3-none-win_amd64.whl", hash = "sha256:25c0ffcbd62aac5bc22c79e08b5b2edad1d5e37f16610ebefa5f06f3e2ea3d96", size = 124909665 },
+    { url = "https://files.pythonhosted.org/packages/95/6c/d2a1636591e204667f320481b036acc9c526608bcc2319be71cce148102d/xgboost-3.0.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:ed8cffd7998bd9431c3b0287a70bec8e45c09b43c9474d9dfd261627713bd890", size = 2245638, upload-time = "2025-03-15T13:46:14.653Z" },
+    { url = "https://files.pythonhosted.org/packages/5a/0b/f9f815f240a9610d42367172b9f7ef7e8c9113a09b1bb35d4d85f96b910a/xgboost-3.0.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:314104bd3a1426a40f0c9662eef40e9ab22eb7a8068a42a8d198ce40412db75c", size = 2025491, upload-time = "2025-03-15T13:46:49.556Z" },
+    { url = "https://files.pythonhosted.org/packages/3c/aa/f4597dc989317d2431cd71998cac1f9205c773658e0cf6680bb9011b26eb/xgboost-3.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72c3405e8dfc37048f9fe339a058fa12b9f0f03bc31d3e56f0887eed2ed2baa1", size = 4841002, upload-time = "2025-03-15T13:47:19.693Z" },
+    { url = "https://files.pythonhosted.org/packages/ba/ba/b89d99b89946afc842feb8f47302e8022f1405f01212d3e186b6d674b2ba/xgboost-3.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:72d39e74649e9b628c4221111aa6a8caa860f2e853b25480424403ee61085126", size = 4903442, upload-time = "2025-03-15T13:47:44.917Z" },
+    { url = "https://files.pythonhosted.org/packages/bb/68/a7a274bedf43bbf9de120104b438b45e232395f8b9861519040b7b725535/xgboost-3.0.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:7bdee5787f86b83bebd75e2c96caf854760788e5f4203d063da50db5bf0efc5f", size = 4602694, upload-time = "2025-03-15T13:48:16.498Z" },
+    { url = "https://files.pythonhosted.org/packages/63/f1/653afe1a1b7e1d03f26fd4bd30f3eebcfac2d8982e1a85b6be3355dcae25/xgboost-3.0.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:61c7e391e373b8a5312503525c0689f83ef1912a1236377022865ab340f465a4", size = 253877333, upload-time = "2025-03-15T13:51:01.139Z" },
+    { url = "https://files.pythonhosted.org/packages/5e/03/15cd49e855c62226ecf1831bbe4c8e73a4324856077a23c495538a36e557/xgboost-3.0.0-py3-none-win_amd64.whl", hash = "sha256:0ea74e97f95b1eddfd27a46b7f22f72ec5a5322e1dc7cb41c9c23fb580763df9", size = 149971978, upload-time = "2025-03-15T13:53:26.596Z" },
 ]
diff --git a/web_programming/co2_emission.py b/web_programming/co2_emission.py
index 19af70489d1d..4f62df8447b0 100644
--- a/web_programming/co2_emission.py
+++ b/web_programming/co2_emission.py
@@ -2,22 +2,29 @@
 Get CO2 emission data from the UK CarbonIntensity API
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from datetime import date
 
-import requests
+import httpx
 
 BASE_URL = "https://api.carbonintensity.org.uk/intensity"
 
 
 # Emission in the last half hour
 def fetch_last_half_hour() -> str:
-    last_half_hour = requests.get(BASE_URL, timeout=10).json()["data"][0]
+    last_half_hour = httpx.get(BASE_URL, timeout=10).json()["data"][0]
     return last_half_hour["intensity"]["actual"]
 
 
 # Emissions in a specific date range
 def fetch_from_to(start, end) -> list:
-    return requests.get(f"{BASE_URL}/{start}/{end}", timeout=10).json()["data"]
+    return httpx.get(f"{BASE_URL}/{start}/{end}", timeout=10).json()["data"]
 
 
 if __name__ == "__main__":
diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py
index c27a5d12bb3f..f7db51b63169 100644
--- a/web_programming/covid_stats_via_xpath.py
+++ b/web_programming/covid_stats_via_xpath.py
@@ -4,9 +4,17 @@
 more convenient to use in Python web projects (e.g. Django or Flask-based)
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+#     "lxml",
+# ]
+# ///
+
 from typing import NamedTuple
 
-import requests
+import httpx
 from lxml import html
 
 
@@ -19,7 +27,7 @@ class CovidData(NamedTuple):
 def covid_stats(url: str = "https://www.worldometers.info/coronavirus/") -> CovidData:
     xpath_str = '//div[@class = "maincounter-number"]/span/text()'
     return CovidData(
-        *html.fromstring(requests.get(url, timeout=10).content).xpath(xpath_str)
+        *html.fromstring(httpx.get(url, timeout=10).content).xpath(xpath_str)
     )
 
 
diff --git a/web_programming/crawl_google_results.py b/web_programming/crawl_google_results.py
index cb75d450ff82..0ae13792e048 100644
--- a/web_programming/crawl_google_results.py
+++ b/web_programming/crawl_google_results.py
@@ -1,14 +1,28 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "fake-useragent",
+#     "httpx",
+# ]
+# ///
+
 import sys
 import webbrowser
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 from fake_useragent import UserAgent
 
 if __name__ == "__main__":
     print("Googling.....")
     url = "https://www.google.com/search?q=" + " ".join(sys.argv[1:])
-    res = requests.get(url, headers={"UserAgent": UserAgent().random}, timeout=10)
+    res = httpx.get(
+        url,
+        headers={"UserAgent": UserAgent().random},
+        timeout=10,
+        follow_redirects=True,
+    )
     # res.raise_for_status()
     with open("project1a.html", "wb") as out_file:  # only for knowing the class
         for data in res.iter_content(10000):
diff --git a/web_programming/crawl_google_scholar_citation.py b/web_programming/crawl_google_scholar_citation.py
index 5f2ccad5f414..77d7be00c7b6 100644
--- a/web_programming/crawl_google_scholar_citation.py
+++ b/web_programming/crawl_google_scholar_citation.py
@@ -3,7 +3,15 @@
 using title and year of publication, and volume and pages of journal.
 """
 
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
+import httpx
 from bs4 import BeautifulSoup
 
 
@@ -12,7 +20,7 @@ def get_citation(base_url: str, params: dict) -> str:
     Return the citation number.
     """
     soup = BeautifulSoup(
-        requests.get(base_url, params=params, timeout=10).content, "html.parser"
+        httpx.get(base_url, params=params, timeout=10).content, "html.parser"
     )
     div = soup.find("div", attrs={"class": "gs_ri"})
     anchors = div.find("div", attrs={"class": "gs_fl"}).find_all("a")
diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py
index 9623504b89ea..a007c9c35a65 100644
--- a/web_programming/currency_converter.py
+++ b/web_programming/currency_converter.py
@@ -3,9 +3,16 @@
 https://www.amdoren.com
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 import os
 
-import requests
+import httpx
 
 URL_BASE = "https://www.amdoren.com/api/currency.php"
 
@@ -176,7 +183,7 @@ def convert_currency(
     params = locals()
     # from is a reserved keyword
     params["from"] = params.pop("from_")
-    res = requests.get(URL_BASE, params=params, timeout=10).json()
+    res = httpx.get(URL_BASE, params=params, timeout=10).json()
     return str(res["amount"]) if res["error"] == 0 else res["error_message"]
 
 
diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py
index 573e1f575c8e..16b0b6772a9c 100644
--- a/web_programming/current_stock_price.py
+++ b/web_programming/current_stock_price.py
@@ -1,4 +1,12 @@
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
+import httpx
 from bs4 import BeautifulSoup
 
 """
@@ -20,8 +28,8 @@ def stock_price(symbol: str = "AAPL") -> str:
     True
     """
     url = f"https://finance.yahoo.com/quote/{symbol}?p={symbol}"
-    yahoo_finance_source = requests.get(
-        url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10
+    yahoo_finance_source = httpx.get(
+        url, headers={"USER-AGENT": "Mozilla/5.0"}, timeout=10, follow_redirects=True
     ).text
     soup = BeautifulSoup(yahoo_finance_source, "html.parser")
 
diff --git a/web_programming/current_weather.py b/web_programming/current_weather.py
index 4a8fa5e3c845..001eaf9020f4 100644
--- a/web_programming/current_weather.py
+++ b/web_programming/current_weather.py
@@ -1,4 +1,11 @@
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 # Put your API key(s) here
 OPENWEATHERMAP_API_KEY = ""
@@ -19,13 +26,13 @@ def current_weather(location: str) -> list[dict]:
     weather_data = []
     if OPENWEATHERMAP_API_KEY:
         params_openweathermap = {"q": location, "appid": OPENWEATHERMAP_API_KEY}
-        response_openweathermap = requests.get(
+        response_openweathermap = httpx.get(
             OPENWEATHERMAP_URL_BASE, params=params_openweathermap, timeout=10
         )
         weather_data.append({"OpenWeatherMap": response_openweathermap.json()})
     if WEATHERSTACK_API_KEY:
         params_weatherstack = {"query": location, "access_key": WEATHERSTACK_API_KEY}
-        response_weatherstack = requests.get(
+        response_weatherstack = httpx.get(
             WEATHERSTACK_URL_BASE, params=params_weatherstack, timeout=10
         )
         weather_data.append({"Weatherstack": response_weatherstack.json()})
diff --git a/web_programming/daily_horoscope.py b/web_programming/daily_horoscope.py
index 75e637d8e52c..b84ea13238cc 100644
--- a/web_programming/daily_horoscope.py
+++ b/web_programming/daily_horoscope.py
@@ -1,4 +1,12 @@
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
+import httpx
 from bs4 import BeautifulSoup
 
 
@@ -7,7 +15,7 @@ def horoscope(zodiac_sign: int, day: str) -> str:
         "https://www.horoscope.com/us/horoscopes/general/"
         f"horoscope-general-daily-{day}.aspx?sign={zodiac_sign}"
     )
-    soup = BeautifulSoup(requests.get(url, timeout=10).content, "html.parser")
+    soup = BeautifulSoup(httpx.get(url, timeout=10).content, "html.parser")
     return soup.find("div", class_="main-horoscope").p.text
 
 
diff --git a/web_programming/download_images_from_google_query.py b/web_programming/download_images_from_google_query.py
index 235cd35763ef..659cf6a398a3 100644
--- a/web_programming/download_images_from_google_query.py
+++ b/web_programming/download_images_from_google_query.py
@@ -1,10 +1,18 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
 import json
 import os
 import re
 import sys
 import urllib.request
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 
 headers = {
@@ -39,7 +47,7 @@ def download_images_from_google_query(query: str = "dhaka", max_images: int = 5)
         "ijn": "0",
     }
 
-    html = requests.get(
+    html = httpx.get(
         "https://www.google.com/search", params=params, headers=headers, timeout=10
     )
     soup = BeautifulSoup(html.text, "html.parser")
diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py
index d41dc4893608..3fa24085df95 100644
--- a/web_programming/emails_from_url.py
+++ b/web_programming/emails_from_url.py
@@ -1,5 +1,12 @@
 """Get the site emails from URL."""
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
 __author__ = "Muhammad Umer Farooq"
@@ -13,7 +20,7 @@
 from html.parser import HTMLParser
 from urllib import parse
 
-import requests
+import httpx
 
 
 class Parser(HTMLParser):
@@ -72,7 +79,7 @@ def emails_from_url(url: str = "https://github.com") -> list[str]:
 
     try:
         # Open URL
-        r = requests.get(url, timeout=10)
+        r = httpx.get(url, timeout=10, follow_redirects=True)
 
         # pass the raw HTML to the parser to get links
         parser.feed(r.text)
@@ -81,9 +88,15 @@ def emails_from_url(url: str = "https://github.com") -> list[str]:
         valid_emails = set()
         for link in parser.urls:
             # open URL.
-            # read = requests.get(link)
+            # Check if the link is already absolute
+            if not link.startswith("http://") and not link.startswith("https://"):
+                # Prepend protocol only if link starts with domain, normalize otherwise
+                if link.startswith(domain):
+                    link = f"https://{link}"
+                else:
+                    link = parse.urljoin(f"https://{domain}", link)
             try:
-                read = requests.get(link, timeout=10)
+                read = httpx.get(link, timeout=10, follow_redirects=True)
                 # Get the valid email.
                 emails = re.findall("[a-zA-Z0-9]+@" + domain, read.text)
                 # If not in list then append it.
diff --git a/web_programming/fetch_anime_and_play.py b/web_programming/fetch_anime_and_play.py
index e56b7124eeb5..616116595697 100644
--- a/web_programming/fetch_anime_and_play.py
+++ b/web_programming/fetch_anime_and_play.py
@@ -1,8 +1,17 @@
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "fake-useragent",
+#     "httpx",
+# ]
+# ///
+
+import httpx
 from bs4 import BeautifulSoup, NavigableString, Tag
 from fake_useragent import UserAgent
 
-BASE_URL = "https://ww1.gogoanime2.org"
+BASE_URL = "https://ww7.gogoanime2.org"
 
 
 def search_scraper(anime_name: str) -> list:
@@ -25,9 +34,9 @@ def search_scraper(anime_name: str) -> list:
     """
 
     # concat the name to form the search url.
-    search_url = f"{BASE_URL}/search/{anime_name}"
+    search_url = f"{BASE_URL}/search?keyword={anime_name}"
 
-    response = requests.get(
+    response = httpx.get(
         search_url, headers={"UserAgent": UserAgent().chrome}, timeout=10
     )  # request the url.
 
@@ -82,7 +91,7 @@ def search_anime_episode_list(episode_endpoint: str) -> list:
 
     request_url = f"{BASE_URL}{episode_endpoint}"
 
-    response = requests.get(
+    response = httpx.get(
         url=request_url, headers={"UserAgent": UserAgent().chrome}, timeout=10
     )
     response.raise_for_status()
@@ -133,7 +142,7 @@ def get_anime_episode(episode_endpoint: str) -> list:
 
     episode_page_url = f"{BASE_URL}{episode_endpoint}"
 
-    response = requests.get(
+    response = httpx.get(
         url=episode_page_url, headers={"User-Agent": UserAgent().chrome}, timeout=10
     )
     response.raise_for_status()
diff --git a/web_programming/fetch_bbc_news.py b/web_programming/fetch_bbc_news.py
index e5cd864a9d83..f3121fee7db4 100644
--- a/web_programming/fetch_bbc_news.py
+++ b/web_programming/fetch_bbc_news.py
@@ -1,13 +1,20 @@
 # Created by sarathkaul on 12/11/19
 
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 _NEWS_API = "https://newsapi.org/v1/articles?source=bbc-news&sortBy=top&apiKey="
 
 
 def fetch_bbc_news(bbc_news_api_key: str) -> None:
     # fetching a list of articles in json format
-    bbc_news_page = requests.get(_NEWS_API + bbc_news_api_key, timeout=10).json()
+    bbc_news_page = httpx.get(_NEWS_API + bbc_news_api_key, timeout=10).json()
     # each article in the list is a dict
     for i, article in enumerate(bbc_news_page["articles"], 1):
         print(f"{i}.) {article['title']}")
diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py
index 25d44245bb58..1d3d344e177e 100644
--- a/web_programming/fetch_github_info.py
+++ b/web_programming/fetch_github_info.py
@@ -18,12 +18,19 @@
 export USER_TOKEN=""
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
 import os
 from typing import Any
 
-import requests
+import httpx
 
 BASE_URL = "https://api.github.com"
 
@@ -36,13 +43,13 @@
 
 def fetch_github_info(auth_token: str) -> dict[Any, Any]:
     """
-    Fetch GitHub info of a user using the requests module
+    Fetch GitHub info of a user using the httpx module
     """
     headers = {
         "Authorization": f"token {auth_token}",
         "Accept": "application/vnd.github.v3+json",
     }
-    return requests.get(AUTHENTICATED_USER_ENDPOINT, headers=headers, timeout=10).json()
+    return httpx.get(AUTHENTICATED_USER_ENDPOINT, headers=headers, timeout=10).json()
 
 
 if __name__ == "__main__":  # pragma: no cover
diff --git a/web_programming/fetch_jobs.py b/web_programming/fetch_jobs.py
index 3753d25bbe5f..7a95f997078d 100644
--- a/web_programming/fetch_jobs.py
+++ b/web_programming/fetch_jobs.py
@@ -2,20 +2,26 @@
 Scraping jobs given job title and location from indeed website
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
 from collections.abc import Generator
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 
 url = "https://www.indeed.co.in/jobs?q=mobile+app+development&l="
 
 
 def fetch_jobs(location: str = "mumbai") -> Generator[tuple[str, str]]:
-    soup = BeautifulSoup(
-        requests.get(url + location, timeout=10).content, "html.parser"
-    )
+    soup = BeautifulSoup(httpx.get(url + location, timeout=10).content, "html.parser")
     # This attribute finds out all the specifics listed in a job
     for job in soup.find_all("div", attrs={"data-tn-component": "organicJob"}):
         job_title = job.find("a", attrs={"data-tn-element": "jobTitle"}).text.strip()
diff --git a/web_programming/fetch_quotes.py b/web_programming/fetch_quotes.py
index cf0add43f002..38113c888d89 100644
--- a/web_programming/fetch_quotes.py
+++ b/web_programming/fetch_quotes.py
@@ -6,19 +6,26 @@
     https://zenquotes.io/
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 import pprint
 
-import requests
+import httpx
 
 API_ENDPOINT_URL = "https://zenquotes.io/api"
 
 
 def quote_of_the_day() -> list:
-    return requests.get(API_ENDPOINT_URL + "/today", timeout=10).json()
+    return httpx.get(API_ENDPOINT_URL + "/today", timeout=10).json()
 
 
 def random_quotes() -> list:
-    return requests.get(API_ENDPOINT_URL + "/random", timeout=10).json()
+    return httpx.get(API_ENDPOINT_URL + "/random", timeout=10).json()
 
 
 if __name__ == "__main__":
diff --git a/web_programming/get_amazon_product_data.py b/web_programming/get_amazon_product_data.py
index b98ff2c030af..02db708e02bf 100644
--- a/web_programming/get_amazon_product_data.py
+++ b/web_programming/get_amazon_product_data.py
@@ -4,9 +4,18 @@
 information will include title, URL, price, ratings, and the discount available.
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+#     "pandas",
+# ]
+# ///
+
 from itertools import zip_longest
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 from pandas import DataFrame
 
@@ -25,7 +34,7 @@ def get_amazon_product_data(product: str = "laptop") -> DataFrame:
         "Accept-Language": "en-US, en;q=0.5",
     }
     soup = BeautifulSoup(
-        requests.get(url, headers=header, timeout=10).text, features="lxml"
+        httpx.get(url, headers=header, timeout=10).text, features="lxml"
     )
     # Initialize a Pandas dataframe with the column titles
     data_frame = DataFrame(
diff --git a/web_programming/get_imdb_top_250_movies_csv.py b/web_programming/get_imdb_top_250_movies_csv.py
index c914b29cb3b3..4cd29f0e1426 100644
--- a/web_programming/get_imdb_top_250_movies_csv.py
+++ b/web_programming/get_imdb_top_250_movies_csv.py
@@ -1,16 +1,24 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
 import csv
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 
 
 def get_imdb_top_250_movies(url: str = "") -> dict[str, float]:
     url = url or "https://www.imdb.com/chart/top/?ref_=nv_mv_250"
-    soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
-    titles = soup.find_all("td", attrs="titleColumn")
-    ratings = soup.find_all("td", class_="ratingColumn imdbRating")
+    soup = BeautifulSoup(httpx.get(url, timeout=10).text, "html.parser")
+    titles = soup.find_all("h3", class_="ipc-title__text")
+    ratings = soup.find_all("span", class_="ipc-rating-star--rating")
     return {
         title.a.text: float(rating.strong.text)
         for title, rating in zip(titles, ratings)
diff --git a/web_programming/get_ip_geolocation.py b/web_programming/get_ip_geolocation.py
index 574d287f0db1..153655257324 100644
--- a/web_programming/get_ip_geolocation.py
+++ b/web_programming/get_ip_geolocation.py
@@ -1,4 +1,11 @@
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 
 # Function to get geolocation data for an IP address
@@ -8,7 +15,7 @@ def get_ip_geolocation(ip_address: str) -> str:
         url = f"https://ipinfo.io/{ip_address}/json"
 
         # Send a GET request to the API
-        response = requests.get(url, timeout=10)
+        response = httpx.get(url, timeout=10)
 
         # Check if the HTTP request was successful
         response.raise_for_status()
@@ -23,7 +30,7 @@ def get_ip_geolocation(ip_address: str) -> str:
             location = "Location data not found."
 
         return location
-    except requests.exceptions.RequestException as e:
+    except httpx.RequestError as e:
         # Handle network-related exceptions
         return f"Request error: {e}"
     except ValueError as e:
diff --git a/web_programming/get_top_billionaires.py b/web_programming/get_top_billionaires.py
index 99f6e0be948a..b6f3bf0ca5be 100644
--- a/web_programming/get_top_billionaires.py
+++ b/web_programming/get_top_billionaires.py
@@ -3,9 +3,17 @@
 This works for some of us but fails for others.
 """
 
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+#     "rich",
+# ]
+# ///
+
 from datetime import UTC, date, datetime
 
-import requests
+import httpx
 from rich import box
 from rich import console as rich_console
 from rich import table as rich_table
@@ -57,7 +65,7 @@ def get_forbes_real_time_billionaires() -> list[dict[str, int | str]]:
     Returns:
         List of top 10 realtime billionaires data.
     """
-    response_json = requests.get(API_URL, timeout=10).json()
+    response_json = httpx.get(API_URL, timeout=10).json()
     return [
         {
             "Name": person["personName"],
diff --git a/web_programming/get_top_hn_posts.py b/web_programming/get_top_hn_posts.py
index f5d4f874c6c6..7d6308ae70c1 100644
--- a/web_programming/get_top_hn_posts.py
+++ b/web_programming/get_top_hn_posts.py
@@ -1,11 +1,18 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
-import requests
+import httpx
 
 
 def get_hackernews_story(story_id: str) -> dict:
     url = f"https://hacker-news.firebaseio.com/v0/item/{story_id}.json?print=pretty"
-    return requests.get(url, timeout=10).json()
+    return httpx.get(url, timeout=10).json()
 
 
 def hackernews_top_stories(max_stories: int = 10) -> list[dict]:
@@ -13,7 +20,7 @@ def hackernews_top_stories(max_stories: int = 10) -> list[dict]:
     Get the top max_stories posts from HackerNews - https://news.ycombinator.com/
     """
     url = "https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty"
-    story_ids = requests.get(url, timeout=10).json()[:max_stories]
+    story_ids = httpx.get(url, timeout=10).json()[:max_stories]
     return [get_hackernews_story(story_id) for story_id in story_ids]
 
 
diff --git a/web_programming/giphy.py b/web_programming/giphy.py
index 2bf3e3ea9c0b..90f4e51a7242 100644
--- a/web_programming/giphy.py
+++ b/web_programming/giphy.py
@@ -1,5 +1,13 @@
 #!/usr/bin/env python3
-import requests
+
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 giphy_api_key = "YOUR API KEY"
 # Can be fetched from https://developers.giphy.com/dashboard/
@@ -11,7 +19,7 @@ def get_gifs(query: str, api_key: str = giphy_api_key) -> list:
     """
     formatted_query = "+".join(query.split())
     url = f"https://api.giphy.com/v1/gifs/search?q={formatted_query}&api_key={api_key}"
-    gifs = requests.get(url, timeout=10).json()["data"]
+    gifs = httpx.get(url, timeout=10).json()["data"]
     return [gif["url"] for gif in gifs]
 
 
diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py
index df62735fb328..68271c1c4643 100644
--- a/web_programming/instagram_crawler.py
+++ b/web_programming/instagram_crawler.py
@@ -1,9 +1,19 @@
 #!/usr/bin/env python3
+
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "fake-useragent",
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
 import json
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 from fake_useragent import UserAgent
 
@@ -39,7 +49,7 @@ def get_json(self) -> dict:
         """
         Return a dict of user information
         """
-        html = requests.get(self.url, headers=headers, timeout=10).text
+        html = httpx.get(self.url, headers=headers, timeout=10).text
         scripts = BeautifulSoup(html, "html.parser").find_all("script")
         try:
             return extract_user_profile(scripts[4])
diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py
index 292cacc16c04..b6918f35515b 100644
--- a/web_programming/instagram_pic.py
+++ b/web_programming/instagram_pic.py
@@ -1,6 +1,14 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
 from datetime import UTC, datetime
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 
 
@@ -15,9 +23,9 @@ def download_image(url: str) -> str:
         A message indicating the result of the operation.
     """
     try:
-        response = requests.get(url, timeout=10)
+        response = httpx.get(url, timeout=10)
         response.raise_for_status()
-    except requests.exceptions.RequestException as e:
+    except httpx.RequestError as e:
         return f"An error occurred during the HTTP request to {url}: {e!r}"
 
     soup = BeautifulSoup(response.text, "html.parser")
@@ -30,13 +38,13 @@ def download_image(url: str) -> str:
         return f"Image URL not found in meta tag {image_meta_tag}."
 
     try:
-        image_data = requests.get(image_url, timeout=10).content
-    except requests.exceptions.RequestException as e:
+        image_data = httpx.get(image_url, timeout=10).content
+    except httpx.RequestError as e:
         return f"An error occurred during the HTTP request to {image_url}: {e!r}"
     if not image_data:
         return f"Failed to download the image from {image_url}."
 
-    file_name = f"{datetime.now(tz=UTC).astimezone():%Y-%m-%d_%H:%M:%S}.jpg"
+    file_name = f"{datetime.now(tz=UTC).astimezone():%Y-%m-%d_%H-%M-%S}.jpg"
     with open(file_name, "wb") as out_file:
         out_file.write(image_data)
     return f"Image downloaded and saved in the file {file_name}"
diff --git a/web_programming/instagram_video.py b/web_programming/instagram_video.py
index a4cddce25138..c6f72aef455a 100644
--- a/web_programming/instagram_video.py
+++ b/web_programming/instagram_video.py
@@ -1,17 +1,24 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from datetime import UTC, datetime
 
-import requests
+import httpx
 
 
 def download_video(url: str) -> bytes:
     base_url = "https://downloadgram.net/wp-json/wppress/video-downloader/video?url="
-    video_url = requests.get(base_url + url, timeout=10).json()[0]["urls"][0]["src"]
-    return requests.get(video_url, timeout=10).content
+    video_url = httpx.get(base_url + url, timeout=10)
+    return httpx.get(video_url, timeout=10).content
 
 
 if __name__ == "__main__":
     url = input("Enter Video/IGTV url: ").strip()
-    file_name = f"{datetime.now(tz=UTC).astimezone():%Y-%m-%d_%H:%M:%S}.mp4"
+    file_name = f"{datetime.now(tz=UTC).astimezone():%Y-%m-%d_%H-%M-%S}.mp4"
     with open(file_name, "wb") as fp:
         fp.write(download_video(url))
     print(f"Done. Video saved to disk as {file_name}.")
diff --git a/web_programming/nasa_data.py b/web_programming/nasa_data.py
index 33a6406c52a6..1025d2b4ab5f 100644
--- a/web_programming/nasa_data.py
+++ b/web_programming/nasa_data.py
@@ -1,6 +1,11 @@
-import shutil
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
 
-import requests
+import httpx
 
 
 def get_apod_data(api_key: str) -> dict:
@@ -9,17 +14,17 @@ def get_apod_data(api_key: str) -> dict:
     Get your API Key from: https://api.nasa.gov/
     """
     url = "https://api.nasa.gov/planetary/apod"
-    return requests.get(url, params={"api_key": api_key}, timeout=10).json()
+    return httpx.get(url, params={"api_key": api_key}, timeout=10).json()
 
 
 def save_apod(api_key: str, path: str = ".") -> dict:
     apod_data = get_apod_data(api_key)
     img_url = apod_data["url"]
     img_name = img_url.split("/")[-1]
-    response = requests.get(img_url, stream=True, timeout=10)
+    response = httpx.get(img_url, timeout=10)
 
     with open(f"{path}/{img_name}", "wb+") as img_file:
-        shutil.copyfileobj(response.raw, img_file)
+        img_file.write(response.content)
     del response
     return apod_data
 
@@ -29,7 +34,7 @@ def get_archive_data(query: str) -> dict:
     Get the data of a particular query from NASA archives
     """
     url = "https://images-api.nasa.gov/search"
-    return requests.get(url, params={"q": query}, timeout=10).json()
+    return httpx.get(url, params={"q": query}, timeout=10).json()
 
 
 if __name__ == "__main__":
diff --git a/web_programming/open_google_results.py b/web_programming/open_google_results.py
index 52dd37d7b91a..f245e4b6d818 100644
--- a/web_programming/open_google_results.py
+++ b/web_programming/open_google_results.py
@@ -1,8 +1,17 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "fake-useragent",
+#     "httpx",
+# ]
+# ///
+
 import webbrowser
 from sys import argv
 from urllib.parse import parse_qs, quote
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 from fake_useragent import UserAgent
 
@@ -13,26 +22,18 @@
 
     url = f"https://www.google.com/search?q={query}&num=100"
 
-    res = requests.get(
+    res = httpx.get(
         url,
         headers={"User-Agent": str(UserAgent().random)},
         timeout=10,
     )
 
     try:
-        link = (
-            BeautifulSoup(res.text, "html.parser")
-            .find("div", attrs={"class": "yuRUbf"})
-            .find("a")
-            .get("href")
-        )
+        link = BeautifulSoup(res.text, "html.parser").find("div").find("a").get("href")
 
     except AttributeError:
         link = parse_qs(
-            BeautifulSoup(res.text, "html.parser")
-            .find("div", attrs={"class": "kCrYT"})
-            .find("a")
-            .get("href")
+            BeautifulSoup(res.text, "html.parser").find("div").find("a").get("href")
         )["url"][0]
 
     webbrowser.open(link)
diff --git a/web_programming/random_anime_character.py b/web_programming/random_anime_character.py
index aed932866258..b94894e7ffe1 100644
--- a/web_programming/random_anime_character.py
+++ b/web_programming/random_anime_character.py
@@ -1,6 +1,15 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "fake-useragent",
+#     "httpx",
+# ]
+# ///
+
 import os
 
-import requests
+import httpx
 from bs4 import BeautifulSoup
 from fake_useragent import UserAgent
 
@@ -12,7 +21,7 @@ def save_image(image_url: str, image_title: str) -> None:
     """
     Saves the image of anime character
     """
-    image = requests.get(image_url, headers=headers, timeout=10)
+    image = httpx.get(image_url, headers=headers, timeout=10)
     with open(image_title, "wb") as file:
         file.write(image.content)
 
@@ -22,7 +31,7 @@ def random_anime_character() -> tuple[str, str, str]:
     Returns the Title, Description, and Image Title of a random anime character .
     """
     soup = BeautifulSoup(
-        requests.get(URL, headers=headers, timeout=10).text, "html.parser"
+        httpx.get(URL, headers=headers, timeout=10).text, "html.parser"
     )
     title = soup.find("meta", attrs={"property": "og:title"}).attrs["content"]
     image_url = soup.find("meta", attrs={"property": "og:image"}).attrs["content"]
diff --git a/web_programming/recaptcha_verification.py b/web_programming/recaptcha_verification.py
index 168862204fa9..c3a53da0b3da 100644
--- a/web_programming/recaptcha_verification.py
+++ b/web_programming/recaptcha_verification.py
@@ -32,7 +32,14 @@
 recaptcha verification.
 """
 
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 try:
     from django.contrib.auth import authenticate, login
@@ -56,7 +63,7 @@ def login_using_recaptcha(request):
     client_key = request.POST.get("g-recaptcha-response")
 
     # post recaptcha response to Google's recaptcha api
-    response = requests.post(
+    response = httpx.post(
         url, data={"secret": secret_key, "response": client_key}, timeout=10
     )
     # if the recaptcha api verified our keys
diff --git a/web_programming/reddit.py b/web_programming/reddit.py
index 6cc1a6b62009..863b44f60b16 100644
--- a/web_programming/reddit.py
+++ b/web_programming/reddit.py
@@ -1,6 +1,13 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
 from __future__ import annotations
 
-import requests
+import httpx
 
 valid_terms = set(
     """approved_at_utc approved_by author_flair_background_color
@@ -28,13 +35,14 @@ def get_subreddit_data(
     if invalid_search_terms := ", ".join(sorted(set(wanted_data) - valid_terms)):
         msg = f"Invalid search term: {invalid_search_terms}"
         raise ValueError(msg)
-    response = requests.get(
-        f"https://reddit.com/r/{subreddit}/{age}.json?limit={limit}",
+    response = httpx.get(
+        f"https://www.reddit.com/r/{subreddit}/{age}.json?limit={limit}",
         headers={"User-agent": "A random string"},
         timeout=10,
     )
+    response.raise_for_status()
     if response.status_code == 429:
-        raise requests.HTTPError(response=response)
+        raise httpx.HTTPError(response=response)
 
     data = response.json()
     if not wanted_data:
diff --git a/web_programming/search_books_by_isbn.py b/web_programming/search_books_by_isbn.py
index 6b69018e6639..02d096181fff 100644
--- a/web_programming/search_books_by_isbn.py
+++ b/web_programming/search_books_by_isbn.py
@@ -4,9 +4,16 @@
 ISBN: https://en.wikipedia.org/wiki/International_Standard_Book_Number
 """
 
-from json import JSONDecodeError  # Workaround for requests.exceptions.JSONDecodeError
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
 
-import requests
+from json import JSONDecodeError
+
+import httpx
 
 
 def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict:
@@ -25,7 +32,9 @@ def get_openlibrary_data(olid: str = "isbn/0140328726") -> dict:
     if new_olid.count("/") != 1:
         msg = f"{olid} is not a valid Open Library olid"
         raise ValueError(msg)
-    return requests.get(f"https://openlibrary.org/{new_olid}.json", timeout=10).json()
+    return httpx.get(
+        f"https://openlibrary.org/{new_olid}.json", timeout=10, follow_redirects=True
+    ).json()
 
 
 def summarize_book(ol_book_data: dict) -> dict:
@@ -36,8 +45,7 @@ def summarize_book(ol_book_data: dict) -> dict:
         "title": "Title",
         "publish_date": "Publish date",
         "authors": "Authors",
-        "number_of_pages": "Number of pages:",
-        "first_sentence": "First sentence",
+        "number_of_pages": "Number of pages",
         "isbn_10": "ISBN (10)",
         "isbn_13": "ISBN (13)",
     }
@@ -45,7 +53,6 @@ def summarize_book(ol_book_data: dict) -> dict:
     data["Authors"] = [
         get_openlibrary_data(author["key"])["name"] for author in data["Authors"]
     ]
-    data["First sentence"] = data["First sentence"]["value"]
     for key, value in data.items():
         if isinstance(value, list):
             data[key] = ", ".join(value)
@@ -71,5 +78,5 @@ def summarize_book(ol_book_data: dict) -> dict:
         try:
             book_summary = summarize_book(get_openlibrary_data(f"isbn/{isbn}"))
             print("\n".join(f"{key}: {value}" for key, value in book_summary.items()))
-        except JSONDecodeError:  # Workaround for requests.exceptions.RequestException:
+        except JSONDecodeError:
             print(f"Sorry, there are no results for ISBN: {isbn}.")
diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py
index d4d5658898ac..b00dac16688f 100644
--- a/web_programming/slack_message.py
+++ b/web_programming/slack_message.py
@@ -1,11 +1,18 @@
 # Created by sarathkaul on 12/11/19
 
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "httpx",
+# ]
+# ///
+
+import httpx
 
 
 def send_slack_message(message_body: str, slack_url: str) -> None:
     headers = {"Content-Type": "application/json"}
-    response = requests.post(
+    response = httpx.post(
         slack_url, json={"text": message_body}, headers=headers, timeout=10
     )
     if response.status_code != 200:
diff --git a/web_programming/test_fetch_github_info.py b/web_programming/test_fetch_github_info.py
index 2da97c782df7..2c433952fe10 100644
--- a/web_programming/test_fetch_github_info.py
+++ b/web_programming/test_fetch_github_info.py
@@ -1,6 +1,6 @@
 import json
 
-import requests
+import httpx
 
 from .fetch_github_info import AUTHENTICATED_USER_ENDPOINT, fetch_github_info
 
@@ -21,7 +21,7 @@ def mock_response(*args, **kwargs):
         assert "Accept" in kwargs["headers"]
         return FakeResponse(b'{"login":"test","id":1}')
 
-    monkeypatch.setattr(requests, "get", mock_response)
+    monkeypatch.setattr(httpx, "get", mock_response)
     result = fetch_github_info("token")
     assert result["login"] == "test"
     assert result["id"] == 1
diff --git a/web_programming/world_covid19_stats.py b/web_programming/world_covid19_stats.py
index 4948d8cfd43c..99383577d627 100644
--- a/web_programming/world_covid19_stats.py
+++ b/web_programming/world_covid19_stats.py
@@ -5,19 +5,31 @@
 This data is being scrapped from 'https://www.worldometers.info/coronavirus/'.
 """
 
-import requests
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "beautifulsoup4",
+#     "httpx",
+# ]
+# ///
+
+import httpx
 from bs4 import BeautifulSoup
 
 
-def world_covid19_stats(url: str = "https://www.worldometers.info/coronavirus") -> dict:
+def world_covid19_stats(
+    url: str = "https://www.worldometers.info/coronavirus/",
+) -> dict:
     """
     Return a dict of current worldwide COVID-19 statistics
     """
-    soup = BeautifulSoup(requests.get(url, timeout=10).text, "html.parser")
-    keys = soup.findAll("h1")
-    values = soup.findAll("div", {"class": "maincounter-number"})
-    keys += soup.findAll("span", {"class": "panel-title"})
-    values += soup.findAll("div", {"class": "number-table-main"})
+    soup = BeautifulSoup(
+        httpx.get(url, timeout=10, follow_redirects=True).text, "html.parser"
+    )
+    keys = soup.find_all("h1")
+    values = soup.find_all("div", {"class": "maincounter-number"})
+    keys += soup.find_all("span", {"class": "panel-title"})
+    values += soup.find_all("div", {"class": "number-table-main"})
     return {key.text.strip(): value.text.strip() for key, value in zip(keys, values)}
 
 

From 26ad6891d8bdadc7dd88a2c91cdb28977a7a9dc0 Mon Sep 17 00:00:00 2001
From: Isidro <isidroariass@hotmail.es>
Date: Wed, 21 May 2025 20:33:29 +0200
Subject: [PATCH 116/135] Add median() function using Quickselect (#12676)

* Add median() function using Quickselect

* Update quick_select.py

* Update quick_select.py

* Update quick_select.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 searches/quick_select.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/searches/quick_select.py b/searches/quick_select.py
index c8282e1fa5fc..f67f939c88c3 100644
--- a/searches/quick_select.py
+++ b/searches/quick_select.py
@@ -60,3 +60,25 @@ def quick_select(items: list, index: int):
     # must be in larger
     else:
         return quick_select(larger, index - (m + count))
+
+
+def median(items: list):
+    """
+    One common application of Quickselect is finding the median, which is
+    the middle element (or average of the two middle elements) in a sorted dataset.
+    It works efficiently on unsorted lists by partially sorting the data without
+    fully sorting the entire list.
+
+    >>> median([3, 2, 2, 9, 9])
+    3
+
+    >>> median([2, 2, 9, 9, 9, 3])
+    6.0
+    """
+    mid, rem = divmod(len(items), 2)
+    if rem != 0:
+        return quick_select(items=items, index=mid)
+    else:
+        low_mid = quick_select(items=items, index=mid - 1)
+        high_mid = quick_select(items=items, index=mid)
+        return (low_mid + high_mid) / 2

From ca445f52964f74ef5d398fd8dd6561ee198b353a Mon Sep 17 00:00:00 2001
From: Prajwal Choudhari <102746930+prajwalc22@users.noreply.github.com>
Date: Thu, 22 May 2025 20:38:37 +0530
Subject: [PATCH 117/135] Add bidirectional search algorithm implementation
 (#12649)

* Add bidirectional search algorithm implementation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix style and linting issues in bidirectional search

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add doctest for main function

* Add doctest for main function

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixed deprications

* fixed deprications

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* removed unused import

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update bidirectional_search.py

* Update bidirectional_search.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update bidirectional_search.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 graphs/bidirectional_search.py | 201 +++++++++++++++++++++++++++++++++
 1 file changed, 201 insertions(+)
 create mode 100644 graphs/bidirectional_search.py

diff --git a/graphs/bidirectional_search.py b/graphs/bidirectional_search.py
new file mode 100644
index 000000000000..b3ff9f75fd44
--- /dev/null
+++ b/graphs/bidirectional_search.py
@@ -0,0 +1,201 @@
+"""
+Bidirectional Search Algorithm.
+
+This algorithm searches from both the source and target nodes simultaneously,
+meeting somewhere in the middle. This approach can significantly reduce the
+search space compared to a traditional one-directional search.
+
+Time Complexity: O(b^(d/2)) where b is the branching factor and d is the depth
+Space Complexity: O(b^(d/2))
+
+https://en.wikipedia.org/wiki/Bidirectional_search
+"""
+
+from collections import deque
+
+
+def expand_search(
+    graph: dict[int, list[int]],
+    queue: deque[int],
+    parents: dict[int, int | None],
+    opposite_direction_parents: dict[int, int | None],
+) -> int | None:
+    if not queue:
+        return None
+
+    current = queue.popleft()
+    for neighbor in graph[current]:
+        if neighbor in parents:
+            continue
+
+        parents[neighbor] = current
+        queue.append(neighbor)
+
+        # Check if this creates an intersection
+        if neighbor in opposite_direction_parents:
+            return neighbor
+
+    return None
+
+
+def construct_path(current: int | None, parents: dict[int, int | None]) -> list[int]:
+    path: list[int] = []
+    while current is not None:
+        path.append(current)
+        current = parents[current]
+    return path
+
+
+def bidirectional_search(
+    graph: dict[int, list[int]], start: int, goal: int
+) -> list[int] | None:
+    """
+    Perform bidirectional search on a graph to find the shortest path.
+
+    Args:
+        graph: A dictionary where keys are nodes and values are lists of adjacent nodes
+        start: The starting node
+        goal: The target node
+
+    Returns:
+        A list representing the path from start to goal, or None if no path exists
+
+    Examples:
+        >>> graph = {
+        ...     0: [1, 2],
+        ...     1: [0, 3, 4],
+        ...     2: [0, 5, 6],
+        ...     3: [1, 7],
+        ...     4: [1, 8],
+        ...     5: [2, 9],
+        ...     6: [2, 10],
+        ...     7: [3, 11],
+        ...     8: [4, 11],
+        ...     9: [5, 11],
+        ...     10: [6, 11],
+        ...     11: [7, 8, 9, 10],
+        ... }
+        >>> bidirectional_search(graph=graph, start=0, goal=11)
+        [0, 1, 3, 7, 11]
+        >>> bidirectional_search(graph=graph, start=5, goal=5)
+        [5]
+        >>> disconnected_graph = {
+        ...     0: [1, 2],
+        ...     1: [0],
+        ...     2: [0],
+        ...     3: [4],
+        ...     4: [3],
+        ... }
+        >>> bidirectional_search(graph=disconnected_graph, start=0, goal=3) is None
+        True
+    """
+    if start == goal:
+        return [start]
+
+    # Check if start and goal are in the graph
+    if start not in graph or goal not in graph:
+        return None
+
+    # Initialize forward and backward search dictionaries
+    # Each maps a node to its parent in the search
+    forward_parents: dict[int, int | None] = {start: None}
+    backward_parents: dict[int, int | None] = {goal: None}
+
+    # Initialize forward and backward search queues
+    forward_queue = deque([start])
+    backward_queue = deque([goal])
+
+    # Intersection node (where the two searches meet)
+    intersection = None
+
+    # Continue until both queues are empty or an intersection is found
+    while forward_queue and backward_queue and intersection is None:
+        # Expand forward search
+        intersection = expand_search(
+            graph=graph,
+            queue=forward_queue,
+            parents=forward_parents,
+            opposite_direction_parents=backward_parents,
+        )
+
+        # If no intersection found, expand backward search
+        if intersection is not None:
+            break
+
+        intersection = expand_search(
+            graph=graph,
+            queue=backward_queue,
+            parents=backward_parents,
+            opposite_direction_parents=forward_parents,
+        )
+
+    # If no intersection found, there's no path
+    if intersection is None:
+        return None
+
+    # Construct path from start to intersection
+    forward_path: list[int] = construct_path(
+        current=intersection, parents=forward_parents
+    )
+    forward_path.reverse()
+
+    # Construct path from intersection to goal
+    backward_path: list[int] = construct_path(
+        current=backward_parents[intersection], parents=backward_parents
+    )
+
+    # Return the complete path
+    return forward_path + backward_path
+
+
+def main() -> None:
+    """
+    Run example of bidirectional search algorithm.
+
+    Examples:
+        >>> main()  # doctest: +NORMALIZE_WHITESPACE
+        Path from 0 to 11: [0, 1, 3, 7, 11]
+        Path from 5 to 5: [5]
+        Path from 0 to 3: None
+    """
+    # Example graph represented as an adjacency list
+    example_graph = {
+        0: [1, 2],
+        1: [0, 3, 4],
+        2: [0, 5, 6],
+        3: [1, 7],
+        4: [1, 8],
+        5: [2, 9],
+        6: [2, 10],
+        7: [3, 11],
+        8: [4, 11],
+        9: [5, 11],
+        10: [6, 11],
+        11: [7, 8, 9, 10],
+    }
+
+    # Test case 1: Path exists
+    start, goal = 0, 11
+    path = bidirectional_search(graph=example_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+    # Test case 2: Start and goal are the same
+    start, goal = 5, 5
+    path = bidirectional_search(graph=example_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+    # Test case 3: No path exists (disconnected graph)
+    disconnected_graph = {
+        0: [1, 2],
+        1: [0],
+        2: [0],
+        3: [4],
+        4: [3],
+    }
+    start, goal = 0, 3
+    path = bidirectional_search(graph=disconnected_graph, start=start, goal=goal)
+    print(f"Path from {start} to {goal}: {path}")
+
+
+if __name__ == "__main__":
+    main()

From c81cc269962b346420b9e551782bd836dedb4528 Mon Sep 17 00:00:00 2001
From: Isidro <isidroariass@hotmail.es>
Date: Thu, 22 May 2025 22:44:25 +0200
Subject: [PATCH 118/135] Improve hash map (#12678)

* Mutable _Item

* document falsy item

* resize_down: expected test result

* resize_down: actual result

This is a problem since it causes rapid cycling

* improve comment about falsy item

Co-authored-by: Andrey <Cjkjvfnby@gmail.com>

* fix long line

* Update hash_map.py

* Update hash_map.py

---------

Co-authored-by: Andrey <Cjkjvfnby@gmail.com>
Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
---
 data_structures/hashing/hash_map.py | 30 +++++++++++++++++++++++++----
 1 file changed, 26 insertions(+), 4 deletions(-)

diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py
index 9213d6930f67..8c56c327a492 100644
--- a/data_structures/hashing/hash_map.py
+++ b/data_structures/hashing/hash_map.py
@@ -16,7 +16,7 @@
 VAL = TypeVar("VAL")
 
 
-@dataclass(frozen=True, slots=True)
+@dataclass(slots=True)
 class _Item(Generic[KEY, VAL]):
     key: KEY
     val: VAL
@@ -72,16 +72,17 @@ def _try_set(self, ind: int, key: KEY, val: VAL) -> bool:
 
         If bucket is empty or key is the same, does insert and return True.
 
-        If bucket has another key or deleted placeholder,
-        that means that we need to check next bucket.
+        If bucket has another key that means that we need to check next bucket.
         """
         stored = self._buckets[ind]
         if not stored:
+            # A falsy item means that bucket was never used (None)
+            # or was deleted (_deleted).
             self._buckets[ind] = _Item(key, val)
             self._len += 1
             return True
         elif stored.key == key:
-            self._buckets[ind] = _Item(key, val)
+            stored.val = val
             return True
         else:
             return False
@@ -228,6 +229,27 @@ def __delitem__(self, key: KEY) -> None:
         Traceback (most recent call last):
         ...
         KeyError: 4
+
+        # Test resize down when sparse
+        ## Setup: resize up
+        >>> hm = HashMap(initial_block_size=100, capacity_factor=0.75)
+        >>> len(hm._buckets)
+        100
+        >>> for i in range(75):
+        ...     hm[i] = i
+        >>> len(hm._buckets)
+        100
+        >>> hm[75] = 75
+        >>> len(hm._buckets)
+        200
+
+        ## Resize down
+        >>> del hm[75]
+        >>> len(hm._buckets)
+        200
+        >>> del hm[74]
+        >>> len(hm._buckets)
+        100
         """
         for ind in self._iterate_buckets(key):
             item = self._buckets[ind]

From e1115b5f15afa44deb4752483b9b456457f7e683 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Fri, 23 May 2025 00:07:43 +0300
Subject: [PATCH 119/135] Add tests and cleanup sum_of_subsets algorithm
 (#12746)

* Add tests and cleanup sum_of_subsets algorithm.

* Update sum_of_subsets.py

* Update sum_of_subsets.py

* Update sum_of_subsets.py

* Update sum_of_subsets.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 backtracking/sum_of_subsets.py | 45 ++++++++++++++++++++++------------
 1 file changed, 30 insertions(+), 15 deletions(-)

diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py
index f34d3ca34339..f26f179f8725 100644
--- a/backtracking/sum_of_subsets.py
+++ b/backtracking/sum_of_subsets.py
@@ -1,5 +1,5 @@
 """
-The sum-of-subsetsproblem states that a set of non-negative integers, and a
+The sum-of-subsets problem states that a set of non-negative integers, and a
 value M, determine all possible subsets of the given set whose summation sum
 equal to given M.
 
@@ -7,10 +7,20 @@
 can be used only once.
 """
 
-from __future__ import annotations
 
+def generate_sum_of_subsets_solutions(nums: list[int], max_sum: int) -> list[list[int]]:
+    """
+    The main function. For list of numbers 'nums' find the subsets with sum
+    equal to 'max_sum'
+
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=9)
+    [[3, 4, 2], [4, 5]]
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=3)
+    [[3]]
+    >>> generate_sum_of_subsets_solutions(nums=[3, 34, 4, 12, 5, 2], max_sum=1)
+    []
+    """
 
-def generate_sum_of_subsets_soln(nums: list[int], max_sum: int) -> list[list[int]]:
     result: list[list[int]] = []
     path: list[int] = []
     num_index = 0
@@ -34,7 +44,21 @@ def create_state_space_tree(
     This algorithm follows depth-fist-search and backtracks when the node is not
     branchable.
 
+    >>> path = []
+    >>> result = []
+    >>> create_state_space_tree(
+    ...     nums=[1],
+    ...     max_sum=1,
+    ...     num_index=0,
+    ...     path=path,
+    ...     result=result,
+    ...     remaining_nums_sum=1)
+    >>> path
+    []
+    >>> result
+    [[1]]
     """
+
     if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum:
         return
     if sum(path) == max_sum:
@@ -51,16 +75,7 @@ def create_state_space_tree(
         )
 
 
-"""
-remove the comment to take an input from the user
-
-print("Enter the elements")
-nums = list(map(int, input().split()))
-print("Enter max_sum sum")
-max_sum = int(input())
+if __name__ == "__main__":
+    import doctest
 
-"""
-nums = [3, 34, 4, 12, 5, 2]
-max_sum = 9
-result = generate_sum_of_subsets_soln(nums, max_sum)
-print(*result)
+    doctest.testmod()

From a8ad2db2b966fcbd2e6a82b8f1544116838c02b0 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Fri, 23 May 2025 00:17:48 +0300
Subject: [PATCH 120/135] Update matrix_chain_order calculation with more
 details and test. (#12759)

---
 dynamic_programming/matrix_chain_order.py | 33 ++++++++++++++++-------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/dynamic_programming/matrix_chain_order.py b/dynamic_programming/matrix_chain_order.py
index d612aea7b99d..6df43e84be28 100644
--- a/dynamic_programming/matrix_chain_order.py
+++ b/dynamic_programming/matrix_chain_order.py
@@ -5,13 +5,19 @@
 Implementation of Matrix Chain Multiplication
 Time Complexity: O(n^3)
 Space Complexity: O(n^2)
+
+Reference: https://en.wikipedia.org/wiki/Matrix_chain_multiplication
 """
 
 
-def matrix_chain_order(array):
+def matrix_chain_order(array: list[int]) -> tuple[list[list[int]], list[list[int]]]:
+    """
+    >>> matrix_chain_order([10, 30, 5])
+    ([[0, 0, 0], [0, 0, 1500], [0, 0, 0]], [[0, 0, 0], [0, 0, 1], [0, 0, 0]])
+    """
     n = len(array)
-    matrix = [[0 for x in range(n)] for x in range(n)]
-    sol = [[0 for x in range(n)] for x in range(n)]
+    matrix = [[0 for _ in range(n)] for _ in range(n)]
+    sol = [[0 for _ in range(n)] for _ in range(n)]
 
     for chain_length in range(2, n):
         for a in range(1, n - chain_length + 1):
@@ -28,26 +34,33 @@ def matrix_chain_order(array):
     return matrix, sol
 
 
-# Print order of matrix with Ai as Matrix
-def print_optiomal_solution(optimal_solution, i, j):
+def print_optimal_solution(optimal_solution: list[list[int]], i: int, j: int):
+    """
+    Print order of matrix with Ai as Matrix.
+    """
+
     if i == j:
         print("A" + str(i), end=" ")
     else:
         print("(", end=" ")
-        print_optiomal_solution(optimal_solution, i, optimal_solution[i][j])
-        print_optiomal_solution(optimal_solution, optimal_solution[i][j] + 1, j)
+        print_optimal_solution(optimal_solution, i, optimal_solution[i][j])
+        print_optimal_solution(optimal_solution, optimal_solution[i][j] + 1, j)
         print(")", end=" ")
 
 
 def main():
+    """
+    Size of matrix created from array [30, 35, 15, 5, 10, 20, 25] will be:
+    30*35 35*15 15*5 5*10 10*20 20*25
+    """
+
     array = [30, 35, 15, 5, 10, 20, 25]
     n = len(array)
-    # Size of matrix created from above array will be
-    # 30*35 35*15 15*5 5*10 10*20 20*25
+
     matrix, optimal_solution = matrix_chain_order(array)
 
     print("No. of Operation required: " + str(matrix[1][n - 1]))
-    print_optiomal_solution(optimal_solution, 1, n - 1)
+    print_optimal_solution(optimal_solution, 1, n - 1)
 
 
 if __name__ == "__main__":

From e26c388af0668c7b4236442691d36a477e02f400 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 26 May 2025 22:55:55 +0200
Subject: [PATCH 121/135] [pre-commit.ci] pre-commit autoupdate (#12760)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.9 → v0.11.11](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.9...v0.11.11)
- [github.com/tox-dev/pyproject-fmt: v2.5.1 → v2.6.0](https://github.com/tox-dev/pyproject-fmt/compare/v2.5.1...v2.6.0)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <pre-commit-ci[bot]@users.noreply.github.com>
---
 .pre-commit-config.yaml | 4 ++--
 DIRECTORY.md            | 1 +
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 288e3f591403..6c1879ab1ac6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.9
+    rev: v0.11.11
     hooks:
       - id: ruff
       - id: ruff-format
@@ -29,7 +29,7 @@ repos:
           - tomli
 
   - repo: https://github.com/tox-dev/pyproject-fmt
-    rev: "v2.5.1"
+    rev: "v2.6.0"
     hooks:
       - id: pyproject-fmt
 
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 4129f9c1a5e2..00f4bb4ef2b2 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -480,6 +480,7 @@
   * [Bi Directional Dijkstra](graphs/bi_directional_dijkstra.py)
   * [Bidirectional A Star](graphs/bidirectional_a_star.py)
   * [Bidirectional Breadth First Search](graphs/bidirectional_breadth_first_search.py)
+  * [Bidirectional Search](graphs/bidirectional_search.py)
   * [Boruvka](graphs/boruvka.py)
   * [Breadth First Search](graphs/breadth_first_search.py)
   * [Breadth First Search 2](graphs/breadth_first_search_2.py)

From d0d7f0b18ab5cca2c573448b757c45bc0eee69c6 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Thu, 29 May 2025 08:56:25 +0200
Subject: [PATCH 122/135] maths/radix2_fft.py: Fix calculation for Python 3.14
 (#12772)

* maths/radix2_fft.py: Fix calculation for Python 3.14

As suggested at:
* https://github.com/TheAlgorithms/Python/issues/12729#issuecomment-2878608477
* https://github.com/TheAlgorithms/Python/pull/12710#discussion_r2080117333

@MaximSmolskiy @KirilBangachev @skirpichev Careful review, please.  I am just implementing the suggestion, but I do not have any expertise in this area.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Change test results

* GitHub Actions: Rename job from build to directory_writer

It is confusing to have two jobs named `build`.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 .github/workflows/directory_writer.yml | 2 +-
 maths/radix2_fft.py                    | 8 +++++---
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml
index 55d89f455a25..3edb5c91a951 100644
--- a/.github/workflows/directory_writer.yml
+++ b/.github/workflows/directory_writer.yml
@@ -3,7 +3,7 @@
 name: directory_writer
 on: [push]
 jobs:
-  build:
+  directory_writer:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
diff --git a/maths/radix2_fft.py b/maths/radix2_fft.py
index d41dc82d5588..ccd5cdcc0160 100644
--- a/maths/radix2_fft.py
+++ b/maths/radix2_fft.py
@@ -40,13 +40,13 @@ class FFT:
 
     Print product
     >>> x.product  # 2x + 3x^2 + 8x^3 + 4x^4 + 6x^5
-    [(-0+0j), (2+0j), (3+0j), (8+0j), (6+0j), (8+0j)]
+    [(-0-0j), (2+0j), (3-0j), (8-0j), (6+0j), (8+0j)]
 
     __str__ test
     >>> print(x)
     A = 0*x^0 + 1*x^1 + 2*x^0 + 3*x^2
     B = 0*x^2 + 1*x^3 + 2*x^4
-    A*B = 0*x^(-0+0j) + 1*x^(2+0j) + 2*x^(3+0j) + 3*x^(8+0j) + 4*x^(6+0j) + 5*x^(8+0j)
+    A*B = 0*x^(-0-0j) + 1*x^(2+0j) + 2*x^(3-0j) + 3*x^(8-0j) + 4*x^(6+0j) + 5*x^(8+0j)
     """
 
     def __init__(self, poly_a=None, poly_b=None):
@@ -147,7 +147,9 @@ def __multiply(self):
             inverce_c = new_inverse_c
             next_ncol *= 2
         # Unpack
-        inverce_c = [round(x[0].real, 8) + round(x[0].imag, 8) * 1j for x in inverce_c]
+        inverce_c = [
+            complex(round(x[0].real, 8), round(x[0].imag, 8)) for x in inverce_c
+        ]
 
         # Remove leading 0's
         while inverce_c[-1] == 0:

From 4b077c06b623ea20a90e9e5678e38101a4636584 Mon Sep 17 00:00:00 2001
From: JEONGHYUN MIN
 <161672721+kangaroo-eating-carrots@users.noreply.github.com>
Date: Sun, 1 Jun 2025 05:11:46 +0800
Subject: [PATCH 123/135] feat: create straight_line_depreciation (#12377)

Co-authored-by: Jeonghyun Min <20122791@tafe.wa.edu.au>
---
 financial/straight_line_depreciation.py | 103 ++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
 create mode 100644 financial/straight_line_depreciation.py

diff --git a/financial/straight_line_depreciation.py b/financial/straight_line_depreciation.py
new file mode 100644
index 000000000000..e11e1c1364ce
--- /dev/null
+++ b/financial/straight_line_depreciation.py
@@ -0,0 +1,103 @@
+"""
+In accounting, depreciation refers to the decreases in the value
+of a fixed asset during the asset's useful life.
+When an organization purchases a fixed asset,
+the purchase expenditure is not recognized as an expense immediately.
+Instead, the decreases in the asset's value are recognized as expenses
+over the years during which the asset is used.
+
+The following methods are widely used
+for depreciation calculation in accounting:
+- Straight-line method
+- Diminishing balance method
+- Units-of-production method
+
+The straight-line method is the simplest and most widely used.
+This method calculates depreciation by spreading the cost evenly
+over the asset's useful life.
+
+The following formula shows how to calculate the yearly depreciation expense:
+
+- annual depreciation expense =
+    (purchase cost of asset - residual value) / useful life of asset(years)
+
+Further information on:
+https://en.wikipedia.org/wiki/Depreciation
+
+The function, straight_line_depreciation, returns a list of
+the depreciation expenses over the given period.
+"""
+
+
+def straight_line_depreciation(
+    useful_years: int,
+    purchase_value: float,
+    residual_value: float = 0.0,
+) -> list[float]:
+    """
+    Calculate the depreciation expenses over the given period
+    :param useful_years: Number of years the asset will be used
+    :param purchase_value: Purchase expenditure for the asset
+    :param residual_value: Residual value of the asset at the end of its useful life
+    :return: A list of annual depreciation expenses over the asset's useful life
+    >>> straight_line_depreciation(10, 1100.0, 100.0)
+    [100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0, 100.0]
+    >>> straight_line_depreciation(6, 1250.0, 50.0)
+    [200.0, 200.0, 200.0, 200.0, 200.0, 200.0]
+    >>> straight_line_depreciation(4, 1001.0)
+    [250.25, 250.25, 250.25, 250.25]
+    >>> straight_line_depreciation(11, 380.0, 50.0)
+    [30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0, 30.0]
+    >>> straight_line_depreciation(1, 4985, 100)
+    [4885.0]
+    """
+
+    if not isinstance(useful_years, int):
+        raise TypeError("Useful years must be an integer")
+
+    if useful_years < 1:
+        raise ValueError("Useful years cannot be less than 1")
+
+    if not isinstance(purchase_value, (float, int)):
+        raise TypeError("Purchase value must be numeric")
+
+    if not isinstance(residual_value, (float, int)):
+        raise TypeError("Residual value must be numeric")
+
+    if purchase_value < 0.0:
+        raise ValueError("Purchase value cannot be less than zero")
+
+    if purchase_value < residual_value:
+        raise ValueError("Purchase value cannot be less than residual value")
+
+    # Calculate annual depreciation expense
+    depreciable_cost = purchase_value - residual_value
+    annual_depreciation_expense = depreciable_cost / useful_years
+
+    # List of annual depreciation expenses
+    list_of_depreciation_expenses = []
+    accumulated_depreciation_expense = 0.0
+    for period in range(useful_years):
+        if period != useful_years - 1:
+            accumulated_depreciation_expense += annual_depreciation_expense
+            list_of_depreciation_expenses.append(annual_depreciation_expense)
+        else:
+            depreciation_expense_in_end_year = (
+                depreciable_cost - accumulated_depreciation_expense
+            )
+            list_of_depreciation_expenses.append(depreciation_expense_in_end_year)
+
+    return list_of_depreciation_expenses
+
+
+if __name__ == "__main__":
+    user_input_useful_years = int(input("Please Enter Useful Years:\n > "))
+    user_input_purchase_value = float(input("Please Enter Purchase Value:\n > "))
+    user_input_residual_value = float(input("Please Enter Residual Value:\n > "))
+    print(
+        straight_line_depreciation(
+            user_input_useful_years,
+            user_input_purchase_value,
+            user_input_residual_value,
+        )
+    )

From c3d4b9e54d65c90422a0863c38b1ca72e6037bc9 Mon Sep 17 00:00:00 2001
From: Mindaugas <76015221+mindaugl@users.noreply.github.com>
Date: Mon, 2 Jun 2025 20:32:47 +0300
Subject: [PATCH 124/135] Add doctests for the boyer_moore_search algorithm.
 (#12769)

* Add doctests for the boyer_moore_search algorithm.

* Update boyer_moore_search.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update boyer_moore_search.py

* Update boyer_moore_search.py

---------

Co-authored-by: Maxim Smolskiy <mithridatus@mail.ru>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
---
 strings/boyer_moore_search.py | 49 +++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 16 deletions(-)

diff --git a/strings/boyer_moore_search.py b/strings/boyer_moore_search.py
index 9615d2fd659b..ad14a504f792 100644
--- a/strings/boyer_moore_search.py
+++ b/strings/boyer_moore_search.py
@@ -11,23 +11,31 @@
 a shift is proposed that moves the entirety of Pattern past
 the point of mismatch in the text.
 
-If there no mismatch then the pattern matches with text block.
+If there is no mismatch then the pattern matches with text block.
 
 Time Complexity : O(n/m)
     n=length of main string
     m=length of pattern string
 """
 
-from __future__ import annotations
-
 
 class BoyerMooreSearch:
+    """
+    Example usage:
+
+        bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        positions = bms.bad_character_heuristic()
+
+    where 'positions' contain the locations where the pattern was matched.
+    """
+
     def __init__(self, text: str, pattern: str):
         self.text, self.pattern = text, pattern
         self.textLen, self.patLen = len(text), len(pattern)
 
     def match_in_pattern(self, char: str) -> int:
-        """finds the index of char in pattern in reverse order
+        """
+        Finds the index of char in pattern in reverse order.
 
         Parameters :
             char (chr): character to be searched
@@ -35,6 +43,10 @@ def match_in_pattern(self, char: str) -> int:
         Returns :
             i (int): index of char from last in pattern
             -1 (int): if char is not found in pattern
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.match_in_pattern("B")
+        1
         """
 
         for i in range(self.patLen - 1, -1, -1):
@@ -44,8 +56,8 @@ def match_in_pattern(self, char: str) -> int:
 
     def mismatch_in_text(self, current_pos: int) -> int:
         """
-        find the index of mis-matched character in text when compared with pattern
-        from last
+        Find the index of mis-matched character in text when compared with pattern
+        from last.
 
         Parameters :
             current_pos (int): current index position of text
@@ -53,6 +65,10 @@ def mismatch_in_text(self, current_pos: int) -> int:
         Returns :
             i (int): index of mismatched char from last in text
             -1 (int): if there is no mismatch between pattern and text block
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.mismatch_in_text(2)
+        3
         """
 
         for i in range(self.patLen - 1, -1, -1):
@@ -61,7 +77,14 @@ def mismatch_in_text(self, current_pos: int) -> int:
         return -1
 
     def bad_character_heuristic(self) -> list[int]:
-        # searches pattern in text and returns index positions
+        """
+        Finds the positions of the pattern location.
+
+        >>> bms = BoyerMooreSearch(text="ABAABA", pattern="AB")
+        >>> bms.bad_character_heuristic()
+        [0, 3]
+        """
+
         positions = []
         for i in range(self.textLen - self.patLen + 1):
             mismatch_index = self.mismatch_in_text(i)
@@ -75,13 +98,7 @@ def bad_character_heuristic(self) -> list[int]:
         return positions
 
 
-text = "ABAABA"
-pattern = "AB"
-bms = BoyerMooreSearch(text, pattern)
-positions = bms.bad_character_heuristic()
+if __name__ == "__main__":
+    import doctest
 
-if len(positions) == 0:
-    print("No match found")
-else:
-    print("Pattern found in following positions: ")
-    print(positions)
+    doctest.testmod()

From 7665ba5e257a1a42caa36d781925d87968ee1497 Mon Sep 17 00:00:00 2001
From: Qasim Umar <111963035+QasimUmarKhan@users.noreply.github.com>
Date: Sat, 5 Jul 2025 15:58:45 +0500
Subject: [PATCH 125/135] Add unit tests for factorial.py (#12815)

* Add unit tests for factorial.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* uvx --python=3.12 unittest2pytest --write test_factorial.py

* Update error message in current_stock_price.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 maths/test_factorial.py                | 37 ++++++++++++++++++++++++++
 web_programming/current_stock_price.py |  2 +-
 2 files changed, 38 insertions(+), 1 deletion(-)
 create mode 100644 maths/test_factorial.py

diff --git a/maths/test_factorial.py b/maths/test_factorial.py
new file mode 100644
index 000000000000..d80d88add745
--- /dev/null
+++ b/maths/test_factorial.py
@@ -0,0 +1,37 @@
+# /// script
+# requires-python = ">=3.13"
+# dependencies = [
+#     "pytest",
+# ]
+# ///
+
+import pytest
+
+from maths.factorial import factorial, factorial_recursive
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_zero(function):
+    assert function(0) == 1
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_positive_integers(function):
+    assert function(1) == 1
+    assert function(5) == 120
+    assert function(7) == 5040
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_large_number(function):
+    assert function(10) == 3628800
+
+
+@pytest.mark.parametrize("function", [factorial, factorial_recursive])
+def test_negative_number(function):
+    with pytest.raises(ValueError):
+        function(-3)
+
+
+if __name__ == "__main__":
+    pytest.main(["-v", __file__])
diff --git a/web_programming/current_stock_price.py b/web_programming/current_stock_price.py
index 16b0b6772a9c..531da949ea50 100644
--- a/web_programming/current_stock_price.py
+++ b/web_programming/current_stock_price.py
@@ -23,7 +23,7 @@
 def stock_price(symbol: str = "AAPL") -> str:
     """
     >>> stock_price("EEEE")
-    '- '
+    'No <fin-streamer> tag with the specified data-testid attribute found.'
     >>> isinstance(float(stock_price("GOOG")),float)
     True
     """

From cd3c3c3130f8b067b27993a93fac0e0ffe5e59d2 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Sun, 6 Jul 2025 00:35:29 +0200
Subject: [PATCH 126/135] Various ruff fixes (#12821)

---
 DIRECTORY.md                                      |  2 ++
 ciphers/hill_cipher.py                            |  2 +-
 .../binary_tree/non_recursive_segment_tree.py     |  4 ++--
 data_structures/hashing/hash_map.py               |  4 ++--
 data_structures/heap/heap.py                      |  4 ++--
 data_structures/heap/randomized_heap.py           |  6 +++---
 data_structures/heap/skew_heap.py                 |  6 +++---
 data_structures/linked_list/skip_list.py          |  6 +++---
 data_structures/queues/queue_by_list.py           | 15 ++++++---------
 data_structures/queues/queue_by_two_stacks.py     | 15 ++++++---------
 data_structures/stacks/stack.py                   |  4 ++--
 .../stacks/stack_with_doubly_linked_list.py       |  6 +++---
 .../stacks/stack_with_singly_linked_list.py       |  6 +++---
 graphs/graph_adjacency_list.py                    |  4 ++--
 graphs/graph_adjacency_matrix.py                  |  4 ++--
 graphs/graph_list.py                              |  4 ++--
 graphs/minimum_spanning_tree_kruskal2.py          |  8 ++++----
 graphs/minimum_spanning_tree_prims2.py            |  8 ++++----
 matrix/matrix_class.py                            |  4 ++--
 other/least_recently_used.py                      |  4 ++--
 other/lfu_cache.py                                |  8 ++++----
 other/lru_cache.py                                |  8 ++++----
 pyproject.toml                                    |  3 +++
 sorts/insertion_sort.py                           |  2 +-
 sorts/tim_sort.py                                 |  2 +-
 25 files changed, 69 insertions(+), 70 deletions(-)

diff --git a/DIRECTORY.md b/DIRECTORY.md
index 00f4bb4ef2b2..81d6f4c70864 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -443,6 +443,7 @@
   * [Present Value](financial/present_value.py)
   * [Price Plus Tax](financial/price_plus_tax.py)
   * [Simple Moving Average](financial/simple_moving_average.py)
+  * [Straight Line Depreciation](financial/straight_line_depreciation.py)
   * [Time And Half Pay](financial/time_and_half_pay.py)
 
 ## Fractals
@@ -790,6 +791,7 @@
   * [Sumset](maths/sumset.py)
   * [Sylvester Sequence](maths/sylvester_sequence.py)
   * [Tanh](maths/tanh.py)
+  * [Test Factorial](maths/test_factorial.py)
   * [Test Prime Check](maths/test_prime_check.py)
   * [Three Sum](maths/three_sum.py)
   * [Trapezoidal Rule](maths/trapezoidal_rule.py)
diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py
index 33b2529f017b..19422688cd70 100644
--- a/ciphers/hill_cipher.py
+++ b/ciphers/hill_cipher.py
@@ -79,7 +79,7 @@ def replace_digits(self, num: int) -> str:
         >>> hill_cipher.replace_digits(26)
         '0'
         """
-        return self.key_string[round(num)]
+        return self.key_string[(num)]
 
     def check_determinant(self) -> None:
         """
diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py
index ca0d5c111c4f..7d1c965fab50 100644
--- a/data_structures/binary_tree/non_recursive_segment_tree.py
+++ b/data_structures/binary_tree/non_recursive_segment_tree.py
@@ -39,12 +39,12 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T")
 
 
-class SegmentTree(Generic[T]):
+class SegmentTree[T]:
     def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None:
         """
         Segment Tree constructor, it works just with commutative combiner.
diff --git a/data_structures/hashing/hash_map.py b/data_structures/hashing/hash_map.py
index 8c56c327a492..0d99e578b73e 100644
--- a/data_structures/hashing/hash_map.py
+++ b/data_structures/hashing/hash_map.py
@@ -10,14 +10,14 @@
 
 from collections.abc import Iterator, MutableMapping
 from dataclasses import dataclass
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 KEY = TypeVar("KEY")
 VAL = TypeVar("VAL")
 
 
 @dataclass(slots=True)
-class _Item(Generic[KEY, VAL]):
+class _Item[KEY, VAL]:
     key: KEY
     val: VAL
 
diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py
index 7b15e69f13ca..41ef0ddd1005 100644
--- a/data_structures/heap/heap.py
+++ b/data_structures/heap/heap.py
@@ -2,7 +2,7 @@
 
 from abc import abstractmethod
 from collections.abc import Iterable
-from typing import Generic, Protocol, TypeVar
+from typing import Protocol, TypeVar
 
 
 class Comparable(Protocol):
@@ -22,7 +22,7 @@ def __eq__(self: T, other: object) -> bool:
 T = TypeVar("T", bound=Comparable)
 
 
-class Heap(Generic[T]):
+class Heap[T: Comparable]:
     """A Max Heap Implementation
 
     >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5]
diff --git a/data_structures/heap/randomized_heap.py b/data_structures/heap/randomized_heap.py
index 12888c1f4089..9668d3ac2a45 100644
--- a/data_structures/heap/randomized_heap.py
+++ b/data_structures/heap/randomized_heap.py
@@ -4,12 +4,12 @@
 
 import random
 from collections.abc import Iterable
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T", bound=bool)
 
 
-class RandomizedHeapNode(Generic[T]):
+class RandomizedHeapNode[T: bool]:
     """
     One node of the randomized heap. Contains the value and references to
     two children.
@@ -73,7 +73,7 @@ def merge(
         return root1
 
 
-class RandomizedHeap(Generic[T]):
+class RandomizedHeap[T: bool]:
     """
     A data structure that allows inserting a new value and to pop the smallest
     values. Both operations take O(logN) time where N is the size of the
diff --git a/data_structures/heap/skew_heap.py b/data_structures/heap/skew_heap.py
index 0839db711cb1..4e82569444fa 100644
--- a/data_structures/heap/skew_heap.py
+++ b/data_structures/heap/skew_heap.py
@@ -3,12 +3,12 @@
 from __future__ import annotations
 
 from collections.abc import Iterable, Iterator
-from typing import Any, Generic, TypeVar
+from typing import Any, TypeVar
 
 T = TypeVar("T", bound=bool)
 
 
-class SkewNode(Generic[T]):
+class SkewNode[T: bool]:
     """
     One node of the skew heap. Contains the value and references to
     two children.
@@ -87,7 +87,7 @@ def merge(
         return result
 
 
-class SkewHeap(Generic[T]):
+class SkewHeap[T: bool]:
     """
     A data structure that allows inserting a new value and to pop the smallest
     values. Both operations take O(logN) time where N is the size of the
diff --git a/data_structures/linked_list/skip_list.py b/data_structures/linked_list/skip_list.py
index 13e9a94a8698..f21ca70bbc82 100644
--- a/data_structures/linked_list/skip_list.py
+++ b/data_structures/linked_list/skip_list.py
@@ -7,13 +7,13 @@
 
 from itertools import pairwise
 from random import random
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 KT = TypeVar("KT")
 VT = TypeVar("VT")
 
 
-class Node(Generic[KT, VT]):
+class Node[KT, VT]:
     def __init__(self, key: KT | str = "root", value: VT | None = None):
         self.key = key
         self.value = value
@@ -49,7 +49,7 @@ def level(self) -> int:
         return len(self.forward)
 
 
-class SkipList(Generic[KT, VT]):
+class SkipList[KT, VT]:
     def __init__(self, p: float = 0.5, max_level: int = 16):
         self.head: Node[KT, VT] = Node[KT, VT]()
         self.level = 0
diff --git a/data_structures/queues/queue_by_list.py b/data_structures/queues/queue_by_list.py
index 4b05be9fd08e..182cc4147c47 100644
--- a/data_structures/queues/queue_by_list.py
+++ b/data_structures/queues/queue_by_list.py
@@ -1,13 +1,10 @@
 """Queue represented by a Python list"""
 
 from collections.abc import Iterable
-from typing import Generic, TypeVar
 
-_T = TypeVar("_T")
 
-
-class QueueByList(Generic[_T]):
-    def __init__(self, iterable: Iterable[_T] | None = None) -> None:
+class QueueByList[T]:
+    def __init__(self, iterable: Iterable[T] | None = None) -> None:
         """
         >>> QueueByList()
         Queue(())
@@ -16,7 +13,7 @@ def __init__(self, iterable: Iterable[_T] | None = None) -> None:
         >>> QueueByList((i**2 for i in range(1, 4)))
         Queue((1, 4, 9))
         """
-        self.entries: list[_T] = list(iterable or [])
+        self.entries: list[T] = list(iterable or [])
 
     def __len__(self) -> int:
         """
@@ -58,7 +55,7 @@ def __repr__(self) -> str:
 
         return f"Queue({tuple(self.entries)})"
 
-    def put(self, item: _T) -> None:
+    def put(self, item: T) -> None:
         """Put `item` to the Queue
 
         >>> queue = QueueByList()
@@ -72,7 +69,7 @@ def put(self, item: _T) -> None:
 
         self.entries.append(item)
 
-    def get(self) -> _T:
+    def get(self) -> T:
         """
         Get `item` from the Queue
 
@@ -118,7 +115,7 @@ def rotate(self, rotation: int) -> None:
         for _ in range(rotation):
             put(get(0))
 
-    def get_front(self) -> _T:
+    def get_front(self) -> T:
         """Get the front item from the Queue
 
         >>> queue = QueueByList((10, 20, 30))
diff --git a/data_structures/queues/queue_by_two_stacks.py b/data_structures/queues/queue_by_two_stacks.py
index cd62f155a63b..f9e302ffcedd 100644
--- a/data_structures/queues/queue_by_two_stacks.py
+++ b/data_structures/queues/queue_by_two_stacks.py
@@ -1,13 +1,10 @@
 """Queue implementation using two stacks"""
 
 from collections.abc import Iterable
-from typing import Generic, TypeVar
 
-_T = TypeVar("_T")
 
-
-class QueueByTwoStacks(Generic[_T]):
-    def __init__(self, iterable: Iterable[_T] | None = None) -> None:
+class QueueByTwoStacks[T]:
+    def __init__(self, iterable: Iterable[T] | None = None) -> None:
         """
         >>> QueueByTwoStacks()
         Queue(())
@@ -16,8 +13,8 @@ def __init__(self, iterable: Iterable[_T] | None = None) -> None:
         >>> QueueByTwoStacks((i**2 for i in range(1, 4)))
         Queue((1, 4, 9))
         """
-        self._stack1: list[_T] = list(iterable or [])
-        self._stack2: list[_T] = []
+        self._stack1: list[T] = list(iterable or [])
+        self._stack2: list[T] = []
 
     def __len__(self) -> int:
         """
@@ -59,7 +56,7 @@ def __repr__(self) -> str:
         """
         return f"Queue({tuple(self._stack2[::-1] + self._stack1)})"
 
-    def put(self, item: _T) -> None:
+    def put(self, item: T) -> None:
         """
         Put `item` into the Queue
 
@@ -74,7 +71,7 @@ def put(self, item: _T) -> None:
 
         self._stack1.append(item)
 
-    def get(self) -> _T:
+    def get(self) -> T:
         """
         Get `item` from the Queue
 
diff --git a/data_structures/stacks/stack.py b/data_structures/stacks/stack.py
index 93698f5aa116..3ffa32d4167f 100644
--- a/data_structures/stacks/stack.py
+++ b/data_structures/stacks/stack.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
@@ -13,7 +13,7 @@ class StackUnderflowError(BaseException):
     pass
 
 
-class Stack(Generic[T]):
+class Stack[T]:
     """A stack is an abstract data type that serves as a collection of
     elements with two principal operations: push() and pop(). push() adds an
     element to the top of the stack, and pop() removes an element from the top
diff --git a/data_structures/stacks/stack_with_doubly_linked_list.py b/data_structures/stacks/stack_with_doubly_linked_list.py
index 50c5236e073c..ad01cd7eb6aa 100644
--- a/data_structures/stacks/stack_with_doubly_linked_list.py
+++ b/data_structures/stacks/stack_with_doubly_linked_list.py
@@ -3,19 +3,19 @@
 
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class Node(Generic[T]):
+class Node[T]:
     def __init__(self, data: T):
         self.data = data  # Assign data
         self.next: Node[T] | None = None  # Initialize next as null
         self.prev: Node[T] | None = None  # Initialize prev as null
 
 
-class Stack(Generic[T]):
+class Stack[T]:
     """
     >>> stack = Stack()
     >>> stack.is_empty()
diff --git a/data_structures/stacks/stack_with_singly_linked_list.py b/data_structures/stacks/stack_with_singly_linked_list.py
index 8e77c2b967ef..57a68679eee1 100644
--- a/data_structures/stacks/stack_with_singly_linked_list.py
+++ b/data_structures/stacks/stack_with_singly_linked_list.py
@@ -3,12 +3,12 @@
 from __future__ import annotations
 
 from collections.abc import Iterator
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class Node(Generic[T]):
+class Node[T]:
     def __init__(self, data: T):
         self.data = data
         self.next: Node[T] | None = None
@@ -17,7 +17,7 @@ def __str__(self) -> str:
         return f"{self.data}"
 
 
-class LinkedStack(Generic[T]):
+class LinkedStack[T]:
     """
     Linked List Stack implementing push (to top),
     pop (from top) and is_empty
diff --git a/graphs/graph_adjacency_list.py b/graphs/graph_adjacency_list.py
index abc75311cd60..244e59e0e1bf 100644
--- a/graphs/graph_adjacency_list.py
+++ b/graphs/graph_adjacency_list.py
@@ -21,14 +21,14 @@
 import random
 import unittest
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 import pytest
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyList(Generic[T]):
+class GraphAdjacencyList[T]:
     def __init__(
         self, vertices: list[T], edges: list[list[T]], directed: bool = True
     ) -> None:
diff --git a/graphs/graph_adjacency_matrix.py b/graphs/graph_adjacency_matrix.py
index 568c84166e4b..8eeeae786513 100644
--- a/graphs/graph_adjacency_matrix.py
+++ b/graphs/graph_adjacency_matrix.py
@@ -21,14 +21,14 @@
 import random
 import unittest
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 import pytest
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyMatrix(Generic[T]):
+class GraphAdjacencyMatrix[T]:
     def __init__(
         self, vertices: list[T], edges: list[list[T]], directed: bool = True
     ) -> None:
diff --git a/graphs/graph_list.py b/graphs/graph_list.py
index 6563cbb76132..6b63590bf55a 100644
--- a/graphs/graph_list.py
+++ b/graphs/graph_list.py
@@ -6,12 +6,12 @@
 from __future__ import annotations
 
 from pprint import pformat
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class GraphAdjacencyList(Generic[T]):
+class GraphAdjacencyList[T]:
     """
     Adjacency List type Graph Data Structure that accounts for directed and undirected
     Graphs.  Initialize graph object indicating whether it's directed or undirected.
diff --git a/graphs/minimum_spanning_tree_kruskal2.py b/graphs/minimum_spanning_tree_kruskal2.py
index 0ddb43ce8e6e..1f6d7255683b 100644
--- a/graphs/minimum_spanning_tree_kruskal2.py
+++ b/graphs/minimum_spanning_tree_kruskal2.py
@@ -1,11 +1,11 @@
 from __future__ import annotations
 
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class DisjointSetTreeNode(Generic[T]):
+class DisjointSetTreeNode[T]:
     # Disjoint Set Node to store the parent and rank
     def __init__(self, data: T) -> None:
         self.data = data
@@ -13,7 +13,7 @@ def __init__(self, data: T) -> None:
         self.rank = 0
 
 
-class DisjointSetTree(Generic[T]):
+class DisjointSetTree[T]:
     # Disjoint Set DataStructure
     def __init__(self) -> None:
         # map from node name to the node object
@@ -46,7 +46,7 @@ def union(self, data1: T, data2: T) -> None:
         self.link(self.find_set(data1), self.find_set(data2))
 
 
-class GraphUndirectedWeighted(Generic[T]):
+class GraphUndirectedWeighted[T]:
     def __init__(self) -> None:
         # connections: map from the node to the neighbouring nodes (with weights)
         self.connections: dict[T, dict[T, int]] = {}
diff --git a/graphs/minimum_spanning_tree_prims2.py b/graphs/minimum_spanning_tree_prims2.py
index 6870cc80f844..d22111289a48 100644
--- a/graphs/minimum_spanning_tree_prims2.py
+++ b/graphs/minimum_spanning_tree_prims2.py
@@ -10,7 +10,7 @@
 from __future__ import annotations
 
 from sys import maxsize
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
@@ -47,7 +47,7 @@ def get_child_right_position(position: int) -> int:
     return (2 * position) + 2
 
 
-class MinPriorityQueue(Generic[T]):
+class MinPriorityQueue[T]:
     """
     Minimum Priority Queue Class
 
@@ -184,7 +184,7 @@ def _swap_nodes(self, node1_pos: int, node2_pos: int) -> None:
         self.position_map[node2_elem] = node1_pos
 
 
-class GraphUndirectedWeighted(Generic[T]):
+class GraphUndirectedWeighted[T]:
     """
     Graph Undirected Weighted Class
 
@@ -217,7 +217,7 @@ def add_edge(self, node1: T, node2: T, weight: int) -> None:
         self.connections[node2][node1] = weight
 
 
-def prims_algo(
+def prims_algo[T](
     graph: GraphUndirectedWeighted[T],
 ) -> tuple[dict[T, int], dict[T, T | None]]:
     """
diff --git a/matrix/matrix_class.py b/matrix/matrix_class.py
index a5940a38e836..dee9247282f9 100644
--- a/matrix/matrix_class.py
+++ b/matrix/matrix_class.py
@@ -260,7 +260,7 @@ def add_row(self, row: list[int], position: int | None = None) -> None:
         if position is None:
             self.rows.append(row)
         else:
-            self.rows = self.rows[0:position] + [row] + self.rows[position:]
+            self.rows = [*self.rows[0:position], row, *self.rows[position:]]
 
     def add_column(self, column: list[int], position: int | None = None) -> None:
         type_error = TypeError(
@@ -279,7 +279,7 @@ def add_column(self, column: list[int], position: int | None = None) -> None:
             self.rows = [self.rows[i] + [column[i]] for i in range(self.num_rows)]
         else:
             self.rows = [
-                self.rows[i][0:position] + [column[i]] + self.rows[i][position:]
+                [*self.rows[i][0:position], column[i], *self.rows[i][position:]]
                 for i in range(self.num_rows)
             ]
 
diff --git a/other/least_recently_used.py b/other/least_recently_used.py
index cb692bb1b1c0..d96960868488 100644
--- a/other/least_recently_used.py
+++ b/other/least_recently_used.py
@@ -2,12 +2,12 @@
 
 import sys
 from collections import deque
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 
 
-class LRUCache(Generic[T]):
+class LRUCache[T]:
     """
     Page Replacement Algorithm, Least Recently Used (LRU) Caching.
 
diff --git a/other/lfu_cache.py b/other/lfu_cache.py
index 5a143c739b9d..6eaacff2966a 100644
--- a/other/lfu_cache.py
+++ b/other/lfu_cache.py
@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 U = TypeVar("U")
 
 
-class DoubleLinkedListNode(Generic[T, U]):
+class DoubleLinkedListNode[T, U]:
     """
     Double Linked List Node built specifically for LFU Cache
 
@@ -30,7 +30,7 @@ def __repr__(self) -> str:
         )
 
 
-class DoubleLinkedList(Generic[T, U]):
+class DoubleLinkedList[T, U]:
     """
     Double Linked List built specifically for LFU Cache
 
@@ -161,7 +161,7 @@ def remove(
         return node
 
 
-class LFUCache(Generic[T, U]):
+class LFUCache[T, U]:
     """
     LFU Cache to store a given capacity of data. Can be used as a stand-alone object
     or as a function decorator.
diff --git a/other/lru_cache.py b/other/lru_cache.py
index 4f0c843c86cc..058b03b021bc 100644
--- a/other/lru_cache.py
+++ b/other/lru_cache.py
@@ -1,13 +1,13 @@
 from __future__ import annotations
 
 from collections.abc import Callable
-from typing import Generic, TypeVar
+from typing import TypeVar
 
 T = TypeVar("T")
 U = TypeVar("U")
 
 
-class DoubleLinkedListNode(Generic[T, U]):
+class DoubleLinkedListNode[T, U]:
     """
     Double Linked List Node built specifically for LRU Cache
 
@@ -28,7 +28,7 @@ def __repr__(self) -> str:
         )
 
 
-class DoubleLinkedList(Generic[T, U]):
+class DoubleLinkedList[T, U]:
     """
     Double Linked List built specifically for LRU Cache
 
@@ -143,7 +143,7 @@ def remove(
         return node
 
 
-class LRUCache(Generic[T, U]):
+class LRUCache[T, U]:
     """
     LRU Cache to store a given capacity of data. Can be used as a stand-alone object
     or as a function decorator.
diff --git a/pyproject.toml b/pyproject.toml
index 2ead5cd51ae8..b680cc0d439e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -112,11 +112,14 @@ lint.ignore = [
   "EXE001",  # Shebang is present but file is not executable -- DO NOT FIX
   "G004",    # Logging statement uses f-string
   "ISC001",  # Conflicts with ruff format -- DO NOT FIX
+  "PLC0415", # import-outside-top-level -- DO NOT FIX
   "PLC1901", # `{}` can be simplified to `{}` as an empty string is falsey
   "PLW060",  # Using global for `{name}` but no assignment is done -- DO NOT FIX
+  "PLW1641", # eq-without-hash
   "PLW2901", # PLW2901: Redefined loop variable -- FIX ME
   "PT011",   # `pytest.raises(Exception)` is too broad, set the `match` parameter or use a more specific exception
   "PT018",   # Assertion should be broken down into multiple parts
+  "PT028",   # pytest-parameter-with-default-argument
   "S101",    # Use of `assert` detected -- DO NOT FIX
   "S311",    # Standard pseudo-random generators are not suitable for cryptographic purposes -- FIX ME
   "SIM905",  # Consider using a list literal instead of `str.split` -- DO NOT FIX
diff --git a/sorts/insertion_sort.py b/sorts/insertion_sort.py
index 46b263d84a33..2e39be255df7 100644
--- a/sorts/insertion_sort.py
+++ b/sorts/insertion_sort.py
@@ -24,7 +24,7 @@ def __lt__(self, other: Any, /) -> bool: ...
 T = TypeVar("T", bound=Comparable)
 
 
-def insertion_sort(collection: MutableSequence[T]) -> MutableSequence[T]:
+def insertion_sort[T: Comparable](collection: MutableSequence[T]) -> MutableSequence[T]:
     """A pure Python implementation of the insertion sort algorithm
 
     :param collection: some mutable ordered collection with heterogeneous
diff --git a/sorts/tim_sort.py b/sorts/tim_sort.py
index 138f11c71bcc..41ab4a10a87b 100644
--- a/sorts/tim_sort.py
+++ b/sorts/tim_sort.py
@@ -19,7 +19,7 @@ def insertion_sort(lst):
     for index in range(1, length):
         value = lst[index]
         pos = binary_search(lst, value, 0, index - 1)
-        lst = lst[:pos] + [value] + lst[pos:index] + lst[index + 1 :]
+        lst = [*lst[:pos], value, *lst[pos:index], *lst[index + 1 :]]
 
     return lst
 

From 5c05335b27c7f072b35fd9028d81433939054628 Mon Sep 17 00:00:00 2001
From: Christian Clauss <cclauss@me.com>
Date: Mon, 7 Jul 2025 10:28:59 +0200
Subject: [PATCH 127/135] hill_cipher.py: Use `int()` to gracefully deal with
 float input (#12833)

* https://github.com/TheAlgorithms/Python/pull/12821#discussion_r2186998710
---
 ciphers/hill_cipher.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py
index 19422688cd70..c690d29bd113 100644
--- a/ciphers/hill_cipher.py
+++ b/ciphers/hill_cipher.py
@@ -78,8 +78,10 @@ def replace_digits(self, num: int) -> str:
         'T'
         >>> hill_cipher.replace_digits(26)
         '0'
+        >>> hill_cipher.replace_digits(26.1)
+        '0'
         """
-        return self.key_string[(num)]
+        return self.key_string[int(num)]
 
     def check_determinant(self) -> None:
         """

From ed7c9b51f940307bfbec79e9cc4587e025e657c5 Mon Sep 17 00:00:00 2001
From: Bertrand Awenze <156874668+bertrand-awz@users.noreply.github.com>
Date: Wed, 9 Jul 2025 08:25:18 -0400
Subject: [PATCH 128/135] Fix devcontainer build failure (#12837)

* add post-installation script

* set zsh as default terminal and make post-install execution on postcreate

* rename file

* rename script

* Create devcontainers_ci.yml

* Update devcontainer.json

* Update devcontainers_ci.yml

Never push image built here. It's for build testing purpose only.

* postCreateCommand update to reflect that the shell script has been renamed.

* update devcontainer readme file

* trigger workflow only on devcontainer/** changes

* prettier refactor
---
 .devcontainer/Dockerfile              |  5 ++--
 .devcontainer/README.md               | 43 ++++++++++++++++++++++++++-
 .devcontainer/devcontainer.json       |  7 +++--
 .devcontainer/post_install            | 29 ++++++++++++++++++
 .github/workflows/devcontainer_ci.yml | 19 ++++++++++++
 5 files changed, 97 insertions(+), 6 deletions(-)
 create mode 100755 .devcontainer/post_install
 create mode 100644 .github/workflows/devcontainer_ci.yml

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index a0bd05f47ec8..edee3bc4febb 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -3,6 +3,5 @@ ARG VARIANT=3.13-bookworm
 FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
 COPY requirements.txt /tmp/pip-tmp/
 RUN python3 -m pip install --upgrade pip \
-  && python3 -m pip install --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
-  && pipx install pre-commit ruff \
-  && pre-commit install
+  && python3 -m pip install --no-cache-dir -r /tmp/pip-tmp/requirements.txt \
+  && pipx install pre-commit ruff
diff --git a/.devcontainer/README.md b/.devcontainer/README.md
index ec3cdb61de7a..8056578ad3a8 100644
--- a/.devcontainer/README.md
+++ b/.devcontainer/README.md
@@ -1 +1,42 @@
-https://code.visualstudio.com/docs/devcontainers/tutorial
+# Development Container
+
+This is **Devcontainer** configuration to provide a consistent development environment for all contributors.
+
+## Features
+
+- [x] Pre-configured **Python environment**
+- [x] Automatic installation of **pre-commit hooks**
+- [x] **Ruff** linter ready to check your code
+- [x] **Oh My Zsh** with plugins:
+- `zsh-autosuggestions`
+- `zsh-syntax-highlighting`
+
+## Usage
+
+1. Install [**Docker** ](https://www.docker.com/get-started/) and [**Visual Studio Code**](https://code.visualstudio.com/)
+2. Install the **Remote - Containers** extension in VS Code
+
+    - Do `CTRL+P`, paste this command and press `Enter`
+
+        ```shell
+        ext install ms-vscode-remote.remote-containers
+        ```
+3. Open this repository in VS Code
+4. When prompted, click **"Reopen in Container"**
+5. Wait for the environment to build and initialize
+
+After setup:
+
+- `pre-commit` hooks are installed
+- `ruff` and other tools are available
+- The shell uses Zsh by default
+
+## Tips
+
+To manually run checks on all files:
+
+```bash
+pre-commit run --all-files
+```
+
+> For further information here's [Microsoft tutorial about devcontainers.](https://code.visualstudio.com/docs/devcontainers/tutorial)
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index e23263f5b9de..4951d5eb268d 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -7,10 +7,12 @@
 			// Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8
 			// Append -bullseye or -buster to pin to an OS version.
 			// Use -bullseye variants on local on arm64/Apple Silicon.
-			"VARIANT": "3.13-bookworm",
+			"VARIANT": "3.13-bookworm"
 		}
 	},
 
+	"postCreateCommand": "zsh .devcontainer/post_install",
+
 	// Configure tool-specific properties.
 	"customizations": {
 		// Configure properties specific to VS Code.
@@ -20,7 +22,8 @@
 				"python.defaultInterpreterPath": "/usr/local/bin/python",
 				"python.linting.enabled": true,
 				"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
-				"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy"
+				"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
+				"terminal.integrated.defaultProfile.linux": "zsh"
 			},
 
 			// Add the IDs of extensions you want installed when the container is created.
diff --git a/.devcontainer/post_install b/.devcontainer/post_install
new file mode 100755
index 000000000000..589ee361f5cb
--- /dev/null
+++ b/.devcontainer/post_install
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+echo "Begin post-installation steps..."
+
+set -e
+
+echo "Installing pre-commit hooks..."
+pre-commit install
+
+echo "Installing Oh My Zsh plugins..."
+
+# Install zsh-autosuggestions if not present
+if [ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions" ]; then
+  echo "Cloning zsh-autosuggestions..."
+  git clone https://github.com/zsh-users/zsh-autosuggestions \
+    "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
+fi
+
+# Install zsh-syntax-highlighting if not present
+if [ ! -d "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting" ]; then
+  echo "Cloning zsh-syntax-highlighting..."
+  git clone https://github.com/zsh-users/zsh-syntax-highlighting.git \
+    "${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting"
+fi
+
+echo "Configuring plugins in ~/.zshrc..."
+sed -i '/^plugins=/c\plugins=(git zsh-autosuggestions zsh-syntax-highlighting)' ~/.zshrc
+
+echo "Post-installation steps completed successfully. Enjoy!"
diff --git a/.github/workflows/devcontainer_ci.yml b/.github/workflows/devcontainer_ci.yml
new file mode 100644
index 000000000000..c0b26bb77da6
--- /dev/null
+++ b/.github/workflows/devcontainer_ci.yml
@@ -0,0 +1,19 @@
+name: Test DevContainer Build
+
+on:
+  push:
+    paths:
+      - ".devcontainer/**"
+  pull_request:
+    paths:
+      - ".devcontainer/**"
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: devcontainers/ci@v0.3
+        with:
+          push: never
+          runCmd: "true"

From 5a4a6a5497776c564774fa915792924eee36865d Mon Sep 17 00:00:00 2001
From: Asim Hanif <asimjutt2003@gmail.com>
Date: Sat, 12 Jul 2025 01:25:53 +0500
Subject: [PATCH 129/135] =?UTF-8?q?Update=20.pre-commit-config.yaml=20to?=
 =?UTF-8?q?=20reflect=20current=20linting=20and=20formatt=E2=80=A6=20(#128?=
 =?UTF-8?q?41)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Update .pre-commit-config.yaml to reflect current linting and formatting setup

- Replaced outdated references to `black` with the actual tools used in the repo: `ruff` and `ruff-format`.
- Ensured all configured hooks are up to date and relevant to Python development.
- This aligns the linting and formatting setup with the project's actual pre-commit pipeline.
- Improves consistency for contributors by preventing confusion between formatting tools.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* We use httpx, not requests

* response = httpx.get(request_url, timeout=10).raise_for_status()

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml                |  9 ++++---
 web_programming/fetch_well_rx_price.py | 33 +++++++++++---------------
 2 files changed, 18 insertions(+), 24 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6c1879ab1ac6..969328415be6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,7 +18,7 @@ repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
     rev: v0.11.11
     hooks:
-      - id: ruff
+      - id: ruff-check
       - id: ruff-format
 
   - repo: https://github.com/codespell-project/codespell
@@ -29,7 +29,7 @@ repos:
           - tomli
 
   - repo: https://github.com/tox-dev/pyproject-fmt
-    rev: "v2.6.0"
+    rev: v2.6.0
     hooks:
       - id: pyproject-fmt
 
@@ -53,12 +53,11 @@ repos:
         args:
           - --explicit-package-bases
           - --ignore-missing-imports
-          - --install-types # See mirrors-mypy README.md
+          - --install-types
           - --non-interactive
-        additional_dependencies: [types-requests]
 
   - repo: https://github.com/pre-commit/mirrors-prettier
-    rev: "v4.0.0-alpha.8"
+    rev: v4.0.0-alpha.8
     hooks:
       - id: prettier
         types_or: [toml, yaml]
diff --git a/web_programming/fetch_well_rx_price.py b/web_programming/fetch_well_rx_price.py
index 93be2a9235d9..e34a89c19cc8 100644
--- a/web_programming/fetch_well_rx_price.py
+++ b/web_programming/fetch_well_rx_price.py
@@ -5,12 +5,10 @@
 
 """
 
-from urllib.error import HTTPError
-
+import httpx
 from bs4 import BeautifulSoup
-from requests import exceptions, get
 
-BASE_URL = "https://www.wellrx.com/prescriptions/{0}/{1}/?freshSearch=true"
+BASE_URL = "https://www.wellrx.com/prescriptions/{}/{}/?freshSearch=true"
 
 
 def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
@@ -18,8 +16,8 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
 
     This function will take input of drug name and zipcode,
     then request to the BASE_URL site.
-    Get the page data and scrape it to the generate the
-    list of lowest prices for the prescription drug.
+    Get the page data and scrape it to generate the
+    list of the lowest prices for the prescription drug.
 
     Args:
         drug_name (str): [Drug name]
@@ -28,12 +26,12 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
     Returns:
         list: [List of pharmacy name and price]
 
-    >>> fetch_pharmacy_and_price_list(None, None)
-
-    >>> fetch_pharmacy_and_price_list(None, 30303)
-
-    >>> fetch_pharmacy_and_price_list("eliquis", None)
-
+    >>> print(fetch_pharmacy_and_price_list(None, None))
+    None
+    >>> print(fetch_pharmacy_and_price_list(None, 30303))
+    None
+    >>> print(fetch_pharmacy_and_price_list("eliquis", None))
+    None
     """
 
     try:
@@ -42,10 +40,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
             return None
 
         request_url = BASE_URL.format(drug_name, zip_code)
-        response = get(request_url, timeout=10)
-
-        # Is the response ok?
-        response.raise_for_status()
+        response = httpx.get(request_url, timeout=10).raise_for_status()
 
         # Scrape the data using bs4
         soup = BeautifulSoup(response.text, "html.parser")
@@ -53,14 +48,14 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
         # This list will store the name and price.
         pharmacy_price_list = []
 
-        # Fetch all the grids that contains the items.
+        # Fetch all the grids that contain the items.
         grid_list = soup.find_all("div", {"class": "grid-x pharmCard"})
         if grid_list and len(grid_list) > 0:
             for grid in grid_list:
                 # Get the pharmacy price.
                 pharmacy_name = grid.find("p", {"class": "list-title"}).text
 
-                # Get price of the drug.
+                # Get the price of the drug.
                 price = grid.find("span", {"p", "price price-large"}).text
 
                 pharmacy_price_list.append(
@@ -72,7 +67,7 @@ def fetch_pharmacy_and_price_list(drug_name: str, zip_code: str) -> list | None:
 
         return pharmacy_price_list
 
-    except (HTTPError, exceptions.RequestException, ValueError):
+    except (httpx.HTTPError, ValueError):
         return None
 
 

From d1a9486f09f47a14ea6bfdfcfaf61c0297476d84 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Fri, 11 Jul 2025 23:06:49 +0200
Subject: [PATCH 130/135] [pre-commit.ci] pre-commit autoupdate (#12781)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.11.11 → v0.12.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.11.11...v0.12.2)
- [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.16.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.16.1)

* Update .pre-commit-config.yaml

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 969328415be6..b7865145fce9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.11.11
+    rev: v0.12.3
     hooks:
       - id: ruff-check
       - id: ruff-format

From 6e1a1048774468ab2d7717ceae34b43f005dce2d Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 21 Jul 2025 15:29:24 -0400
Subject: [PATCH 131/135] [pre-commit.ci] pre-commit autoupdate (#12846)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.12.3 → v0.12.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.3...v0.12.4)
- [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.17.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.17.0)

* Apply suggestion from @cclauss

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b7865145fce9..f733908c2987 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.12.3
+    rev: v0.12.4
     hooks:
       - id: ruff-check
       - id: ruff-format

From 7a0fee401d2990bd5904e94cd5d3a62c50a8b682 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 29 Jul 2025 17:27:27 -0400
Subject: [PATCH 132/135] [pre-commit.ci] pre-commit autoupdate (#12864)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.12.4 → v0.12.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.4...v0.12.5)
- [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.17.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.17.0)

* Apply suggestion from @cclauss

* --ignore=web_programming/current_stock_price.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .github/workflows/build.yml | 3 ++-
 .pre-commit-config.yaml     | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8b83cb41c79a..01b67c6de05b 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -16,7 +16,7 @@ jobs:
           cache-dependency-glob: uv.lock
       - uses: actions/setup-python@v5
         with:
-          python-version: 3.13
+          python-version: 3.x
           allow-prereleases: true
       - run: uv sync --group=test
       - name: Run tests
@@ -30,6 +30,7 @@ jobs:
           --ignore=project_euler/
           --ignore=quantum/q_fourier_transform.py
           --ignore=scripts/validate_solutions.py
+          --ignore=web_programming/current_stock_price.py
           --ignore=web_programming/fetch_anime_and_play.py
           --cov-report=term-missing:skip-covered
           --cov=. .
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f733908c2987..d52c31c42592 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.12.4
+    rev: v0.12.5
     hooks:
       - id: ruff-check
       - id: ruff-format

From d05f5d22d92424f1197950d519fc7a2059de4a5a Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 11 Aug 2025 21:03:20 +0200
Subject: [PATCH 133/135] [pre-commit.ci] pre-commit autoupdate (#12880)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/pre-commit/pre-commit-hooks: v5.0.0 → v6.0.0](https://github.com/pre-commit/pre-commit-hooks/compare/v5.0.0...v6.0.0)
- [github.com/astral-sh/ruff-pre-commit: v0.12.5 → v0.12.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.5...v0.12.8)
- [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.17.1)

* Apply suggestion from @cclauss

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d52c31c42592..594489d87c76 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v5.0.0
+    rev: v6.0.0
     hooks:
       - id: check-executables-have-shebangs
       - id: check-toml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.12.5
+    rev: v0.12.8
     hooks:
       - id: ruff-check
       - id: ruff-format

From f662b63d1ad958d6f14a25136979f3ebdabb987a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 12 Aug 2025 09:30:10 +0200
Subject: [PATCH 134/135] Bump actions/checkout from 4 to 5 (#12891)

Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 .github/workflows/build.yml            | 2 +-
 .github/workflows/devcontainer_ci.yml  | 2 +-
 .github/workflows/directory_writer.yml | 2 +-
 .github/workflows/project_euler.yml    | 4 ++--
 .github/workflows/ruff.yml             | 2 +-
 .github/workflows/sphinx.yml           | 2 +-
 6 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 01b67c6de05b..69192db0c4c6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,7 +9,7 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
         with:
           enable-cache: true
diff --git a/.github/workflows/devcontainer_ci.yml b/.github/workflows/devcontainer_ci.yml
index c0b26bb77da6..71623e5e6e69 100644
--- a/.github/workflows/devcontainer_ci.yml
+++ b/.github/workflows/devcontainer_ci.yml
@@ -12,7 +12,7 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: devcontainers/ci@v0.3
         with:
           push: never
diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml
index 3edb5c91a951..f5167f8d1a58 100644
--- a/.github/workflows/directory_writer.yml
+++ b/.github/workflows/directory_writer.yml
@@ -6,7 +6,7 @@ jobs:
   directory_writer:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
         with:
           fetch-depth: 0
       - uses: actions/setup-python@v5
diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml
index eaf4150e4eaa..8b8cb2a1e68f 100644
--- a/.github/workflows/project_euler.yml
+++ b/.github/workflows/project_euler.yml
@@ -14,7 +14,7 @@ jobs:
   project-euler:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
@@ -24,7 +24,7 @@ jobs:
   validate-solutions:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:
diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml
index ec9f0202bd7e..7bcc2850782f 100644
--- a/.github/workflows/ruff.yml
+++ b/.github/workflows/ruff.yml
@@ -11,6 +11,6 @@ jobs:
   ruff:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - run: uvx ruff check --output-format=github .
diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml
index 2010041d80c5..a5502a586297 100644
--- a/.github/workflows/sphinx.yml
+++ b/.github/workflows/sphinx.yml
@@ -25,7 +25,7 @@ jobs:
   build_docs:
     runs-on: ubuntu-24.04-arm
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v5
       - uses: astral-sh/setup-uv@v6
       - uses: actions/setup-python@v5
         with:

From beb3cfdf283f68545364aedf34dbbb843ffa95b7 Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
 <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Tue, 19 Aug 2025 14:28:03 +0200
Subject: [PATCH 135/135] [pre-commit.ci] pre-commit autoupdate (#12900)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.12.8 → v0.12.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.12.8...v0.12.9)
- [github.com/pre-commit/mirrors-mypy: v1.15.0 → v1.17.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.15.0...v1.17.1)

* Apply suggestion from @cclauss

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
---
 .pre-commit-config.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 594489d87c76..252cfebc53a8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -16,7 +16,7 @@ repos:
       - id: auto-walrus
 
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.12.8
+    rev: v0.12.9
     hooks:
       - id: ruff-check
       - id: ruff-format
