diff --git a/poetry.lock b/poetry.lock index b6b06d0..3eb9d14 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand. [[package]] name = "ag2" @@ -403,6 +403,47 @@ files = [ {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, ] +[[package]] +name = "arch" +version = "6.3.0" +description = "ARCH for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "arch-6.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:179ea15e96d1aafdf88c470f9d4cbda867e5a379b629dde282c1d85691d66598"}, + {file = "arch-6.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecc23e58836c12073f105c4bd78efd3fb1caf335b0d3d80f5914fe01a6eaa3fc"}, + {file = "arch-6.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5573b4f9331993019b8fcbb2e6edf249e17a9a5f27a02c0fc2c07a1a41da9f3b"}, + {file = "arch-6.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cf2ddeb076e276d0f350449253d578c59df8170738727a2b5171ae9d98784b"}, + {file = "arch-6.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5446d681555419cb0739006c706c69435975e93e5104a8cf114bdea88a3e841a"}, + {file = "arch-6.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:92997633de472d2d1c142262d947bc4b6e7ad50e73686695cf3767942b6a6a2f"}, + {file = "arch-6.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:af144ec65d6a99cc1a5c06327cf1c957de7a6936e84bd4b3d7604cc0084b2f02"}, + {file = "arch-6.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a53c8b1d8f6237ebe4718451b1ef6fc5738a124e0454bdd6fabf22bdbe570da"}, + {file = "arch-6.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:830add5723f7491226f4a58e97c84b7bfa584255581d8f9c624fa97de5f7686d"}, + {file = "arch-6.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1488ab7b4f15a071581e3ea2bc5199af4586558e414da539a62919ae2bcc11c"}, + {file = "arch-6.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5aa8d428f6147e6f22343673c54ea4846968a5e8d37f35b39d824a0a0bf115ee"}, + {file = "arch-6.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:844ab59b7fb24cd65ada9491e6e2423a6dac76f0b53e1350ec469efeb45cf617"}, + {file = "arch-6.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f406b1d2b9e07cb233779f7f737541695c04ae07caea26082c028fbe6766ca6"}, + {file = "arch-6.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c67187d2bfa66c3a4e2bd67423780e88f63fc5413bf97adf765b034c8226b969"}, + {file = "arch-6.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82874a0831fa13a28b3d3eb934a61ca26fd92b2e37a716147601b6d0a1292c9a"}, + {file = "arch-6.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:573913e6ee2ab703dcda2bba73ebc20dd6f4ab2622e640ac1e50367a5a99449c"}, + {file = "arch-6.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:35b4e480d6241691cce591950f99d688e6866761e8946e68eaae8bb4f73db00c"}, + {file = "arch-6.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:10bad48b79059ae3661c7d744e0aea3c857211c1b447bdc466fd54664c2079e1"}, + {file = "arch-6.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0b82d4c1c6012d5f6ce80ba85020db281658ada0af448e18fa4b5424a0e2f7c2"}, + {file = "arch-6.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ddf235084f8ef7b396ff4eb259a03759c34daec7ab2c777d3fe7271418518e"}, + {file = "arch-6.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88c61d8909971110427007fba5021c1f5fcc7cac386ca961d38a6501b76ff207"}, + {file = "arch-6.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c1138b80043b3179ad2fe66127fa799c5c19759c15284d70945736463ea732"}, + {file = "arch-6.3.0-cp39-cp39-win32.whl", hash = "sha256:c36b19010644dd42830fbd6bbe03f961270fb9e4af4afae24d38c1685b6e3e24"}, + {file = "arch-6.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:21487863371d4d36d202f06974db5095b83339c10faeec0e67f1acc7cf7d22dc"}, + {file = "arch-6.3.0.tar.gz", hash = "sha256:6c56d8cb8a530723c68cb432bb9839490272c90614264d4ec241fe56b1fd96b7"}, +] + +[package.dependencies] +numpy = ">=1.19" +pandas = ">=1.1" +scipy = ">=1.5" +statsmodels = ">=0.12" + [[package]] name = "asgiref" version = "3.9.1" @@ -622,6 +663,19 @@ files = [ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, ] +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +description = "Backport of asyncio.Runner, a context manager that controls event loop life cycle." +optional = false +python-versions = "<3.11,>=3.8" +groups = ["test"] +markers = "python_version == \"3.10\"" +files = [ + {file = "backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5"}, + {file = "backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162"}, +] + [[package]] name = "beautifulsoup4" version = "4.13.5" @@ -836,8 +890,7 @@ version = "2.0.0" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.9" -groups = ["docs"] -markers = "implementation_name == \"pypy\"" +groups = ["main", "docs"] files = [ {file = "cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44"}, {file = "cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49"}, @@ -924,6 +977,7 @@ files = [ {file = "cffi-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:b882b3df248017dba09d6b16defe9b5c407fe32fc7c65a9c69798e6175601be9"}, {file = "cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529"}, ] +markers = {docs = "implementation_name == \"pypy\""} [package.dependencies] pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} @@ -1029,6 +1083,27 @@ files = [ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, ] +[[package]] +name = "clarabel" +version = "0.11.1" +description = "Clarabel Conic Interior Point Solver for Rust / Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "clarabel-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c39160e4222040f051f2a0598691c4f9126b4d17f5b9e7678f76c71d611e12d8"}, + {file = "clarabel-0.11.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8963687ee250d27310d139eea5a6816f9c3ae31f33691b56579ca4f0f0b64b63"}, + {file = "clarabel-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4837b9d0db01e98239f04b1e3526a6cf568529d3c19a8b3f591befdc467f9bb"}, + {file = "clarabel-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8c41aaa6f3f8c0f3bd9d86c3e568dcaee079562c075bd2ec9fb3a80287380ef"}, + {file = "clarabel-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:557d5148a4377ae1980b65d00605ae870a8f34f95f0f6a41e04aa6d3edf67148"}, + {file = "clarabel-0.11.1.tar.gz", hash = "sha256:e7c41c47f0e59aeab99aefff9e58af4a8753ee5269bbeecbd5526fc6f41b9598"}, +] + +[package.dependencies] +cffi = "*" +numpy = "*" +scipy = "*" + [[package]] name = "click" version = "8.2.1" @@ -1363,6 +1438,73 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli ; python_full_version <= \"3.11.0a6\""] +[[package]] +name = "cvxpy" +version = "1.7.5" +description = "A domain-specific language for modeling convex optimization problems in Python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "cvxpy-1.7.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a9938ea90898da51b1129ba9c185cd774d83fdbea3eb0099cd86d47e37ed5297"}, + {file = "cvxpy-1.7.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f0a4818665c3231a5a35001c41f691471b35e2231295f85ddf6044f3982f2f88"}, + {file = "cvxpy-1.7.5-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd50c29539fb39cc53de93a689e73019cd26c1b80fc29aba7a63cc0ae5ec7b01"}, + {file = "cvxpy-1.7.5-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c05116b9633747857758ca105f2744a9c27bb9dbed771087e5712c4405f2517"}, + {file = "cvxpy-1.7.5-cp310-cp310-win_amd64.whl", hash = "sha256:3207a3cf7360d176fe7f1dfe172846d7a3befd9b1db604c0082e4fa242373aff"}, + {file = "cvxpy-1.7.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0df3bc1aee0431ee6419cfc77fb7543ad7588150b9bb5d8ef44da7a76770ba1d"}, + {file = "cvxpy-1.7.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86876084d1874c837b6dc9dad61ba1e873e979d06462fdc149a6ba0b067a8638"}, + {file = "cvxpy-1.7.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7633c2a369188aa0fa3df4a767267774257c9dba71ac8e5b9e8eefb17e2613f8"}, + {file = "cvxpy-1.7.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f9d93892f0805a9fa1b0702ca4c6d3b8deb056ab0140a58f41b933fe8f28aae"}, + {file = "cvxpy-1.7.5-cp311-cp311-win_amd64.whl", hash = "sha256:911575f28ecd3fd913165354aad24ebfe264a59a1d86a2c0e296177c6a13092f"}, + {file = "cvxpy-1.7.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6c397b86ef2109b99ec10d4fb144a826af840e1111167d307c52c96719ac5f57"}, + {file = "cvxpy-1.7.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:20bacc1781b5b168e0272688d8652cef7433a4d07dea2482e790e1bdcee4f46e"}, + {file = "cvxpy-1.7.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:573396b116cff9c46952c885d9c06db1fc7a6e4838feb2fcba2982d521140205"}, + {file = "cvxpy-1.7.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5926ca62e6998f160ecf4c4acc139eb0fe8c28453c904e1c3d7b93b5b40e4303"}, + {file = "cvxpy-1.7.5-cp312-cp312-win_amd64.whl", hash = "sha256:e8308b88b515567d7a5a5762c8e7c971692e1022a924613d808648916c20834b"}, + {file = "cvxpy-1.7.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:56718a649e7d7c593becb1d088d7c1c0f073df821e20baead80e3662a083a34f"}, + {file = "cvxpy-1.7.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ca12e393acd83973ec56b5ac9194db403a4f99af451d4ea041f27b3e432acd8d"}, + {file = "cvxpy-1.7.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3d4b7498a1419689566fa6e20d9c5479c384ca950ee7403c51e70425059aa5"}, + {file = "cvxpy-1.7.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ed867017ebe3c6bf2e34aa108208237eb9d655b9897687af8c98ed282f7004"}, + {file = "cvxpy-1.7.5-cp313-cp313-win_amd64.whl", hash = "sha256:d71688a5725ee61666cc9cf456f048d0016ae96c206c1030af06f3ad803b5d22"}, + {file = "cvxpy-1.7.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de23fad688520f099c476e70917a28e9162d58496c9f12d29bde01eb58b0d2e2"}, + {file = "cvxpy-1.7.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e416efb52ff89e2dffa2079ccca8034b59f27d5414cf92674d89bfb89a6a61ad"}, + {file = "cvxpy-1.7.5-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:806d9f435a062cb05dfb63812738d973ce209e58df72fa424cf9bbae5320996e"}, + {file = "cvxpy-1.7.5-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ad9e26897584b441c95ea824a0b6fc0f0ffd2260c1435e3c1f1183c28817142"}, + {file = "cvxpy-1.7.5-cp39-cp39-win_amd64.whl", hash = "sha256:c570d240ba63c1c6dcc34a40c405e1057ae7faade64691a3f25ba8ca3b534cb1"}, + {file = "cvxpy-1.7.5.tar.gz", hash = "sha256:4b512218001c27659e16fc914a2490038635874681032c3c3485ff1099b83f5d"}, +] + +[package.dependencies] +clarabel = ">=0.5.0" +numpy = ">=1.22.4" +osqp = ">=1.0.0" +scipy = ">=1.13.0" +scs = ">=3.2.4.post1" + +[package.extras] +cbc = ["cylp (>=0.91.5)"] +cuopt = ["cuopt-cu12 (>=25.5)", "nvidia-cuda-runtime-cu12 (>=12.8,<13.0)"] +cvxopt = ["cvxopt"] +daqp = ["daqp"] +diffcp = ["diffcp"] +doc = ["sphinx", "sphinx-design", "sphinx-immaterial (>=0.11.7)", "sphinx-inline-tabs", "sphinxcontrib.jquery"] +ecos = ["ecos"] +ecos-bb = ["ecos"] +glop = ["ortools (>=9.7,<9.15)"] +glpk = ["cvxopt"] +glpk-mi = ["cvxopt"] +gurobi = ["gurobipy"] +highs = ["highspy"] +mosek = ["Mosek"] +pdlp = ["ortools (>=9.7,<9.15)"] +piqp = ["piqp"] +proxqp = ["proxsuite"] +qoco = ["qoco"] +scip = ["PySCIPOpt"] +scipy = ["scipy"] +testing = ["hypothesis", "pytest"] +xpress = ["xpress (>=9.5)"] + [[package]] name = "cycler" version = "0.12.1" @@ -1608,6 +1750,26 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] +[[package]] +name = "empyrical" +version = "0.5.5" +description = "empyrical is a Python library with performance and risk statistics commonly used in quantitative finance" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "empyrical-0.5.5.tar.gz", hash = "sha256:84320f36931496774e948d7ca9f03494ae2dd94451b320eeda378639e5eba950"}, +] + +[package.dependencies] +numpy = ">=1.9.2" +pandas = ">=0.16.1" +pandas-datareader = ">=0.2" +scipy = ">=0.15.1" + +[package.extras] +dev = ["flake8 (==2.5.1)", "nose (==1.3.7)", "parameterized (==0.6.1)"] + [[package]] name = "exceptiongroup" version = "1.3.0" @@ -2053,7 +2215,7 @@ files = [ beautifulsoup4 = "*" pygments = ">=2.7" sphinx = ">=6.0,<9.0" -sphinx-basic-ng = ">=1.0.0.beta2" +sphinx-basic-ng = ">=1.0.0b2" [[package]] name = "google-ai-generativelanguage" @@ -2146,11 +2308,11 @@ files = [ ] [package.dependencies] -google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0dev" -google-auth = ">=1.25.0,<3.0dev" +google-api-core = ">=1.31.6,<2.0.dev0 || >2.3.0,<3.0.0.dev0" +google-auth = ">=1.25.0,<3.0.dev0" [package.extras] -grpc = ["grpcio (>=1.38.0,<2.0dev)", "grpcio-status (>=1.38.0,<2.0.dev0)"] +grpc = ["grpcio (>=1.38.0,<2.0.dev0)", "grpcio-status (>=1.38.0,<2.0.dev0)"] [[package]] name = "google-cloud-storage" @@ -2262,11 +2424,11 @@ files = [ ] [package.dependencies] -google-crc32c = ">=1.0,<2.0dev" +google-crc32c = ">=1.0,<2.0.dev0" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] -requests = ["requests (>=2.18.0,<3.0.0dev)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "google-auth (>=1.22.0,<2.0.dev0)"] +requests = ["requests (>=2.18.0,<3.0.0.dev0)"] [[package]] name = "googleapis-common-protos" @@ -2488,7 +2650,7 @@ files = [ [package.dependencies] googleapis-common-protos = ">=1.5.5" grpcio = ">=1.71.2" -protobuf = ">=5.26.1,<6.0dev" +protobuf = ">=5.26.1,<6.0.dev0" [[package]] name = "h11" @@ -3210,7 +3372,7 @@ files = [ [package.dependencies] attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" +jsonschema-specifications = ">=2023.3.6" referencing = ">=0.28.4" rpds-py = ">=0.7.1" @@ -3676,6 +3838,162 @@ dev = ["black", "flake8", "isort", "pre-commit", "pyproject-flake8"] doc = ["myst-parser", "sphinx", "sphinx-book-theme"] test = ["coverage", "pytest", "pytest-cov"] +[[package]] +name = "lxml" +version = "6.0.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"}, + {file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"}, + {file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"}, + {file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"}, + {file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"}, + {file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"}, + {file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"}, + {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"}, + {file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"}, + {file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"}, + {file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"}, + {file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"}, + {file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"}, + {file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"}, + {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"}, + {file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"}, + {file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"}, + {file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"}, + {file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"}, + {file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"}, + {file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"}, + {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"}, + {file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"}, + {file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"}, + {file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"}, + {file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"}, + {file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"}, + {file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"}, + {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"}, + {file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"}, + {file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"}, + {file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"}, + {file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"}, + {file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"}, + {file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"}, + {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"}, + {file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"}, + {file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"}, + {file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"}, + {file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"}, + {file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"}, + {file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"}, + {file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"}, + {file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"}, + {file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"}, + {file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"}, + {file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"}, + {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"}, + {file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"}, + {file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"}, + {file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"}, + {file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"}, + {file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"}, + {file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"}, + {file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"}, + {file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"}, + {file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -4427,7 +4745,7 @@ sphinx = ">=6,<8" [package.extras] code-style = ["pre-commit (>=3.0,<4.0)"] linkify = ["linkify-it-py (>=2.0,<3.0)"] -rtd = ["ipython", "pydata-sphinx-theme (==v0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +rtd = ["ipython", "pydata-sphinx-theme (==0.13.0rc4)", "sphinx-autodoc2 (>=0.4.2,<0.5.0)", "sphinx-book-theme (==1.0.0rc2)", "sphinx-copybutton", "sphinx-design2", "sphinx-pyscript", "sphinx-tippy (>=0.3.1)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.8.2,<0.9.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=7,<8)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx-pytest"] testing-docutils = ["pygments", "pytest (>=7,<8)", "pytest-param-files (>=0.3.4,<0.4.0)"] @@ -4644,155 +4962,65 @@ files = [ [[package]] name = "numpy" -version = "2.2.6" +version = "1.26.4" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.9" groups = ["main"] -markers = "python_version == \"3.10\"" files = [ - {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, - {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, - {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, - {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, - {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, - {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, - {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, - {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, - {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, - {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, - {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, - {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, - {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, - {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, - {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, - {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, - {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, - {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, - {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, - {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, - {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, - {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, - {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, - {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, - {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, - {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, - {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, - {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, -] - -[[package]] -name = "numpy" -version = "2.3.3" -description = "Fundamental package for array computing in Python" + {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, + {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, + {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, + {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, + {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, + {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, + {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, + {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, + {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, + {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, + {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, + {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, + {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, + {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, + {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, + {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, + {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, + {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, + {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, + {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, + {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, + {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, + {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, +] + +[[package]] +name = "numpy-financial" +version = "1.0.0" +description = "Simple financial functions" optional = false -python-versions = ">=3.11" +python-versions = ">=3.5" groups = ["main"] -markers = "python_version >= \"3.11\"" files = [ - {file = "numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f"}, - {file = "numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48"}, - {file = "numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa"}, - {file = "numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30"}, - {file = "numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57"}, - {file = "numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa"}, - {file = "numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe"}, - {file = "numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8"}, - {file = "numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea"}, - {file = "numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7"}, - {file = "numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf"}, - {file = "numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb"}, - {file = "numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6"}, - {file = "numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c"}, - {file = "numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae"}, - {file = "numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86"}, - {file = "numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8"}, - {file = "numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf"}, - {file = "numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b"}, - {file = "numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30"}, - {file = "numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3"}, - {file = "numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea"}, - {file = "numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd"}, - {file = "numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d"}, - {file = "numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7"}, - {file = "numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe"}, - {file = "numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021"}, - {file = "numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf"}, - {file = "numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0"}, - {file = "numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8"}, - {file = "numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d"}, - {file = "numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54"}, - {file = "numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097"}, - {file = "numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970"}, - {file = "numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5"}, - {file = "numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f"}, - {file = "numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db"}, - {file = "numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc"}, - {file = "numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029"}, + {file = "numpy-financial-1.0.0.tar.gz", hash = "sha256:f84341bc62b2485d5604a73d5fac7e91975b4b9cd5f4a5a9cf608902ea00cb40"}, + {file = "numpy_financial-1.0.0-py3-none-any.whl", hash = "sha256:bae534b357516f12258862d1f0181d911032d0467f215bfcd1c264b4da579047"}, ] +[package.dependencies] +numpy = ">=1.15" + [[package]] name = "numpydoc" version = "1.9.0" @@ -5678,6 +5906,64 @@ files = [ {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, ] +[[package]] +name = "osqp" +version = "1.1.1" +description = "OSQP: The Operator Splitting QP Solver" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "osqp-1.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:415096d3cf1710a2200a7e70c0c69591abef9081ce3ef8efb8fe16b14e214726"}, + {file = "osqp-1.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3f26a5deb848e577d3b8b03c129be141756f3675297c38c128d556ab6216fb8"}, + {file = "osqp-1.1.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9974235f05905317cd01cf6c6e526fa97f9097812c2c5c3e4dc479c07cdf9d55"}, + {file = "osqp-1.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7328e138fe8c2f40a8235d1f47593a0437bc48e29aad5c14ab7b3dafa6baf17"}, + {file = "osqp-1.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:de3aaa7b3db1c61c288d710be5b190894d0475d3fdb13969a6fe823f6c0c5634"}, + {file = "osqp-1.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:48a6f62df0ec55a5a3a445e4143f51a813931f1e48ac006b15b7e5c9899e2937"}, + {file = "osqp-1.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e569d36955e1a69129f391bb27b2240b3b69d0bcff28e5d19446013dda59836"}, + {file = "osqp-1.1.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fd6d87d5aa17161c43b95e44ab53c76cef466b851cc4ed32da658596cb0a0a1"}, + {file = "osqp-1.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a0481c1f19f70eea9e9883b176eb37b64cd52525920c9ed765acb02411998ae"}, + {file = "osqp-1.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7524d22e91a8381ed30eecbfdf82935528f84b3d8a1b5ad1f8dd84dff3fc07e"}, + {file = "osqp-1.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca4e41477852f725293c666ffa5f795413151c9a14155a7750dff25d3107b851"}, + {file = "osqp-1.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25cd4e8995d18b65c54d1163769797665b9ca5a8a0009f1c4adf4dafe30e33be"}, + {file = "osqp-1.1.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed006d74017578fe98a2afad77f4bbeb096f2d64aa00f50809bb394a7bbd98bf"}, + {file = "osqp-1.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61aca4a356d1555d13c26166c282b9b7985c6c715baf093f839e338e6b49aca0"}, + {file = "osqp-1.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:cd4ac30fd125e12ef5b67836442ebd3bb90925828816e0253e96a203197f5dc7"}, + {file = "osqp-1.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4da548997e7187b1b55358ef291fbb3f9d29b6103917bedbbe77ab8d2307a43a"}, + {file = "osqp-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e33d9de8e6d68a77ef5ca3fee77d42fac89fb5c3fbac3ab5df452176009d28be"}, + {file = "osqp-1.1.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c45eb7a2ef39751417d964c792f3bfe396642b8bc1ae6eca7b28aaa7398ca5"}, + {file = "osqp-1.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3c726d8516d90b2d6acb47acf0bef248188119c52692cca307e418f0f2d8fad"}, + {file = "osqp-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1bd86a9fb19f484705acdff47ec89d68af5c12fba9def921df503bc6bca8e39"}, + {file = "osqp-1.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:42315f8047708c7a2ae184df2255a2b5d323164e67a20df5c03ecd9b4208f2f7"}, + {file = "osqp-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:610a4ecba7a274348f95eeb3c6d56d131207482b6ad95bd20e2a5e4f87111887"}, + {file = "osqp-1.1.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1532b0ade13cb10d8875e121e6131448528fb79e931ffb5dccef555b26b464e"}, + {file = "osqp-1.1.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1ee59dbda22d283de001e7948f7523f509279f7131d5abf0e53fc5ab66b8bb0"}, + {file = "osqp-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:514b2e1d14b5bad9a91ff4dbcbad8da75ef4fb5eee18864e0bbbb620fc6dbcd7"}, + {file = "osqp-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:416b38a30dcee915b9abe0acb54306a173f08fb4d58bdd21ff9b89ea048da39d"}, + {file = "osqp-1.1.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32b7c4d759766064d76a8165dee823e956f7091e62e0194c2ef22c8209b302c7"}, + {file = "osqp-1.1.1-cp38-cp38-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d669fd429e717df123ba2913f0aaee2df42e1739380eb10b9ebc07312c804c4"}, + {file = "osqp-1.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5ec70477c038d78048de3e9c4f1600c33bd08dcbfb0cebccc054eaac86b081"}, + {file = "osqp-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:5f382886682aee5e6536b80272fb955eaa1c89b83770a4cfd2474449a2753409"}, + {file = "osqp-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e2eb72980685a59d57bcb61a32559bddb61deaf3ee275a68e70c77ecb9da2910"}, + {file = "osqp-1.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24a9c749a0d8d9378483c55a8519244e1c6789c6f3c4638a6e546178c70fccc4"}, + {file = "osqp-1.1.1-cp39-cp39-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e8c0078682646260a8c154fd9042f006a60426736ffa495ac1c358723a1d7f7"}, + {file = "osqp-1.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e506467e88d9552f07bb2005adce569619d01fd8d2c8676213e7cfede2feeb24"}, + {file = "osqp-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:89988e8661ab43b139013e9e37c34e18d5ae0c084f296db87c31b97a8d95b9be"}, + {file = "osqp-1.1.1.tar.gz", hash = "sha256:1719e6a88f2ec2bd5dab06131331d1433152fb222372832727d9eb5604d7acf4"}, +] + +[package.dependencies] +jinja2 = "*" +joblib = "*" +numpy = ">=1.7" +scipy = ">=0.13.2" +setuptools = "*" + +[package.extras] +cu12 = ["osqp-cu12"] +dev = ["pre-commit", "pytest (>=6)", "scipy (!=1.12.0)", "torch"] +mkl = ["osqp-mkl"] + [[package]] name = "packageurl-python" version = "0.17.5" @@ -5795,6 +6081,23 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pandas-datareader" +version = "0.10.0" +description = "Data readers extracted from the pandas codebase,should be compatible with recent pandas versions" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "pandas-datareader-0.10.0.tar.gz", hash = "sha256:9fc3c63d39bc0c10c2683f1c6d503ff625020383e38f6cbe14134826b454d5a6"}, + {file = "pandas_datareader-0.10.0-py3-none-any.whl", hash = "sha256:0b95ff3635bc3ee1a6073521b557ab0e3c39d219f4a3b720b6b0bc6e8cdb4bb7"}, +] + +[package.dependencies] +lxml = "*" +pandas = ">=0.23" +requests = ">=2.19.0" + [[package]] name = "pandocfilters" version = "1.5.1" @@ -5847,6 +6150,24 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "patsy" +version = "1.0.2" +description = "A Python package for describing statistical models and for building design matrices." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a"}, + {file = "patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0"}, +] + +[package.dependencies] +numpy = ">=1.4" + +[package.extras] +test = ["pytest", "pytest-cov", "scipy"] + [[package]] name = "pbr" version = "7.0.1" @@ -6357,6 +6678,20 @@ files = [ [package.extras] tests = ["pytest"] +[[package]] +name = "py-lets-be-rational" +version = "1.0.1" +description = "Pure python implementation of Peter Jaeckel's LetsBeRational." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "py_lets_be_rational-1.0.1.tar.gz", hash = "sha256:0e0788a4109e102a666f26d67276c0d3c2feb8a059e788354a90e565f2db0ed2"}, +] + +[package.dependencies] +numpy = "*" + [[package]] name = "py-serializable" version = "2.1.0" @@ -6372,6 +6707,24 @@ files = [ [package.dependencies] defusedxml = ">=0.7.1,<0.8.0" +[[package]] +name = "py-vollib" +version = "1.0.1" +description = "" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "py_vollib-1.0.1.tar.gz", hash = "sha256:36e752eee16dcf52994c42aaf5372f0ab9cfd0d2aedc5462c4c1b6123c9ecf20"}, +] + +[package.dependencies] +numpy = "*" +pandas = "*" +py_lets_be_rational = "*" +scipy = "*" +simplejson = "*" + [[package]] name = "pyarrow" version = "21.0.0" @@ -6473,12 +6826,12 @@ version = "2.23" description = "C parser in Python" optional = false python-versions = ">=3.8" -groups = ["docs"] -markers = "implementation_name == \"pypy\"" +groups = ["main", "docs"] files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] +markers = {main = "implementation_name != \"PyPy\"", docs = "implementation_name == \"pypy\""} [[package]] name = "pydantic" @@ -6710,6 +7063,31 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyportfolioopt" +version = "1.6.0" +description = "Financial portfolio optimization in python" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pyportfolioopt-1.6.0-py3-none-any.whl", hash = "sha256:fcdff5dfb4e7d0c477d8b76e9b5aff58defc30370fc3cb096e95dd46ae43d617"}, + {file = "pyportfolioopt-1.6.0.tar.gz", hash = "sha256:de5ea1914d6ca6bc83c54a954be123bd9c770275c769d1d70fb8760092cd5e9b"}, +] + +[package.dependencies] +cvxpy = ">=1.1.19" +numpy = ">=1.26.0,<3.0.0" +pandas = ">=1.0.0,<4.0.0" +scikit-base = "<0.14.0" +scikit-learn = ">=0.24.1" +scipy = ">=1.3.0" + +[package.extras] +all-extras = ["cvxopt ; python_version < \"3.14\"", "ecos (>=2.0.14,<2.1)", "matplotlib (>=3.2.0)", "plotly (>=5.0.0,<7)"] +dev = ["pytest (>=9.0.0)", "pytest-cov (>=7.0.0)", "yfinance (>=0.2.66)"] +notebook-test = ["nbmake", "pytest-rerunfailures"] + [[package]] name = "pytest" version = "8.3.3" @@ -6733,6 +7111,27 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-asyncio" +version = "1.3.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.10" +groups = ["test"] +files = [ + {file = "pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5"}, + {file = "pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5"}, +] + +[package.dependencies] +backports-asyncio-runner = {version = ">=1.1,<2", markers = "python_version < \"3.11\""} +pytest = ">=8.2,<10" +typing-extensions = {version = ">=4.12", markers = "python_version < \"3.13\""} + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "pytest-cov" version = "3.0.0" @@ -6806,14 +7205,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.2" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, - {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, + {file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"}, + {file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"}, ] [package.extras] @@ -7481,10 +7880,10 @@ files = [ ] [package.dependencies] -botocore = ">=1.37.4,<2.0a.0" +botocore = ">=1.37.4,<2.0a0" [package.extras] -crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] +crt = ["botocore[crt] (>=1.37.4,<2.0a0)"] [[package]] name = "schedule" @@ -7501,6 +7900,26 @@ files = [ [package.extras] timezone = ["pytz"] +[[package]] +name = "scikit-base" +version = "0.13.1" +description = "Base classes for sklearn-like parametric objects" +optional = false +python-versions = "<3.15,>=3.10" +groups = ["main"] +files = [ + {file = "scikit_base-0.13.1-py3-none-any.whl", hash = "sha256:1aca86759435fd2d32d83a526ce11095119c0745e4e5dd91f2e5820023ca8e39"}, + {file = "scikit_base-0.13.1.tar.gz", hash = "sha256:169e5427233f7237b38c7d858bf07b8a86bbf59feccf0708e26dad4ac312c593"}, +] + +[package.extras] +all-extras = ["numpy", "pandas"] +binder = ["jupyter"] +dev = ["pre-commit", "pytest", "pytest-cov", "scikit-learn (>=0.24.0)"] +docs = ["Sphinx (!=7.2.0,<10.0.0)", "jupyter", "myst-parser", "nbsphinx (>=0.8.6)", "numpydoc", "pydata-sphinx-theme", "sphinx-design (<0.7.0)", "sphinx-gallery (<0.21.0)", "sphinx-issues (<6.0.0)", "sphinx-panels", "tabulate"] +linters = ["black", "doc8", "flake8", "flake8-bugbear", "flake8-builtins", "flake8-comprehensions", "flake8-print", "flake8-quotes", "isort", "mypy", "nbqa", "pandas-vet", "pep8-naming", "pydocstyle"] +test = ["coverage", "numpy", "pandas", "pytest", "pytest-cov", "safety", "scikit-learn (>=0.24.0)", "scipy"] + [[package]] name = "scikit-learn" version = "1.7.2" @@ -7559,148 +7978,104 @@ tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas [[package]] name = "scipy" -version = "1.15.3" +version = "1.14.1" description = "Fundamental algorithms for scientific computing in Python" optional = false python-versions = ">=3.10" groups = ["main"] -markers = "python_version == \"3.10\"" files = [ - {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, - {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, - {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f"}, - {file = "scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92"}, - {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82"}, - {file = "scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40"}, - {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e"}, - {file = "scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c"}, - {file = "scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13"}, - {file = "scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b"}, - {file = "scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba"}, - {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65"}, - {file = "scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1"}, - {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889"}, - {file = "scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982"}, - {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9"}, - {file = "scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594"}, - {file = "scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb"}, - {file = "scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019"}, - {file = "scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6"}, - {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477"}, - {file = "scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c"}, - {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45"}, - {file = "scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49"}, - {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e"}, - {file = "scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539"}, - {file = "scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed"}, - {file = "scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759"}, - {file = "scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62"}, - {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb"}, - {file = "scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730"}, - {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825"}, - {file = "scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7"}, - {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11"}, - {file = "scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126"}, - {file = "scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163"}, - {file = "scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8"}, - {file = "scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5"}, - {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e"}, - {file = "scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb"}, - {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723"}, - {file = "scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb"}, - {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4"}, - {file = "scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5"}, - {file = "scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca"}, - {file = "scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf"}, -] - -[package.dependencies] -numpy = ">=1.23.5,<2.5" + {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"}, + {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"}, + {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"}, + {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"}, + {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"}, + {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"}, + {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"}, + {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"}, + {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"}, + {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"}, + {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"}, + {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"}, + {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"}, + {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"}, + {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"}, + {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"}, + {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"}, + {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.3" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] - -[[package]] -name = "scipy" -version = "1.16.2" -description = "Fundamental algorithms for scientific computing in Python" -optional = false -python-versions = ">=3.11" -groups = ["main"] -markers = "python_version >= \"3.11\"" -files = [ - {file = "scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173"}, - {file = "scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2"}, - {file = "scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3"}, - {file = "scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88"}, - {file = "scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa"}, - {file = "scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0"}, - {file = "scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1"}, - {file = "scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef"}, - {file = "scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1"}, - {file = "scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e"}, - {file = "scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5"}, - {file = "scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9"}, - {file = "scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb"}, - {file = "scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e"}, - {file = "scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c"}, - {file = "scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f"}, - {file = "scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21"}, - {file = "scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8"}, - {file = "scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472"}, - {file = "scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351"}, - {file = "scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88"}, - {file = "scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb"}, - {file = "scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548"}, - {file = "scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936"}, - {file = "scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff"}, - {file = "scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831"}, - {file = "scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac"}, - {file = "scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6"}, - {file = "scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c"}, - {file = "scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9"}, - {file = "scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779"}, - {file = "scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b"}, -] - -[package.dependencies] -numpy = ">=1.25.2,<2.6" +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "scs" +version = "3.2.11" +description = "Splitting conic solver" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "scs-3.2.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:928b2fd09d1f4446bd440037d8ca4fb70eb55e411c3259461646ba909ed098f7"}, + {file = "scs-3.2.11-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ace8221013596173de6bdf199d4415f6532b38372e7b68ff22f688b7de2a72bf"}, + {file = "scs-3.2.11-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0a42a6df9308f7a7dc5e5f6c9cc08a4f556e139e39dc7d7fe1f1c6768d7ff9a"}, + {file = "scs-3.2.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c839603fd99d719d27b63918e0eff9e6f1df594506c5fd0dd1529a0df0219243"}, + {file = "scs-3.2.11-cp310-cp310-win_amd64.whl", hash = "sha256:8c56c9739da8d06c10a94f84c5715ff0731bb2efce695a83e07c116eb1c48dcf"}, + {file = "scs-3.2.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698bdf36c67acc43b7a65f1ffa13d11d09b7050f4da6dd5b9c05080e10d198f7"}, + {file = "scs-3.2.11-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aece172705a6e3b04b54b49558d580ab71be02c2fa8fba12b35012e1f386e9c"}, + {file = "scs-3.2.11-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67a9bf34da4be7baf28eb50c8ea7d2e29ae5f345e4b04f057ba3dbeca42efbba"}, + {file = "scs-3.2.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9810701691de9da18d98e542e4f8900e197ec501a47b3a4c1c76242cba133453"}, + {file = "scs-3.2.11-cp311-cp311-win_amd64.whl", hash = "sha256:4bd13200492b9ea334a3c50c17ccbfc9359b206bf7a4f0b022504ebc34e11cda"}, + {file = "scs-3.2.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ad646848375b5cf2d3e45a9ebefd87ccc37a53da9c32f2bf30ea5ad0e84d9e5b"}, + {file = "scs-3.2.11-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f30821521a74f6930924b13e731e9455b6bdcfc964f66d5623d3c8d3fdd98126"}, + {file = "scs-3.2.11-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a89c71ebacd4790c461d3032a47e59ed4759e11c0f03fa79b5b84086ef9c7bc"}, + {file = "scs-3.2.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4e37dc60081dd742bdcd63eeb5b260db116b3803162bcf6084eb203ebedcb080"}, + {file = "scs-3.2.11-cp312-cp312-win_amd64.whl", hash = "sha256:2504266ff8e6a226f7ecb987567c93e6e996534cbf479a60a5a886549446205e"}, + {file = "scs-3.2.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a42696b0a26c3e749b8da8d2ffc57a93af4f0f500fc3a83acb50daad92386de4"}, + {file = "scs-3.2.11-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50d204ae417c014a1756be36e8c0b857ed39c7b64e2c63b6afb1ff64c0a465d0"}, + {file = "scs-3.2.11-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:594c09207395de922e0ff40ced562453e46f4f197dd0128022cb82c096f06615"}, + {file = "scs-3.2.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd672701ed81744e8c300df71d823700b05e7fe3d6a26f8b19b74b0a31fe3c8a"}, + {file = "scs-3.2.11-cp313-cp313-win_amd64.whl", hash = "sha256:2f4ebc0be14783ce3fcda61c616a7e922ac528af033e44a0da952dda0fe98091"}, + {file = "scs-3.2.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe43181c3822bed600363c25c7566a643b319e0edb0c2af385c5f086a9c826d2"}, + {file = "scs-3.2.11-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3c59ce43585d3ea0c6771c5ce3df272b6c8239231acbb9567876be5d0a0474d"}, + {file = "scs-3.2.11-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c46f597892c9f8c5551bb9a3a680dfb86e86a1a6c3bc67b09a5af2e89ba5357"}, + {file = "scs-3.2.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:513131af6991fb4983f84c4ba276c756c0a3574003c2790dda891c68d5b6da30"}, + {file = "scs-3.2.11-cp314-cp314-win_amd64.whl", hash = "sha256:7b2c37e87baca0389f005fe19a0ca8209d43c0f1e9136a1a6fde23cae1735db9"}, + {file = "scs-3.2.11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29c0a5c233fb5a964ea5f7523ec2b2209f000217c0a24423ab5dcd8b8922f37d"}, + {file = "scs-3.2.11-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7519c2f436e793b004d1eae4aaf98c18857e519f8169219d1167fe88b3b0a568"}, + {file = "scs-3.2.11-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c166768dc87c389b2d000b5dcd472bb0ba40f96b4cf0e63c0fb603a4a5c80db"}, + {file = "scs-3.2.11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f51a14a5315974fae4ca4e1b4dc8926f872eca7e66b42e070dbdcfa6904b7860"}, + {file = "scs-3.2.11-cp314-cp314t-win_amd64.whl", hash = "sha256:7fe26e8a0efc96232f4c5b7649817e48dae04a61be911417e925071091b8cbf6"}, + {file = "scs-3.2.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a58e2be11a6eeb03463ab57accfac96fbe4a64168b32769ecca0a35962c86231"}, + {file = "scs-3.2.11-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9761271e2efd4ae06af7cde4d367c99c82fddb6e6c7542fa6f4d02526d0888ef"}, + {file = "scs-3.2.11-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6ec937514d613181ec0b27df6d7fc33a06ada7769da419a1a98c846528758b6"}, + {file = "scs-3.2.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4f9e1abcac09bddc6a6d9e5955440e5cd26fb51371759491b3100cffa8ebad2f"}, + {file = "scs-3.2.11-cp39-cp39-win_amd64.whl", hash = "sha256:caf85064e7ee78001ea205205b8a8c59e134f912e8d7491cc702a64af8cc7227"}, + {file = "scs-3.2.11.tar.gz", hash = "sha256:2a5455cf2093d07f84f2f848c199faed52e79cdb3a11fe250b5622b6bbac4913"}, +] -[package.extras] -dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest (>=8.0.0)", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +[package.dependencies] +numpy = "*" +scipy = "*" [[package]] name = "seaborn" @@ -7747,7 +8122,6 @@ files = [ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] -markers = {main = "python_version == \"3.12\""} [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] @@ -7770,6 +8144,126 @@ files = [ {file = "shortuuid-1.0.13.tar.gz", hash = "sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72"}, ] +[[package]] +name = "simplejson" +version = "3.20.2" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.5" +groups = ["main"] +files = [ + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:11847093fd36e3f5a4f595ff0506286c54885f8ad2d921dfb64a85bce67f72c4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4d291911d23b1ab8eb3241204dd54e3ec60ddcd74dfcb576939d3df327205865"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:da6d16d7108d366bbbf1c1f3274662294859c03266e80dd899fc432598115ea4"}, + {file = "simplejson-3.20.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9ddf9a07694c5bbb4856271cbc4247cc6cf48f224a7d128a280482a2f78bae3d"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:3a0d2337e490e6ab42d65a082e69473717f5cc75c3c3fb530504d3681c4cb40c"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:8ba88696351ed26a8648f8378a1431223f02438f8036f006d23b4f5b572778fa"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:00bcd408a4430af99d1f8b2b103bb2f5133bb688596a511fcfa7db865fbb845e"}, + {file = "simplejson-3.20.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4fc62feb76f590ccaff6f903f52a01c58ba6423171aa117b96508afda9c210f0"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6d7286dc11af60a2f76eafb0c2acde2d997e87890e37e24590bb513bec9f1bc5"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c01379b4861c3b0aa40cba8d44f2b448f5743999aa68aaa5d3ef7049d4a28a2d"}, + {file = "simplejson-3.20.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a16b029ca25645b3bc44e84a4f941efa51bf93c180b31bd704ce6349d1fc77c1"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e22a5fb7b1437ffb057e02e1936a3bfb19084ae9d221ec5e9f4cf85f69946b6"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8b6ff02fc7b8555c906c24735908854819b0d0dc85883d453e23ca4c0445d01"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2bfc1c396ad972ba4431130b42307b2321dba14d988580c1ac421ec6a6b7cee3"}, + {file = "simplejson-3.20.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a97249ee1aee005d891b5a211faf58092a309f3d9d440bc269043b08f662eda"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f1036be00b5edaddbddbb89c0f80ed229714a941cfd21e51386dc69c237201c2"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5d6f5bacb8cdee64946b45f2680afa3f54cd38e62471ceda89f777693aeca4e4"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8db6841fb796ec5af632f677abf21c6425a1ebea0d9ac3ef1a340b8dc69f52b8"}, + {file = "simplejson-3.20.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c0a341f7cc2aae82ee2b31f8a827fd2e51d09626f8b3accc441a6907c88aedb7"}, + {file = "simplejson-3.20.2-cp310-cp310-win32.whl", hash = "sha256:27f9c01a6bc581d32ab026f515226864576da05ef322d7fc141cd8a15a95ce53"}, + {file = "simplejson-3.20.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0a63ec98a4547ff366871bf832a7367ee43d047bcec0b07b66c794e2137b476"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:06190b33cd7849efc413a5738d3da00b90e4a5382fd3d584c841ac20fb828c6f"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4ad4eac7d858947a30d2c404e61f16b84d16be79eb6fb316341885bdde864fa8"}, + {file = "simplejson-3.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b392e11c6165d4a0fde41754a0e13e1d88a5ad782b245a973dd4b2bdb4e5076a"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51eccc4e353eed3c50e0ea2326173acdc05e58f0c110405920b989d481287e51"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:306e83d7c331ad833d2d43c76a67f476c4b80c4a13334f6e34bb110e6105b3bd"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f820a6ac2ef0bc338ae4963f4f82ccebdb0824fe9caf6d660670c578abe01013"}, + {file = "simplejson-3.20.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e7a066528a5451433eb3418184f05682ea0493d14e9aae690499b7e1eb6b81"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:438680ddde57ea87161a4824e8de04387b328ad51cfdf1eaf723623a3014b7aa"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cac78470ae68b8d8c41b6fca97f5bf8e024ca80d5878c7724e024540f5cdaadb"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7524e19c2da5ef281860a3d74668050c6986be15c9dd99966034ba47c68828c2"}, + {file = "simplejson-3.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e9b6d845a603b2eef3394eb5e21edb8626cd9ae9a8361d14e267eb969dbe413"}, + {file = "simplejson-3.20.2-cp311-cp311-win32.whl", hash = "sha256:47d8927e5ac927fdd34c99cc617938abb3624b06ff86e8e219740a86507eb961"}, + {file = "simplejson-3.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba4edf3be8e97e4713d06c3d302cba1ff5c49d16e9d24c209884ac1b8455520c"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259"}, + {file = "simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b"}, + {file = "simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e"}, + {file = "simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544"}, + {file = "simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54"}, + {file = "simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74"}, + {file = "simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0"}, + {file = "simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3"}, + {file = "simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769"}, + {file = "simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661"}, + {file = "simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608"}, + {file = "simplejson-3.20.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a135941a50795c934bdc9acc74e172b126e3694fe26de3c0c1bc0b33ea17e6ce"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ba488decb18738f5d6bd082018409689ed8e74bc6c4d33a0b81af6edf1c9f4"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d81f8e982923d5e9841622ff6568be89756428f98a82c16e4158ac32b92a3787"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdad497ccb1edc5020bef209e9c3e062a923e8e6fca5b8a39f0fb34380c8a66c"}, + {file = "simplejson-3.20.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a3f1db97bcd9fb592928159af7a405b18df7e847cbcc5682a209c5b2ad5d6b1"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:215b65b0dc2c432ab79c430aa4f1e595f37b07a83c1e4c4928d7e22e6b49a748"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_i686.whl", hash = "sha256:ece4863171ba53f086a3bfd87f02ec3d6abc586f413babfc6cf4de4d84894620"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_ppc64le.whl", hash = "sha256:4a76d7c47d959afe6c41c88005f3041f583a4b9a1783cf341887a3628a77baa0"}, + {file = "simplejson-3.20.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e9b0523582a57d9ea74f83ecefdffe18b2b0a907df1a9cef06955883341930d8"}, + {file = "simplejson-3.20.2-cp36-cp36m-win32.whl", hash = "sha256:16366591c8e08a4ac76b81d76a3fc97bf2bcc234c9c097b48d32ea6bfe2be2fe"}, + {file = "simplejson-3.20.2-cp36-cp36m-win_amd64.whl", hash = "sha256:732cf4c4ac1a258b4e9334e1e40a38303689f432497d3caeb491428b7547e782"}, + {file = "simplejson-3.20.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6c3a98e21e5f098e4f982ef302ebb1e681ff16a5d530cfce36296bea58fe2396"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10cf9ca1363dc3711c72f4ec7c1caed2bbd9aaa29a8d9122e31106022dc175c6"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:106762f8aedf3fc3364649bfe8dc9a40bf5104f872a4d2d86bae001b1af30d30"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b21659898b7496322e99674739193f81052e588afa8b31b6a1c7733d8829b925"}, + {file = "simplejson-3.20.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78fa1db6a02bca88829f2b2057c76a1d2dc2fccb8c5ff1199e352f213e9ec719"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:156139d94b660448ec8a4ea89f77ec476597f752c2ff66432d3656704c66b40e"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:b2620ac40be04dff08854baf6f4df10272f67079f61ed1b6274c0e840f2e2ae1"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:9ccef5b5d3e3ac5d9da0a0ca1d2de8cf2b0fb56b06aa0ab79325fa4bcc5a1d60"}, + {file = "simplejson-3.20.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f526304c2cc9fd8b8d18afacb75bc171650f83a7097b2c92ad6a431b5d7c1b72"}, + {file = "simplejson-3.20.2-cp37-cp37m-win32.whl", hash = "sha256:e0f661105398121dd48d9987a2a8f7825b8297b3b2a7fe5b0d247370396119d5"}, + {file = "simplejson-3.20.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dab98625b3d6821e77ea59c4d0e71059f8063825a0885b50ed410e5c8bd5cb66"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b8205f113082e7d8f667d6cd37d019a7ee5ef30b48463f9de48e1853726c6127"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fc8da64929ef0ff16448b602394a76fd9968a39afff0692e5ab53669df1f047f"}, + {file = "simplejson-3.20.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bfe704864b5fead4f21c8d448a89ee101c9b0fc92a5f40b674111da9272b3a90"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40ca7cbe7d2f423b97ed4e70989ef357f027a7e487606628c11b79667639dc84"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cec1868b237fe9fb2d466d6ce0c7b772e005aadeeda582d867f6f1ec9710cad"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:792debfba68d8dd61085ffb332d72b9f5b38269cda0c99f92c7a054382f55246"}, + {file = "simplejson-3.20.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e022b2c4c54cb4855e555f64aa3377e3e5ca912c372fa9e3edcc90ebbad93dce"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:5de26f11d5aca575d3825dddc65f69fdcba18f6ca2b4db5cef16f41f969cef15"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:e2162b2a43614727ec3df75baeda8881ab129824aa1b49410d4b6c64f55a45b4"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e11a1d6b2f7e72ca546bdb4e6374b237ebae9220e764051b867111df83acbd13"}, + {file = "simplejson-3.20.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:daf7cd18fe99eb427fa6ddb6b437cfde65125a96dc27b93a8969b6fe90a1dbea"}, + {file = "simplejson-3.20.2-cp38-cp38-win32.whl", hash = "sha256:da795ea5f440052f4f497b496010e2c4e05940d449ea7b5c417794ec1be55d01"}, + {file = "simplejson-3.20.2-cp38-cp38-win_amd64.whl", hash = "sha256:6a4b5e7864f952fcce4244a70166797d7b8fd6069b4286d3e8403c14b88656b6"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b3bf76512ccb07d47944ebdca44c65b781612d38b9098566b4bb40f713fc4047"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:214e26acf2dfb9ff3314e65c4e168a6b125bced0e2d99a65ea7b0f169db1e562"}, + {file = "simplejson-3.20.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2fb1259ca9c385b0395bad59cdbf79535a5a84fb1988f339a49bfbc57455a35a"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c34e028a2ba8553a208ded1da5fa8501833875078c4c00a50dffc33622057881"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b538f9d9e503b0dd43af60496780cb50755e4d8e5b34e5647b887675c1ae9fee"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab998e416ded6c58f549a22b6a8847e75a9e1ef98eb9fbb2863e1f9e61a4105b"}, + {file = "simplejson-3.20.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a8f1c307edf5fbf0c6db3396c5d3471409c4a40c7a2a466fbc762f20d46601a"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a7bbac80bdb82a44303f5630baee140aee208e5a4618e8b9fde3fc400a42671"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5ef70ec8fe1569872e5a3e4720c1e1dcb823879a3c78bc02589eb88fab920b1f"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:cb11c09c99253a74c36925d461c86ea25f0140f3b98ff678322734ddc0f038d7"}, + {file = "simplejson-3.20.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:66f7c78c6ef776f8bd9afaad455e88b8197a51e95617bcc44b50dd974a7825ba"}, + {file = "simplejson-3.20.2-cp39-cp39-win32.whl", hash = "sha256:619ada86bfe3a5aa02b8222ca6bfc5aa3e1075c1fb5b3263d24ba579382df472"}, + {file = "simplejson-3.20.2-cp39-cp39-win_amd64.whl", hash = "sha256:44a6235e09ca5cc41aa5870a952489c06aa4aee3361ae46daa947d8398e57502"}, + {file = "simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017"}, + {file = "simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649"}, +] + [[package]] name = "six" version = "1.17.0" @@ -8243,6 +8737,64 @@ typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\"" [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] +[[package]] +name = "statsmodels" +version = "0.14.6" +description = "Statistical computations and models for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "statsmodels-0.14.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4ff0649a2df674c7ffb6fa1a06bffdb82a6adf09a48e90e000a15a6aaa734b0"}, + {file = "statsmodels-0.14.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:109012088b3e370080846ab053c76d125268631410142daad2f8c10770e8e8d9"}, + {file = "statsmodels-0.14.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e93bd5d220f3cb6fc5fc1bffd5b094966cab8ee99f6c57c02e95710513d6ac3f"}, + {file = "statsmodels-0.14.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06eec42d682fdb09fe5d70a05930857efb141754ec5a5056a03304c1b5e32fd9"}, + {file = "statsmodels-0.14.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0444e88557df735eda7db330806fe09d51c9f888bb1f5906cb3a61fb1a3ed4a8"}, + {file = "statsmodels-0.14.6-cp310-cp310-win_amd64.whl", hash = "sha256:e83a9abe653835da3b37fb6ae04b45480c1de11b3134bd40b09717192a1456ea"}, + {file = "statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4"}, + {file = "statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6"}, + {file = "statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb"}, + {file = "statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca"}, + {file = "statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d"}, + {file = "statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7"}, + {file = "statsmodels-0.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe76140ae7adc5ff0e60a3f0d56f4fffef484efa803c3efebf2fcd734d72ecb5"}, + {file = "statsmodels-0.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26d4f0ed3b31f3c86f83a92f5c1f5cbe63fc992cd8915daf28ca49be14463a1c"}, + {file = "statsmodels-0.14.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c00a42863e4f4733ac9d078bbfad816249c01451740e6f5053ecc7db6d6368"}, + {file = "statsmodels-0.14.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b58cf7474aa9e7e3b0771a66537148b2df9b5884fbf156096c0e6c1ff0469d"}, + {file = "statsmodels-0.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81e7dcc5e9587f2567e52deaff5220b175bf2f648951549eae5fc9383b62bc37"}, + {file = "statsmodels-0.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:b5eb07acd115aa6208b4058211138393a7e6c2cf12b6f213ede10f658f6a714f"}, + {file = "statsmodels-0.14.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47ee7af083623d2091954fa71c7549b8443168f41b7c5dce66510274c50fd73e"}, + {file = "statsmodels-0.14.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa60d82e29fcd0a736e86feb63a11d2380322d77a9369a54be8b0965a3985f71"}, + {file = "statsmodels-0.14.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89ee7d595f5939cc20bf946faedcb5137d975f03ae080f300ebb4398f16a5bd4"}, + {file = "statsmodels-0.14.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:730f3297b26749b216a06e4327fe0be59b8d05f7d594fb6caff4287b69654589"}, + {file = "statsmodels-0.14.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f1c08befa85e93acc992b72a390ddb7bd876190f1360e61d10cf43833463bc9c"}, + {file = "statsmodels-0.14.6-cp313-cp313-win_amd64.whl", hash = "sha256:8021271a79f35b842c02a1794465a651a9d06ec2080f76ebc3b7adce77d08233"}, + {file = "statsmodels-0.14.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:00781869991f8f02ad3610da6627fd26ebe262210287beb59761982a8fa88cae"}, + {file = "statsmodels-0.14.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:73f305fbf31607b35ce919fae636ab8b80d175328ed38fdc6f354e813b86ee37"}, + {file = "statsmodels-0.14.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e443e7077a6e2d3faeea72f5a92c9f12c63722686eb80bb40a0f04e4a7e267ad"}, + {file = "statsmodels-0.14.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3414e40c073d725007a6603a18247ab7af3467e1af4a5e5a24e4c27bc26673b4"}, + {file = "statsmodels-0.14.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a518d3f9889ef920116f9fa56d0338069e110f823926356946dae83bc9e33e19"}, + {file = "statsmodels-0.14.6-cp314-cp314-win_amd64.whl", hash = "sha256:151b73e29f01fe619dbce7f66d61a356e9d1fe5e906529b78807df9189c37721"}, + {file = "statsmodels-0.14.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0c1b0f9f6915619e2a0d3853e5763d4d66876892ad352e7d7b93a737556978"}, + {file = "statsmodels-0.14.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e0fc891d6358bf376cc0ae1fee10a650478172ae9ba359daba1785fc496cd1a"}, + {file = "statsmodels-0.14.6-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f52ef0f0b63b8fd11e1ef1c2a1e73a410720b8715c9a83a26d733b6815597fe"}, + {file = "statsmodels-0.14.6-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b328eafa86a2a67303fdb1d25677d15b70cd2a5229aabec7670ec5ea840f1375"}, + {file = "statsmodels-0.14.6-cp39-cp39-win_amd64.whl", hash = "sha256:3bef39f8587754f2d644b2e831e102fa08ace9a5a1af4b583b122e6fd3e083ab"}, + {file = "statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a"}, +] + +[package.dependencies] +numpy = ">=1.22.3,<3" +packaging = ">=21.3" +pandas = ">=1.4,<2.1.0 || >2.1.0" +patsy = ">=0.5.6" +scipy = ">=1.8,<1.9.2 || >1.9.2" + +[package.extras] +build = ["cython (>=3.0.10)"] +develop = ["colorama", "cython (>=3.0.10)", "cython (>=3.0.10,<4)", "flake8", "isort", "jinja2", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty ; os_name == \"nt\"", "setuptools_scm[toml] (>=8.0,<9.0)"] +docs = ["ipykernel", "jupyter_client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] + [[package]] name = "sympy" version = "1.13.1" @@ -9490,9 +10042,9 @@ files = [ ] [package.extras] -cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] +cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and python_version < \"3.14\"", "cffi (>=2.0.0b0) ; platform_python_implementation != \"PyPy\" and python_version >= \"3.14\""] [metadata] lock-version = "2.1" python-versions = ">=3.10, <3.13" -content-hash = "2ca08429df55e63e3001da780f9032e2da40b906d8092b2bb6e97e5e44b09d34" +content-hash = "bb7ca920f417fd257b96a256685db0d36d43bdb626c992b610c3f5e328dcc943" diff --git a/pyproject.toml b/pyproject.toml index 2d3b3ab..1ab3716 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,16 @@ dependencies = [ "pytest-env (>=1.1.5,<2.0.0)", "langfuse>=2.0.0", "openlit>=1.35.0", + "numpy (>=1.26,<2.0)", + "scipy (>=1.11,<1.15)", + "sympy (>=1.12,<2.0)", + "numpy-financial (>=1.0.0,<2.0.0)", + "py-vollib (>=1.0.1,<2.0.0)", + "pyportfolioopt (>=1.6.0,<2.0.0)", + "empyrical (>=0.5.5,<0.6.0)", + "arch (>=6.0,<7.0)", + "statsmodels (>=0.14,<0.15)", + "python-dotenv (>=1.2.2,<2.0.0)", ] [project.urls] @@ -64,6 +74,7 @@ mypy = "^1.15.0" ruff = ">=0.11.4,<0.12.0" nbqa = { version = "^1.7.0", extras = ["toolchain"] } pip-audit = "^2.7.1" +pytest-asyncio = "^1.3.0" [tool.poetry.group.docs] optional = true @@ -179,6 +190,8 @@ filterwarnings = [ ] # Exclude legacy tests (imports are broken after code was moved) norecursedirs = ["legacy"] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "function" [tool.coverage] [tool.coverage.run] diff --git a/src/task_solver/messages.py b/src/task_solver/messages.py index 36c196e..7420a48 100644 --- a/src/task_solver/messages.py +++ b/src/task_solver/messages.py @@ -1,7 +1,7 @@ """Message types for task solving debate system.""" from dataclasses import dataclass -from typing import Dict, List +from typing import Dict, List, Union @dataclass @@ -37,6 +37,7 @@ class AgentSolution: round_number: int capability_name: str area_name: str + solution_type: str = "standard" # Discriminator for Union serialization def to_dict(self) -> Dict[str, str]: """Convert to dictionary.""" @@ -52,6 +53,46 @@ def to_dict(self) -> Dict[str, str]: } +@dataclass +class ToolAssistedAgentSolution: + """Solution proposed by a tool-assisted agent with code execution details. + + Note: code and code_output use empty string as default instead of None + to avoid Union type issues with autogen_core serialization. + """ + + agent_id: str + task_id: str + thought: str + final_answer: str + numerical_answer: str + round_number: int + capability_name: str + area_name: str + solution_type: str = "tool_assisted" # Discriminator for Union serialization + code: str = "" + code_output: str = "" + + def to_dict(self) -> Dict[str, str]: + """Convert to dictionary.""" + result = { + "agent_id": self.agent_id, + "task_id": self.task_id, + "thought": self.thought, + "final_answer": self.final_answer, + "numerical_answer": self.numerical_answer, + "round_number": str(self.round_number), + "capability_name": self.capability_name, + "area_name": self.area_name, + } + # Include code fields if present (not empty) + if self.code: + result["code"] = self.code + if self.code_output: + result["code_output"] = self.code_output + return result + + @dataclass class AgentRevisionRequest: """Request for agent to revise solution based on other agents' solutions.""" @@ -73,6 +114,10 @@ class ConsensusCheck: round_number: int +# Type alias for solutions that can appear in the debate +SolutionUnion = Union[AgentSolution, ToolAssistedAgentSolution] + + @dataclass class FinalSolution: """Final solution for a task.""" diff --git a/src/task_solver/moderator.py b/src/task_solver/moderator.py index 32c0f51..2078dd5 100644 --- a/src/task_solver/moderator.py +++ b/src/task_solver/moderator.py @@ -24,8 +24,10 @@ AgentRevisionRequest, AgentSolution, FinalSolution, + SolutionUnion, Task, TaskSolutionRequest, + ToolAssistedAgentSolution, ) from src.utils.agentic_prompts import ( TASK_MODERATOR_CONSENSUS_PROMPT, @@ -80,7 +82,8 @@ def __init__( self._langfuse_client = langfuse_client # Track solutions by task_id and round - self._solutions_buffer: Dict[int, List[AgentSolution]] + # Now properly typed with discriminator fields in solution classes + self._solutions_buffer: dict[int, list[SolutionUnion]] = {} self._current_round = 0 self._final_solutions: FinalSolution self._tasks: Task # Store original tasks for consensus checking @@ -111,9 +114,13 @@ def _extract_consensus_components( raise def _check_simple_consensus( - self, solutions: List[AgentSolution] + self, solutions: list[SolutionUnion] ) -> tuple[bool, str, str]: - """Check consensus; if all agents have the same final answer.""" + """Check consensus; if all agents have the same final answer. + + Works with both AgentSolution and ToolAssistedAgentSolution since + both have final_answer and numerical_answer fields. + """ if not solutions or len(solutions) < self._num_solvers: return False, "", "null" @@ -234,6 +241,74 @@ async def handle_agent_solution( log.error(traceback.format_exc()) span.update(metadata={"error": error_msg}) + @message_handler + async def handle_tool_assisted_agent_solution( + self, message: ToolAssistedAgentSolution, ctx: MessageContext + ) -> None: + """Handle solution from a tool-assisted agent. + + Tool-assisted solutions include code and code_output fields that need + to be preserved in the final solution files. + """ + with self._langfuse_client.start_as_current_span( + name=f"moderator_handle_tool_assisted_solution_{message.task_id}_round_{message.round_number}" + ) as span: + try: + task_id = message.task_id + round_num = message.round_number + + msg = f"Moderator received tool-assisted solution from agent {message.agent_id} for task {task_id}, {message.capability_name}, {message.area_name} round {round_num}" + log.info(msg) + log.debug( + "Moderator: Tool-assisted solution has code: %s, has code_output: %s", + bool(message.code), + bool(message.code_output), + ) + if message.code: + log.debug( + "Moderator: Code length in received message: %d characters", + len(message.code), + ) + span.update( + metadata={ + "solution_received": msg, + "task_id": task_id, + "agent_id": message.agent_id, + "round": round_num, + "has_code": bool(message.code), + "code_executed": bool(message.code_output), + } + ) + + if round_num != self._current_round: + msg = f"Moderator received solution from agent {message.agent_id} for task {task_id}, {message.capability_name}, {message.area_name} round {round_num} but current round is {self._current_round}" + log.error(msg) + span.update(metadata={"error": msg}) + raise Exception(msg) + + # Initialize round buffer if needed + if self._current_round not in self._solutions_buffer: + self._solutions_buffer[self._current_round] = [] + + # Add solution to buffer - store the ToolAssistedAgentSolution directly + self._solutions_buffer[self._current_round].append(message) + + msg = f"{len(self._solutions_buffer[self._current_round])}/{self._num_solvers} solutions collected for round {self._current_round}" + log.info(msg) + span.update(metadata={"solutions_collected": msg}) + + if ( + len(self._solutions_buffer[self._current_round]) + == self._num_solvers + ): + await self._check_consensus_and_proceed(task_id, ctx) + + except Exception as e: + error_msg = f"Error handling tool-assisted solution from agent {message.agent_id}: {str(e)}" + log.error(error_msg) + log.error(traceback.format_exc()) + span.update(metadata={"error": error_msg}) + async def _check_consensus_and_proceed( self, task_id: str, ctx: MessageContext ) -> None: @@ -420,17 +495,7 @@ async def _save_final_solution(self, final_solution: FinalSolution) -> None: "reasoning": final_solution.reasoning, "consensus_reached": final_solution.consensus_reached, "total_rounds": final_solution.total_rounds, - "all_solutions": [ - { - "agent_id": sol["agent_id"], - "task_id": sol["task_id"], - "thought": sol["thought"], - "final_answer": sol["final_answer"], - "numerical_answer": sol["numerical_answer"], - "round_number": sol["round_number"], - } - for sol in final_solution.all_solutions - ], + "all_solutions": final_solution.all_solutions, # Include all fields from to_dict() } with open(output_file, "w") as f: diff --git a/src/task_solver/tool_assisted_scientist.py b/src/task_solver/tool_assisted_scientist.py new file mode 100644 index 0000000..93ad473 --- /dev/null +++ b/src/task_solver/tool_assisted_scientist.py @@ -0,0 +1,1064 @@ +"""Tool-assisted task solver agent with code execution capabilities.""" + +import json +import logging +import re +import traceback + +from autogen_core import ( + DefaultTopicId, + MessageContext, + RoutedAgent, + default_subscription, + message_handler, +) +from autogen_core.models import ( + ChatCompletionClient, + LLMMessage, + SystemMessage, + UserMessage, +) +from langfuse import Langfuse + +from src.task_solver.messages import ( + AgentRevisionRequest, + TaskSolutionRequest, + ToolAssistedAgentSolution, +) +from src.tools.toolkit import ScientificToolKit +from src.utils.json_utils import parse_llm_json_response +from src.utils.tool_assisted_prompts import ( + TOOL_ASSISTED_ANSWER_FORMATTING_PROMPT, + TOOL_ASSISTED_ROUND_1_PROMPT, + TOOL_ASSISTED_SUBSEQUENT_ROUNDS_PROMPT, + TOOL_ASSISTED_SYSTEM_MESSAGE, +) + + +log = logging.getLogger("task_solver.tool_assisted_scientist") + +MAX_MODEL_ATTEMPTS = 3 +MAX_CODE_EXECUTION_ATTEMPTS = 3 # Increased from 2 for better reliability + + +@default_subscription +class ToolAssistedScientist(RoutedAgent): + """A scientist that solves tasks with code execution capabilities. + + This agent can execute Python code using SymPy, NumPy, and SciPy to assist + in solving mathematical problems. + + Attributes + ---------- + _model_client : ChatCompletionClient + ChatCompletionClient for generating solutions via LLM. + _scientist_id : str + Unique identifier for this scientist agent in the debate. + _langfuse_client : Langfuse + Langfuse client for tracing and logging scientist activity. + _toolkit : ScientificToolKit + Toolkit for tool selection and code execution. + """ + + def __init__( + self, + model_client: ChatCompletionClient, + scientist_id: str, + langfuse_client: Langfuse, + toolkit: ScientificToolKit, + ) -> None: + super().__init__(f"Tool-Assisted Scientist {scientist_id}") + self._model_client = model_client + self._scientist_id = scientist_id + self._langfuse_client = langfuse_client + self._toolkit = toolkit + + def _extract_numerical_from_code_output(self, code_output: str) -> str | None: + """Extract the final numerical answer from code output. + + Looks for the last number printed in the code output, preferring + numbers that appear after labels like "answer:", "result:", "price:", etc. + + Returns + ------- + str | None + The extracted numerical value as a string, or None if no number found. + """ + if not code_output or code_output.startswith("ERROR"): + return None + + # First, try to find numbers after common result labels + result_patterns = [ + r"(?:answer|result|final|price|spread|years?|maturity|value|solution)\s*[:=]\s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)", + r"(?:answer|result|final|price|spread|years?|maturity|value|solution)\s*\(\s*[ns]\s*\)\s*:\s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)", + ] + + for pattern in result_patterns: + matches = re.findall(pattern, code_output, re.IGNORECASE) + if matches: + # Use the last match (most specific/final result) + return str(matches[-1]) + + # Fallback: Find all numbers and return the last one + numbers = re.findall(r"-?\d+\.?\d*(?:[eE][+-]?\d+)?", code_output) + + if numbers: + # Return the last number found + return str(numbers[-1]) + + return "null" + + def _extract_code_generation_response(self, response: str) -> tuple[str, str]: + """Extract thought and code from Stage 1 response. + + Returns + ------- + (thought, code): Thought process and Python code + """ + try: + parsed = parse_llm_json_response(response) + thought_raw = parsed.get("thought", response.strip()) + code = parsed.get("code") + + thought = ( + json.dumps(thought_raw, ensure_ascii=False) + if isinstance(thought_raw, (dict, list)) + else str(thought_raw).strip() + ) + + # Handle code field + if code is not None and code != "null": + code = str(code).strip() + log.debug("Code (first 200 chars): %s", code[:200]) + else: + code = "" + + return thought, code + + except Exception as e: + msg = f"Failed to parse Stage 1 (code generation) response: {e} \n Response: {response}" + log.error(msg) + log.error(traceback.format_exc()) + raise + + def _extract_answer_formatting_response(self, response: str) -> tuple[str, str]: + """Extract final_answer and numerical_answer from Stage 2 response. + + Returns + ------- + (final_answer, numerical_answer): Formatted answer and numerical value + """ + try: + parsed = parse_llm_json_response(response) + final_answer_raw = parsed.get("final_answer", "No clear answer provided") + numerical_answer = parsed.get("numerical_answer") + + final_answer = ( + json.dumps(final_answer_raw, ensure_ascii=False, indent=2) + if isinstance(final_answer_raw, (dict, list)) + else str(final_answer_raw).strip() + ) + + # Handle numerical_answer + if numerical_answer is not None: + numerical_answer = str(numerical_answer) + else: + numerical_answer = "null" + + return final_answer, numerical_answer + + except Exception as e: + msg = f"Failed to parse Stage 2 (answer formatting) response: {e} \n Response: {response}" + log.error(msg) + log.error(traceback.format_exc()) + raise + + def _extract_solution_components( + self, response: str + ) -> tuple[str, str | None, str | None, str, str]: + """Extract components from JSON response including code fields.""" + try: + parsed = parse_llm_json_response(response) + thought_raw = parsed.get("thought", response.strip()) + code = parsed.get("code") + code_output = parsed.get("code_output") + final_answer_raw = parsed.get("final_answer", "No clear answer provided") + numerical_answer = parsed.get("numerical_answer") + + thought = ( + json.dumps(thought_raw, ensure_ascii=False) + if isinstance(thought_raw, (dict, list)) + else str(thought_raw).strip() + ) + final_answer = ( + json.dumps(final_answer_raw, ensure_ascii=False, indent=2) + if isinstance(final_answer_raw, (dict, list)) + else str(final_answer_raw).strip() + ) + + # Handle code field + if code is not None and code != "null": + # JSON parser already handles escape sequences correctly + code = str(code).strip() + log.debug("Code (first 200 chars): %s", code[:200]) + else: + code = "" + + # Handle code_output field + if code_output is not None and code_output != "null": + code_output = str(code_output).strip() + else: + code_output = "" + + # Handle numerical_answer + if numerical_answer is not None: + numerical_answer = str(numerical_answer) + else: + numerical_answer = "null" + + return thought, code, code_output, final_answer, numerical_answer + + except Exception as e: + msg = f"Failed to parse JSON response: {e} \n Response: {response}" + log.error(msg) + log.error(traceback.format_exc()) + raise + + async def _generate_solution_with_code_execution( + self, system_message: SystemMessage, user_message: UserMessage + ) -> tuple[str, str, str, str, str]: + """Generate solution using two-stage architecture. + + Stage 1: Code Generation + - Model generates thought + code + - Code is executed to get output + - If execution fails, provide feedback and retry + + Stage 2: Answer Formatting + - Model sees code output + - Formats final_answer and numerical_answer appropriately + - Handles bool/MCQ/numerical question types correctly + + Returns + ------- + (thought, code, code_output, final_answer, numerical_answer) + """ + # ================================================================= + # STAGE 1: CODE GENERATION AND EXECUTION + # ================================================================= + conversation_history: list[LLMMessage] = [system_message, user_message] + last_error: Exception | None = None + + thought = "" + code = "" + code_output = "" + + # Track total code execution attempts across all model responses + total_exec_attempts = 0 + + for model_attempt in range(1, MAX_MODEL_ATTEMPTS + 1): + try: + # Get Stage 1 response (thought + code only) + log.debug( + "Scientist %s: Stage 1 LLM attempt %d/%d (code generation)", + self._scientist_id, + model_attempt, + MAX_MODEL_ATTEMPTS, + ) + response = await self._model_client.create( + conversation_history, + json_output=True, + ) + except Exception as exc: # pragma: no cover + last_error = exc + log.warning( + "Tool-assisted scientist %s failed to get Stage 1 response on attempt %d: %s", + self._scientist_id, + model_attempt, + exc, + ) + continue + + response_content = str(getattr(response, "content", "") or "").strip() + if not response_content: + last_error = ValueError("Empty response content") + log.warning( + "Tool-assisted scientist %s received empty Stage 1 response on attempt %d", + self._scientist_id, + model_attempt, + ) + continue + + log.debug( + "Scientist %s: Received Stage 1 response (first 200 chars): %s", + self._scientist_id, + response_content[:200], + ) + + try: + # Parse Stage 1 response + thought, code = self._extract_code_generation_response(response_content) + + # If no code, proceed to Stage 2 with empty output + if not code: + log.info( + "Scientist %s: No code generated, proceeding to Stage 2 for direct answer", + self._scientist_id, + ) + code_output = "" + break + + # Code is present - attempt execution + log.info( + "Scientist %s: Code detected, starting execution", + self._scientist_id, + ) + + # Clean up common code issues + if "\\n" in code: + code = code.replace("\\n", "\n") + log.debug( + "Scientist %s: Applied \\n replacement to clean up code", + self._scientist_id, + ) + + log.debug( + "Scientist %s: Generated code:\n%s\n%s\n%s", + self._scientist_id, + "-" * 60, + code, + "-" * 60, + ) + + # Increment total execution attempts + total_exec_attempts += 1 + log.info( + "Scientist %s: Code execution attempt %d/%d", + self._scientist_id, + total_exec_attempts, + MAX_CODE_EXECUTION_ATTEMPTS, + ) + + execution_result = self._toolkit.execute_code(code) + execution_result = type( + "obj", + (object,), + { + "success": execution_result["success"], + "output": execution_result["output"], + "error": execution_result.get("error"), + }, + )() + + if execution_result.success: + code_output = execution_result.output + log.info( + "Scientist %s: Code execution successful", self._scientist_id + ) + log.debug( + "Scientist %s: Code output:\n%s\n%s\n%s", + self._scientist_id, + "-" * 60, + code_output[:500], + "-" * 60, + ) + # Success - break out of Stage 1 loop and proceed to Stage 2 + break + + # Code execution failed + code_output = f"ERROR: {execution_result.error}" + log.warning( + "Scientist %s: Code execution failed (attempt %d/%d): %s", + self._scientist_id, + total_exec_attempts, + MAX_CODE_EXECUTION_ATTEMPTS, + execution_result.error, + ) + + # proceed to Stage 2 with error if code still invalid + if total_exec_attempts >= MAX_CODE_EXECUTION_ATTEMPTS: + log.error( + "Scientist %s: Max code execution attempts (%d) exhausted, proceeding to Stage 2 with error", + self._scientist_id, + MAX_CODE_EXECUTION_ATTEMPTS, + ) + break + + # Provide feedback to LLM for another attempt + log.info( + "Scientist %s: Providing error feedback to LLM for code correction", + self._scientist_id, + ) + + error_msg = str(execution_result.error) + hints = [] + + if ( + "unterminated string" in error_msg.lower() + or "eol while scanning" in error_msg.lower() + ): + hints.append( + "- Use triple-quoted strings for multi-line output: print('''text''')" + ) + hints.append( + "- Avoid backslashes in strings; use raw strings r'' if needed" + ) + hints.append( + "- For print statements, use separate print() calls instead of \\n in strings" + ) + elif "syntaxerror" in error_msg.lower(): + hints.append( + "- Review Python syntax, especially quotes, parentheses, and indentation" + ) + hints.append("- Check for unmatched brackets or quotes") + elif "nameerror" in error_msg.lower(): + hints.append("- Ensure all imports are at the top of the code") + hints.append("- Check that all variables are defined before use") + elif ( + "importerror" in error_msg.lower() + or "modulenotfounderror" in error_msg.lower() + ): + hints.append( + "- Only use approved libraries: sympy, numpy, scipy, math, fractions, decimal" + ) + + hints_text = ( + "\n".join(hints) + if hints + else "- Carefully review the error message above" + ) + + feedback_prompt = f"""Your previous code execution failed with the following error: + +ERROR: {execution_result.error} + +Failed code: +```python +{code} +``` + +ACTIONABLE FIXES: +{hints_text} + +IMPORTANT: When writing code in JSON: +- Use simple print() statements on separate lines +- Avoid LaTeX notation in code comments or strings +- Use triple-quoted strings for multi-line output: print('''result''') +- Keep code focused on numerical computation only + +Return your corrected solution in the same JSON format {{thought, code}}.""" + + feedback_message = UserMessage(content=feedback_prompt, source="user") + conversation_history.append(feedback_message) + # Continue to next model attempt for corrected code + + except Exception as exc: + last_error = exc + log.warning( + "Tool-assisted scientist %s failed to parse Stage 1 response on attempt %d: %s", + self._scientist_id, + model_attempt, + exc, + ) + log.debug("Full exception: %s", traceback.format_exc()) + continue + + # Check if Stage 1 succeeded + if not thought: + raise RuntimeError( + f"Tool-assisted scientist {self._scientist_id} could not generate code " + f"after {MAX_MODEL_ATTEMPTS} attempts" + ) from last_error + + # ================================================================= + # STAGE 2: ANSWER FORMATTING + # ================================================================= + log.info( + "Scientist %s: Stage 1 complete. Proceeding to Stage 2 (answer formatting)", + self._scientist_id, + ) + + # Build Stage 2 prompt with code output and original problem + content_str = ( + user_message.content if isinstance(user_message.content, str) else "" + ) + original_problem = ( + content_str.split("PROBLEM: ")[1].split("\n")[0] + if "PROBLEM: " in content_str + else "the problem" + ) + + stage2_prompt = TOOL_ASSISTED_ANSWER_FORMATTING_PROMPT.format( + code_output=code_output if code_output else "No code was executed.", + problem_text=original_problem, + ) + + stage2_message = UserMessage(content=stage2_prompt, source="user") + + for attempt in range(1, MAX_MODEL_ATTEMPTS + 1): + try: + log.debug( + "Scientist %s: Stage 2 LLM attempt %d/%d (answer formatting)", + self._scientist_id, + attempt, + MAX_MODEL_ATTEMPTS, + ) + response = await self._model_client.create( + [system_message, stage2_message], + json_output=True, + ) + + response_content = str(getattr(response, "content", "") or "").strip() + if not response_content: + log.warning("Empty Stage 2 response on attempt %d", attempt) + continue + + # Parse Stage 2 response + final_answer, numerical_answer = ( + self._extract_answer_formatting_response(response_content) + ) + + log.info( + "Scientist %s: Stage 2 complete. Two-stage solution generated successfully.", + self._scientist_id, + ) + + return thought, code, code_output, final_answer, numerical_answer + + except Exception as exc: + log.warning( + "Tool-assisted scientist %s failed Stage 2 attempt %d: %s", + self._scientist_id, + attempt, + exc, + ) + last_error = exc + continue + + # Stage 2 failed - use fallback + log.error( + "Scientist %s: Stage 2 failed after %d attempts, using fallback", + self._scientist_id, + MAX_MODEL_ATTEMPTS, + ) + + # Fallback: extract numerical from code output if possible + extracted = ( + self._extract_numerical_from_code_output(code_output) + if code_output + else None + ) + numerical_answer = extracted if extracted is not None else "null" + final_answer = code_output if code_output else "Unable to generate final answer" + + return thought, code, code_output, final_answer, numerical_answer + + async def _generate_solution_with_code_execution_legacy( + self, system_message: SystemMessage, user_message: UserMessage + ) -> tuple[str, str | None, str | None, str, str]: + """Generate solution with iterative code execution feedback. + + This method handles the iterative process of: + 1. Getting LLM response with potential code + 2. Executing code if present + 3. If execution fails, providing error feedback to LLM + 4. Repeating until code succeeds or max attempts exhausted + 5. Returning final solution components + """ + conversation_history: list[LLMMessage] = [system_message, user_message] + last_error: Exception | None = None + + # Track final components from last valid parse + last_valid_components = None + + # Track total code execution attempts across all model responses + total_exec_attempts = 0 + + for model_attempt in range(1, MAX_MODEL_ATTEMPTS + 1): + try: + # Get response from LLM + log.debug( + "Scientist %s: LLM attempt %d/%d", + self._scientist_id, + model_attempt, + MAX_MODEL_ATTEMPTS, + ) + response = await self._model_client.create( + conversation_history, + json_output=True, + ) + except Exception as exc: # pragma: no cover + last_error = exc + log.warning( + "Tool-assisted scientist %s failed to get response on attempt %d: %s", + self._scientist_id, + model_attempt, + exc, + ) + continue + + response_content = str(getattr(response, "content", "") or "").strip() + if not response_content: + last_error = ValueError("Empty response content") + log.warning( + "Tool-assisted scientist %s received empty response on attempt %d", + self._scientist_id, + model_attempt, + ) + continue + + log.debug( + "Scientist %s: Received response (first 200 chars): %s", + self._scientist_id, + response_content[:200], + ) + log.debug( + "Scientist %s: Raw JSON with special chars visible (first 500 chars): %s", + self._scientist_id, + repr(response_content[:500]), + ) + + try: + thought, code, code_output, final_answer, numerical_answer = ( + self._extract_solution_components(response_content) + ) + last_valid_components = ( + thought, + code, + code_output, + final_answer, + numerical_answer, + ) + + # Log the extracted code to see if parsing is correct + if code: + log.debug( + "Scientist %s: Extracted code (first 200 chars, repr): %s", + self._scientist_id, + repr(code[:200]), + ) + last_valid_components = ( + thought, + code, + code_output, + final_answer, + numerical_answer, + ) + + # If no code (empty string), we're done + if not code: + log.info( + "Scientist %s: No code to execute, returning solution", + self._scientist_id, + ) + return thought, code, code_output, final_answer, numerical_answer + + # Code is present - attempt execution with retry loop + log.info( + "Scientist %s: Code detected, starting execution loop", + self._scientist_id, + ) + + # Clean up common code issues before execution + # Sometimes LLM generates code with literal \n that should be newlines + if "\\n" in code: + # Replace literal \n with actual newlines + # This is a heuristic fix for common JSON escaping issues + # TODO: Investigate why current json parsing doesn't handle this. + code = code.replace("\\n", "\n") + log.debug( + "Scientist %s: Applied \\n replacement to clean up code", + self._scientist_id, + ) + + log.debug( + "Scientist %s: Generated code:\n%s\n%s\n%s", + self._scientist_id, + "-" * 60, + code, + "-" * 60, + ) + + # Increment total execution attempts + total_exec_attempts += 1 + log.info( + "Scientist %s: Code execution attempt %d/%d", + self._scientist_id, + total_exec_attempts, + MAX_CODE_EXECUTION_ATTEMPTS, + ) + + execution_result = self._toolkit.execute_code(code) + # Convert dict to object-like access + execution_result = type( + "obj", + (object,), + { + "success": execution_result["success"], + "output": execution_result["output"], + "error": execution_result.get("error"), + }, + )() + + if execution_result.success: + code_output = execution_result.output + log.info( + "Scientist %s: Code execution successful", + self._scientist_id, + ) + log.debug( + "Scientist %s: Code output:\n%s\n%s\n%s", + self._scientist_id, + "-" * 60, + code_output[:500], + "-" * 60, + ) + + # Extract numerical answer from code output + # instead of using model's approximation + extracted_numerical = self._extract_numerical_from_code_output( + code_output + ) + if extracted_numerical is not None: + log.info( + "Scientist %s: Overriding numerical_answer (was: %s, now: %s from code output)", + self._scientist_id, + numerical_answer, + extracted_numerical, + ) + numerical_answer = extracted_numerical + + return thought, code, code_output, final_answer, numerical_answer + + # Code execution failed + code_output = f"ERROR: {execution_result.error}" + log.warning( + "Scientist %s: Code execution failed (attempt %d/%d): %s", + self._scientist_id, + total_exec_attempts, + MAX_CODE_EXECUTION_ATTEMPTS, + execution_result.error, + ) + log.debug( + "Scientist %s: Full error:\n%s\n%s\n%s", + self._scientist_id, + "-" * 60, + execution_result.error, + "-" * 60, + ) + + # If we've exhausted code execution attempts, break out + if total_exec_attempts >= MAX_CODE_EXECUTION_ATTEMPTS: + log.error( + "Scientist %s: Max code execution attempts (%d) exhausted", + self._scientist_id, + MAX_CODE_EXECUTION_ATTEMPTS, + ) + return thought, code, code_output, final_answer, numerical_answer + + # Provide feedback to LLM for another attempt + log.info( + "Scientist %s: Providing error feedback to LLM for code correction", + self._scientist_id, + ) + + # Construct enhanced error feedback with actionable hints + error_msg = str(execution_result.error) + hints = [] + + if ( + "unterminated string" in error_msg.lower() + or "eol while scanning" in error_msg.lower() + ): + hints.append( + "- Use triple-quoted strings for multi-line output: print('''text''')" + ) + hints.append( + "- Avoid backslashes in strings; use raw strings r'' if needed" + ) + hints.append( + "- For print statements, use separate print() calls instead of \\n in strings" + ) + elif "syntaxerror" in error_msg.lower(): + hints.append( + "- Review Python syntax, especially quotes, parentheses, and indentation" + ) + hints.append("- Check for unmatched brackets or quotes") + elif "nameerror" in error_msg.lower(): + hints.append("- Ensure all imports are at the top of the code") + hints.append("- Check that all variables are defined before use") + elif ( + "importerror" in error_msg.lower() + or "modulenotfounderror" in error_msg.lower() + ): + hints.append( + "- Only use approved libraries: sympy, numpy, scipy, math, fractions, decimal" + ) + + hints_text = ( + "\n".join(hints) + if hints + else "- Carefully review the error message above" + ) + + feedback_prompt = f"""Your previous code execution failed with the following error: + +ERROR: {execution_result.error} + +Failed code: +```python +{code} +``` + +ACTIONABLE FIXES: +{hints_text} + +IMPORTANT: When writing code in JSON: +- Use simple print() statements on separate lines +- Avoid LaTeX notation in code comments or strings +- Use triple-quoted strings for multi-line output: print('''result''') +- Keep code focused on numerical computation only + +Return your corrected solution in the same JSON format with fixed code.""" + + feedback_message = UserMessage(content=feedback_prompt, source="user") + conversation_history.append(feedback_message) + + # Continue to next model attempt for corrected code + + except Exception as exc: + last_error = exc + log.warning( + "Tool-assisted scientist %s failed to parse response on attempt %d: %s", + self._scientist_id, + model_attempt, + exc, + ) + log.debug("Full exception: %s", traceback.format_exc()) + continue + + # If we have a last valid parse, return it (even if code failed) + if last_valid_components is not None: + log.warning( + "Scientist %s: Returning last valid components after exhausting attempts", + self._scientist_id, + ) + return last_valid_components + + raise RuntimeError( + f"Tool-assisted scientist {self._scientist_id} could not obtain valid " + f"response after {MAX_MODEL_ATTEMPTS} attempts" + ) from last_error + + @message_handler + async def handle_task_solution_request( + self, message: TaskSolutionRequest, ctx: MessageContext + ) -> None: + """Handle initial task solution request with code execution.""" + with self._langfuse_client.start_as_current_span( + name=f"tool_assisted_scientist_{self._scientist_id}_initial_solution" + ) as span: + try: + msg = ( + f"Tool-assisted scientist {self._scientist_id} handling initial " + f"solution request for task: {message.task_id}, " + f"capability: {message.capability_name}, area: {message.area_name}, " + f"round: {message.round_number}" + ) + log.info(msg) + span.update( + metadata={ + "solution_request_received": msg, + "scientist_id": self._scientist_id, + "task_id": message.task_id, + "capability": message.capability_name, + "area": message.area_name, + "round": message.round_number, + "tool_assisted": True, + } + ) + + # Step 1: Prepare tool context for problem + log.info(f"Scientist {self._scientist_id}: Preparing tool context") + tool_context = await self._toolkit.prepare_tools(message.problem) + + span.update( + metadata={ + "tool_selection_needs_tools": tool_context.get("needs_tools"), + "tool_selection_reasoning": tool_context.get("reasoning"), + "selected_tools": tool_context.get("selected_libraries", []), + } + ) + + # Step 2: Format tool context for prompt + tool_context_str = self._toolkit.format_tool_context(tool_context) + + # Step 3: Format prompt + prompt = TOOL_ASSISTED_ROUND_1_PROMPT.format( + problem_text=message.problem, tool_context=tool_context_str + ) + + system_message = SystemMessage(content=TOOL_ASSISTED_SYSTEM_MESSAGE) + user_message = UserMessage(content=prompt, source="user") + + ( + thought, + code, + code_output, + final_answer, + numerical_answer, + ) = await self._generate_solution_with_code_execution( + system_message, user_message + ) + + # Debug log before creating solution + log.debug( + "Scientist %s: About to create solution - code present: %s, code_output present: %s", + self._scientist_id, + bool(code), + bool(code_output), + ) + if code: + log.debug( + "Scientist %s: Code length: %d characters", + self._scientist_id, + len(code), + ) + + # Create solution with code execution metadata + solution = ToolAssistedAgentSolution( + agent_id=self._scientist_id, + task_id=message.task_id, + thought=thought, + final_answer=final_answer, + numerical_answer=numerical_answer, + round_number=message.round_number, + capability_name=message.capability_name, + area_name=message.area_name, + code=code if code is not None else "", + code_output=code_output if code_output is not None else "", + ) + + # Debug log after creating solution + log.debug( + "Scientist %s: Created solution - code in solution: %s, code_output in solution: %s", + self._scientist_id, + bool(solution.code), + bool(solution.code_output), + ) + + await self.publish_message(solution, topic_id=DefaultTopicId()) + + span.update( + metadata={ + "solution_generated": ( + f"Tool-assisted scientist {self._scientist_id} generated " + f"solution for task {message.task_id}" + ), + "code_executed": bool(code), + "code_success": code_output + and not code_output.startswith("ERROR:"), + } + ) + + except Exception as e: + msg = ( + f"Error in tool-assisted scientist {self._scientist_id} " + f"task solution request: {str(e)}" + ) + log.error(msg) + log.error(traceback.format_exc()) + span.update(metadata={"error": msg}) + + @message_handler + async def handle_agent_revision_request( + self, message: AgentRevisionRequest, ctx: MessageContext + ) -> None: + """Handle revision request with code execution capabilities.""" + with self._langfuse_client.start_as_current_span( + name=f"tool_assisted_scientist_{self._scientist_id}_round_{message.round_number}" + ) as span: + try: + msg = ( + f"Tool-assisted scientist {self._scientist_id} handling revision " + f"request for task: {message.task_id}, " + f"capability: {message.capability_name}, area: {message.area_name}, " + f"round: {message.round_number}" + ) + log.info(msg) + span.update( + metadata={ + "revision_request_received": msg, + "scientist_id": self._scientist_id, + "task_id": message.task_id, + "round": message.round_number, + "num_other_solutions": len(message.other_solutions), + "tool_assisted": True, + } + ) + + other_solutions_text = "\n\n".join( + [ + ( + f"Scientist {sol['agent_id']}: Reasoning: {sol['thought']}, " + f"Final solution: {sol['final_answer']}" + ) + for sol in message.other_solutions + if sol["agent_id"] != self._scientist_id + ] + ) + + # Format prompt + prompt = TOOL_ASSISTED_SUBSEQUENT_ROUNDS_PROMPT.format( + other_solutions=other_solutions_text, + problem_text=message.problem, + ) + + system_message = SystemMessage(content=TOOL_ASSISTED_SYSTEM_MESSAGE) + user_message = UserMessage(content=prompt, source="user") + + ( + thought, + code, + code_output, + final_answer, + numerical_answer, + ) = await self._generate_solution_with_code_execution( + system_message, user_message + ) + + solution = ToolAssistedAgentSolution( + agent_id=self._scientist_id, + task_id=message.task_id, + thought=thought, + final_answer=final_answer, + numerical_answer=numerical_answer, + round_number=message.round_number, + capability_name=message.capability_name, + area_name=message.area_name, + code=code if code is not None else "", + code_output=code_output if code_output is not None else "", + ) + + await self.publish_message(solution, topic_id=DefaultTopicId()) + + span.update( + metadata={ + "revision_generated": ( + f"Tool-assisted scientist {self._scientist_id} generated " + f"revision for task {message.task_id}" + ), + "code_executed": bool(code), + "code_success": code_output + and not code_output.startswith("ERROR:"), + } + ) + + except Exception as e: + msg = ( + f"Error in tool-assisted scientist {self._scientist_id} " + f"agent revision request: {str(e)}" + ) + log.error(msg) + log.error(traceback.format_exc()) + span.update(metadata={"error": msg}) diff --git a/src/tools/README.md b/src/tools/README.md new file mode 100644 index 0000000..ca8b544 --- /dev/null +++ b/src/tools/README.md @@ -0,0 +1,265 @@ +# Scientific Tools Module + +A comprehensive toolkit for integrating scientific computing capabilities into LLM-based research pipelines. The module provides safe Python code execution, intelligent tool selection, and documentation-aware context retrieval for mathematical, scientific, and financial computations. + +## Overview + +The tools module consists of four main components: + +1. **`toolkit.py`**: High-level orchestration layer that coordinates tool selection and code execution +2. **`executor.py`**: Safe Python code execution with import restrictions and error handling +3. **`docs.py`**: Structure-aware documentation retrieval from HTML files +4. **`definitions.py`**: Library configurations and metadata for scientific computing packages + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ ScientificToolKit │ +│ ┌─────────────────────┐ ┌────────────────────┐ │ +│ │ Tool Selection │ │ Code Execution │ │ +│ │ (LLM-based) │ │ (PythonExecutor) │ │ +│ └─────────────────────┘ └────────────────────┘ │ +│ ↓ ↑ │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ ScientificDocRetriever (Optional RAG) │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### Operating Modes + +The toolkit supports two modes optimized for different use cases: + +**1. RAG Mode (enable_rag=True)** +- Retrieves full function signatures and documentation from local HTML files +- Provides precise, deterministic context to the LLM +- Best for: Accuracy-critical tasks, evaluation benchmarks +- Trade-off: Slightly higher latency and token usage + +**2. Parametric Mode (enable_rag=False)** +- Relies on model's built-in knowledge of scientific libraries +- Fast, lightweight context generation +- Best for: High-throughput generation, rapid prototyping +- Trade-off: May hallucinate less common function signatures + +## Quick Start + +### Basic Usage + +```python +from autogen_core.models import ChatCompletionClient +from src.tools.toolkit import ScientificToolKit + +# Initialize with your LLM client +model_client = ChatCompletionClient(...) + +# Create toolkit instance (uses parametric mode by default) +toolkit = ScientificToolKit(model_client=model_client) + +# Execute Python code directly +result = toolkit.execute_code(""" +import numpy as np + +# Coefficient matrix +A = np.array([[2, 3], [1, -1]]) +b = np.array([7, 1]) + +# Solve system +solution = np.linalg.solve(A, b) +print(f"x = {solution[0]}, y = {solution[1]}") +""") + +if result["success"]: + print(result["output"]) # x = 2.0, y = 1.0 +else: + print(f"Error: {result['error']}") +``` + +### Integration with Task Solver + +The toolkit integrates seamlessly with the agentic task solver pipeline: + +```python +from src.task_solver.tool_assisted_scientist import ToolAssistedScientist +from src.tools.toolkit import ScientificToolKit + +# Initialize toolkit (defaults to parametric mode) +toolkit = ScientificToolKit(model_client=model_client) + +# Create scientist agent with toolkit +scientist = ToolAssistedScientist( + scientist_id="scientist_1", + model_client=model_client, + toolkit=toolkit, # Inject toolkit dependency + moderator_topic_type="task_solver", +) + +# The scientist will automatically: +# 1. Generate code using the LLM's parametric knowledge +# 2. Execute code using toolkit.execute_code() +# 3. Handle errors and retry with feedback +``` + +## Module Reference + +### ScientificToolKit + +Primary interface for tool management and orchestration. + +#### Initialization + +```python +ScientificToolKit( + model_client: ChatCompletionClient, + docs_path: Path = Path("materials"), + enable_tool_selection: bool = False, + enable_rag: bool = False, +) +``` + +**Parameters:** +- `model_client`: LLM client (used only if enable_tool_selection=True) +- `docs_path`: Path to HTML documentation (used only if enable_rag=True) +- `enable_tool_selection`: Enable LLM-based necessity detection (default: False) +- `enable_rag`: **EXPERIMENTAL/WIP** - Enable HTML doc retrieval (default: False) + +#### Methods + +##### `async prepare_tools(problem_text: str) -> Dict[str, Any]` + +**Note:** Only needed if `enable_tool_selection=True`. Most users can skip this and execute code directly. + +Analyzes a problem to determine if computational tools are needed (requires an additional LLM call). + +**Returns:** +```python +{ + "needs_tools": bool, # Whether computation is required + "selected_modules": List[Dict], # Selected library modules (if tool_selection enabled) + "documentation": str, # Context for code generation + "reasoning": str, # LLM's reasoning +} +``` + +**Example:** +```python +# Only if enable_tool_selection=True +toolkit = ScientificToolKit(model_client=client, enable_tool_selection=True) +context = await toolkit.prepare_tools( + "Calculate the eigenvalues of the matrix [[1, 2], [3, 4]]" +) +# Returns: {"needs_tools": True, "reasoning": "...", ...} +``` + +##### `execute_code(code: str) -> Dict[str, Any]` + +Executes Python code with import restrictions and error handling. + +**Returns:** +```python +{ + "success": bool, # Whether execution succeeded + "output": str, # Standard output from code + "error": str|None, # Error message if failed +} +``` + +**Example:** +```python +result = toolkit.execute_code("print(2 ** 10)") +# Returns: {"success": True, "output": "1024\n", "error": None} +``` + +##### `format_tool_context(tool_context: Dict[str, Any]) -> str` + +Formats tool context for inclusion in LLM prompts. + +**Note:** Only used with `enable_tool_selection=True`. Formats tool context for LLM prompts. + +**Example:** +```python +# Only if enable_tool_selection=True +context = await toolkit.prepare_tools("Calculate sqrt(16)") +formatted = toolkit.format_tool_context(context) +# Returns formatted string for prompt injection +``` + +### PythonExecutor + +Low-level Python code execution with security controls. + +#### Initialization + +```python +PythonExecutor( + allowed_imports: Optional[List[str]] = None, + timeout: int = 30, +) +``` + +**Default allowed imports:** +- Scientific: `numpy`, `scipy`, `sympy`, `math`, `fractions`, `decimal`, `cmath` +- Statistical: `statsmodels` +- Financial: `numpy_financial`, `py_vollib`, `pypfopt`, `empyrical`, `arch` + +#### Methods + +##### `execute(code: str) -> CodeExecutionResult` + +Executes Python code in a controlled environment. + +**Example:** +```python +from src.tools.executor import PythonExecutor + +executor = PythonExecutor() +result = executor.execute(""" +import sympy as sp +x = sp.symbols('x') +derivative = sp.diff(x**2, x) +print(derivative) +""") + +print(result.success) # True +print(result.output) # "2*x\n" +``` + +##### `validate_syntax(code: str) -> tuple[bool, Optional[str]]` + +Validates Python syntax without execution. + +**Example:** +```python +is_valid, error = executor.validate_syntax("print('hello')") +# Returns: (True, None) + +is_valid, error = executor.validate_syntax("print('unclosed") +# Returns: (False, "Syntax error: unterminated string literal...") +``` + +### ScientificDocRetriever (WIP) + +Retrieves function signatures and documentation from HTML files. + +#### Structure + +Exploits naming conventions in scientific library documentation: +- **NumPy/SciPy**: `reference/generated/numpy.module.function.html` +- **SymPy**: `modules/topic/file.html` + +## Testing + +The module includes comprehensive test coverage: + +```bash +# Run all tools tests +pytest tests/tools/ -v + +# Run specific test suite +pytest tests/tools/test_toolkit.py -v +pytest tests/tools/test_executor.py -v + +# Integration testing +RUN_INTEGRATION_TESTS=1 pytest -v -s tests/tools/test_toolkit_integration.py +``` diff --git a/src/tools/__init__.py b/src/tools/__init__.py new file mode 100644 index 0000000..d28a6e2 --- /dev/null +++ b/src/tools/__init__.py @@ -0,0 +1,40 @@ +"""Scientific computing toolkit for research code. + +Simplified, flat structure for easy experimentation and prompt iteration. + +Core Components: +--------------- +- definitions.py: Tool and library definitions +- docs.py: Documentation retrieval from HTML files +- executor.py: Python code execution engine +- toolkit.py: Main ScientificToolKit class (combines selection + execution) + +Usage: +------ + from src.tools.toolkit import ScientificToolKit + + # Create toolkit + toolkit = ScientificToolKit( + model_client=my_client, + enable_tool_selection=True + ) + + # Use in scientist + scientist = ToolAssistedScientist(..., toolkit=toolkit) +""" + +from src.tools.definitions import PYTHON_SCIENTIFIC_TOOL, LibraryConfig, ToolDefinition +from src.tools.docs import ScientificDocRetriever +from src.tools.executor import CodeExecutionResult, PythonExecutor +from src.tools.toolkit import ScientificToolKit + + +__all__ = [ + "ScientificToolKit", + "ScientificDocRetriever", + "PythonExecutor", + "CodeExecutionResult", + "ToolDefinition", + "LibraryConfig", + "PYTHON_SCIENTIFIC_TOOL", +] diff --git a/src/tools/definitions.py b/src/tools/definitions.py new file mode 100644 index 0000000..d8cf503 --- /dev/null +++ b/src/tools/definitions.py @@ -0,0 +1,307 @@ +"""Tool definitions for scientific computing. + +Simple dataclasses without over-abstraction. Easy to read and modify. +""" + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +@dataclass +class LibraryConfig: + """Configuration for a library/module.""" + + name: str + import_name: str + description: str + docs_path: Optional[str] = None + common_functions: List[str] = field(default_factory=list) + use_cases: List[str] = field(default_factory=list) + + +@dataclass +class ToolDefinition: + """Complete definition of a tool.""" + + tool_id: str + name: str + description: str + libraries: List[LibraryConfig] + allowed_imports: List[str] + use_cases: List[str] = field(default_factory=list) + metadata: Dict[str, Any] = field(default_factory=dict) + + +# Library Configurations +NUMPY_CONFIG = LibraryConfig( + name="numpy", + import_name="numpy as np", + description="Fundamental package for numerical computing. Arrays, matrices, and mathematical functions.", + docs_path="numpy-html-1.17.0", + common_functions=[ + "np.array", + "np.linspace", + "np.zeros", + "np.ones", + "np.eye", + "np.linalg.eig", + "np.linalg.det", + "np.linalg.inv", + "np.linalg.solve", + "np.dot", + "np.cross", + "np.sin", + "np.cos", + "np.exp", + "np.log", + "np.sqrt", + ], + use_cases=[ + "Array operations and linear algebra", + "Matrix computations", + "Statistical computations", + ], +) + +SCIPY_CONFIG = LibraryConfig( + name="scipy", + import_name="scipy", + description="Scientific computing library. Integration, optimization, linear algebra, statistics.", + docs_path="scipy-html-1.17.0", + common_functions=[ + "scipy.integrate.quad", + "scipy.integrate.odeint", + "scipy.integrate.solve_ivp", + "scipy.optimize.minimize", + "scipy.optimize.fsolve", + "scipy.linalg.lu", + "scipy.linalg.qr", + "scipy.linalg.svd", + "scipy.stats.norm", + "scipy.interpolate.interp1d", + ], + use_cases=[ + "Numerical integration", + "Optimization and root finding", + "Advanced linear algebra", + ], +) + +SYMPY_CONFIG = LibraryConfig( + name="sympy", + import_name="sympy", + description="Symbolic mathematics library. Algebraic manipulation, calculus, equation solving.", + docs_path="sympy-docs-html-1.14.0", + common_functions=[ + "sympy.symbols", + "sympy.simplify", + "sympy.expand", + "sympy.factor", + "sympy.diff", + "sympy.integrate", + "sympy.solve", + "sympy.dsolve", + "sympy.Matrix", + "sympy.sin", + "sympy.cos", + "sympy.pi", + ], + use_cases=[ + "Symbolic differentiation and integration", + "Solving equations", + "Symbolic simplification", + ], +) + +MATH_CONFIG = LibraryConfig( + name="math", + import_name="math", + description="Python's standard math library.", + docs_path=None, + common_functions=[ + "math.sin", + "math.cos", + "math.sqrt", + "math.exp", + "math.log", + "math.pi", + ], + use_cases=["Basic mathematical operations"], +) + +FRACTIONS_CONFIG = LibraryConfig( + name="fractions", + import_name="fractions", + description="Rational number arithmetic.", + docs_path=None, + common_functions=["Fraction"], + use_cases=["Exact rational arithmetic"], +) + +DECIMAL_CONFIG = LibraryConfig( + name="decimal", + import_name="decimal", + description="High-precision decimal arithmetic.", + docs_path=None, + common_functions=["Decimal"], + use_cases=["High-precision calculations"], +) + +CMATH_CONFIG = LibraryConfig( + name="cmath", + import_name="cmath", + description="Complex number operations.", + docs_path=None, + common_functions=["cmath.sqrt", "cmath.exp", "cmath.log"], + use_cases=["Complex number operations"], +) + +# Financial Library Configurations +NUMPY_FINANCIAL_CONFIG = LibraryConfig( + name="numpy_financial", + import_name="numpy_financial as npf", + description="Financial functions for time value of money calculations (NPV, IRR, PMT, PV, FV, etc.).", + docs_path=None, + common_functions=[ + "npf.npv", + "npf.irr", + "npf.pmt", + "npf.pv", + "npf.fv", + "npf.nper", + "npf.rate", + "npf.ppmt", + "npf.ipmt", + ], + use_cases=[ + "Time Value of Money", + "Net Present Value calculations", + "Internal Rate of Return", + "Loan payment calculations", + ], +) + +PY_VOLLIB_GEN_CONFIG = LibraryConfig( + name="py_vollib_gen", + import_name="py_vollib", + description="Options pricing and Greeks using Black-Scholes model.", + docs_path=None, + common_functions=[ + "py_vollib.black_scholes.black_scholes", + "py_vollib.black_scholes.implied_volatility", + "py_vollib.black_scholes.greeks", + ], + use_cases=[ + "Options pricing", + "Implied volatility calculation", + "Greeks computation (delta, gamma, theta, vega, rho)", + ], +) + +PYPORTFOLIOOPT_CONFIG = LibraryConfig( + name="PyPortfolioOpt", + import_name="pypfopt", + description="Portfolio optimization and asset allocation library.", + docs_path=None, + common_functions=[ + "pypfopt.expected_returns.mean_historical_return", + "pypfopt.risk_models.CovarianceShrinkage", + "pypfopt.efficient_frontier.EfficientFrontier", + "pypfopt.objective_functions.sharpe_ratio", + ], + use_cases=[ + "Portfolio optimization", + "Risk-return analysis", + "Asset allocation", + "Efficient frontier computation", + ], +) + +EMPYRICAL_CONFIG = LibraryConfig( + name="empyrical", + import_name="empyrical", + description="Performance statistics and risk metrics for financial analysis.", + docs_path=None, + common_functions=[ + "empyrical.sharpe_ratio", + "empyrical.sortino_ratio", + "empyrical.max_drawdown", + "empyrical.annual_return", + "empyrical.alpha_beta", + "empyrical.beta", + ], + use_cases=[ + "Performance metrics", + "Risk analysis", + "Sharpe and Sortino ratios", + "Drawdown analysis", + ], +) + +ARCH_CONFIG = LibraryConfig( + name="arch", + import_name="arch", + description="Volatility modeling and time series analysis.", + docs_path=None, + common_functions=[ + "arch.univariate.GARCH", + "arch.univariate.EGARCH", + "arch.univariate.ConstantMean", + "arch.univariate.HARX", + ], + use_cases=[ + "GARCH/EGARCH volatility modeling", + "Time series forecasting", + "Conditional variance estimation", + ], +) + +# The Tool Definition +PYTHON_SCIENTIFIC_TOOL = ToolDefinition( + tool_id="python_code_execution", + name="Python Code Execution", + description="Execute Python code with math, scientific, and financial computing libraries including SymPy, NumPy, SciPy, numpy_financial, py_vollib, PyPortfolioOpt, empyrical, and arch.", + libraries=[ + SYMPY_CONFIG, + NUMPY_CONFIG, + SCIPY_CONFIG, + MATH_CONFIG, + FRACTIONS_CONFIG, + DECIMAL_CONFIG, + CMATH_CONFIG, + NUMPY_FINANCIAL_CONFIG, + PY_VOLLIB_GEN_CONFIG, + PYPORTFOLIOOPT_CONFIG, + EMPYRICAL_CONFIG, + ARCH_CONFIG, + ], + allowed_imports=[ + "sympy", + "numpy", + "scipy", + "math", + "fractions", + "decimal", + "cmath", + "datetime", + "numpy_financial", + "py_vollib", + "pypfopt", + "empyrical", + "arch", + "statsmodels", + ], + use_cases=[ + "Solving differential equations", + "Matrix operations and linear algebra", + "Numerical integration", + "Symbolic manipulation", + "Optimization problems", + "Time Value of Money calculations (NPV, IRR, XNPV, XIRR)", + "Options pricing and Greeks", + "Portfolio optimization", + "Performance metrics and risk analysis", + "Volatility modeling (GARCH/EGARCH)", + ], + metadata={"timeout": 30}, +) diff --git a/src/tools/docs.py b/src/tools/docs.py new file mode 100644 index 0000000..9eb29f3 --- /dev/null +++ b/src/tools/docs.py @@ -0,0 +1,209 @@ +"""Documentation retrieval from local HTML files. + +Structure-aware RAG that exploits naming conventions of HTML documentation +to deterministically build a Library -> Module -> Function index. +""" + +import logging +from collections import defaultdict +from pathlib import Path +from typing import Dict, List + +from bs4 import BeautifulSoup, Tag + + +log = logging.getLogger("tools.docs") + + +class ScientificDocRetriever: + """ + Parse local HTML documentation directories. + + Build a structured index of Libraries -> Modules -> Functions. + """ + + def __init__(self, docs_base_path: Path): + self.docs_path = docs_base_path + self._index: Dict[str, Dict[str, List[str]]] = {} + self._file_map: Dict[str, Path] = {} + + self._build_index() + + def _build_index(self) -> None: + """Build index based on 'reference/generated' file patterns.""" + # 1. Index NumPy and SciPy (Pattern: pkg.submodule.function.html) + for lib in ["numpy", "scipy"]: + # Find versioned directory (e.g., numpy-html-1.17.0) + lib_dir = next(self.docs_path.glob(f"{lib}-html-*"), None) + if not lib_dir: + log.warning(f"Documentation directory not found for {lib}") + continue + + # Target specific reference directory to avoid tutorials/dev noise + generated_dir = lib_dir / "reference" / "generated" + if generated_dir.exists(): + if lib not in self._index: + self._index[lib] = defaultdict(list) + + for html_file in generated_dir.glob("*.html"): + name = html_file.stem # e.g., "numpy.linalg.eig" + + if name.startswith("index") or name.startswith("gallery"): + continue + + parts = name.split(".") + if len(parts) >= 3: + # numpy.linalg.eig -> module="linalg", func="eig" + module = parts[1] + func = parts[-1] + + self._index[lib][module].append(func) + self._file_map[f"{lib}.{module}.{func}"] = html_file + + log.info(f"Indexed {lib}: {len(self._index[lib])} modules") + + # 2. Index SymPy (Pattern: modules/topic/file.html) + sympy_dir = next(self.docs_path.glob("sympy-docs-html-*"), None) + if sympy_dir: + modules_dir = sympy_dir / "modules" + if modules_dir.exists(): + if "sympy" not in self._index: + self._index["sympy"] = defaultdict(list) + + # SymPy organizes by folder names in 'modules/' + for category_dir in modules_dir.iterdir(): + if category_dir.is_dir() and not category_dir.name.startswith("_"): + topic = category_dir.name # e.g., "matrices", "solvers" + for html_file in category_dir.glob("*.html"): + if html_file.stem != "index": + self._index["sympy"][topic].append(html_file.stem) + self._file_map[f"sympy.{topic}.{html_file.stem}"] = ( + html_file + ) + + log.info(f"Indexed sympy: {len(self._index['sympy'])} modules") + + def get_library_overview(self) -> str: + """Return a high-level summary of available modules.""" + overview = [] + for lib, modules in self._index.items(): + overview.append(f"### {lib.upper()}") + # List modules, sorted alphabetically + mod_list = sorted(modules.keys()) + overview.append(f"Available Modules: {', '.join(mod_list)}\n") + return "\n".join(overview) + + def get_full_module_context(self, library: str, module: str) -> str: + """ + Retrieve signatures for ALL functions in a module. + + Intended for high-context models (Gemini Flash). + """ + if library not in self._index or module not in self._index[library]: + return f"Error: Module {library}.{module} not found." + + # SORT the functions so the prompt is deterministic + functions = sorted(self._index[library][module]) + context_blocks = [f"--- DOCUMENTATION FOR {library}.{module} ---"] + + for func in functions: + key = f"{library}.{module}.{func}" + file_path = self._file_map.get(key) + if file_path: + signature = self._extract_signature_from_html(file_path) + if signature: + context_blocks.append(signature) + + return "\n\n".join(context_blocks) + + def _extract_signature_from_html(self, path: Path) -> str: + """Extract signature and brief description from HTML.""" + try: + with open(path, "r", encoding="utf-8") as f: + soup = BeautifulSoup(f.read(), "html.parser") + + extracted_content = [] + + # Find Main Definitions + definitions = soup.find_all( + "dl", class_=["function", "class", "method"] + ) + + for dl in definitions: + # Type narrowing: ensure dl is a Tag + if not isinstance(dl, Tag): + continue + + # 1. Get the signature (dt) + dt = dl.find("dt") + if not dt or not isinstance(dt, Tag): + continue + + # CLEANUP: Remove ¶, [source], and extra whitespace + raw_sig = ( + dt.get_text().replace("¶", "").replace("[source]", "").strip() + ) + sig = " ".join(raw_sig.split()) + + # FILTER 1: Skip private/magic methods in the main definition + # (e.g. skip scipy.sparse.bsr_matrix.__add__) + func_name = sig.split("(")[0].split(".")[-1] + if func_name.startswith("_"): + continue + + # 2. Get the description (dd) - First sentence only, max 120 chars + desc_text = "" + dd = dl.find("dd") + if dd and isinstance(dd, Tag): + p = dd.find("p") + if p and isinstance(p, Tag): + raw_desc = " ".join(p.get_text().split()) + # Split by period to get first sentence, or cap at 120 chars + if "." in raw_desc: + desc_text = raw_desc.split(".")[0] + "." + else: + desc_text = raw_desc + + if len(desc_text) > 120: + desc_text = desc_text[:117] + "..." + + # FORMAT: Dense one-liner + # e.g., "numpy.linalg.eig(a): Compute eigenvalues." + entry = f"{sig}: {desc_text}" if desc_text else sig + extracted_content.append(entry) + + # 3. SYMPY/CLASS HANDLING: Look for methods inside + # Only do this if it's a class definition + class_attr = dl.get("class") + if ( + isinstance(class_attr, list) and "class" in class_attr + ) or "sympy" in str(path): + methods = dl.find_all("dl", class_="method") + for method in methods: + if not isinstance(method, Tag): + continue + m_dt = method.find("dt") + if m_dt and isinstance(m_dt, Tag): + m_sig_raw = ( + m_dt.get_text() + .replace("¶", "") + .replace("[source]", "") + .strip() + ) + m_sig = " ".join(m_sig_raw.split()) + + # FILTER 2: Strict method filtering + # Skip __init__, __add__, _private_method + method_name = m_sig.split("(")[0].split(".")[-1] + if method_name.startswith("_"): + continue + + extracted_content.append( + f" . {method_name}(...): Method" + ) + # Note: We omit method descriptions to save tokens, + # relying on the method name being descriptive. + + except Exception: + pass + return "" diff --git a/src/tools/executor.py b/src/tools/executor.py new file mode 100644 index 0000000..55032a4 --- /dev/null +++ b/src/tools/executor.py @@ -0,0 +1,265 @@ +"""Python code executor with restricted imports.""" + +import ast +import contextlib +import sys +import traceback +from dataclasses import dataclass, field +from io import StringIO +from typing import Any, Dict, List, Optional + + +@dataclass +class CodeExecutionResult: + """Result of code execution.""" + + success: bool + output: str + error: Optional[str] = None + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + """Convert result to dictionary.""" + return { + "success": self.success, + "output": self.output, + "error": self.error, + "metadata": self.metadata, + } + + +class PythonExecutor: + """Execute Python code with restricted imports for safe execution. + + Attributes + ---------- + allowed_imports : List[str] + Set of allowed module names for import. + timeout : int + Maximum execution time in seconds (not enforced in basic implementation). + """ + + def __init__(self, allowed_imports: Optional[List[str]] = None, timeout: int = 30): + """Initialize the Python executor. + + Parameters + ---------- + allowed_imports : Optional[List[str]] + List of allowed module names. If None, uses default scientific libs. + timeout : int + Maximum execution time in seconds. + """ + if allowed_imports is None: + allowed_imports = [ + "sympy", + "numpy", + "scipy", + "math", + "fractions", + "decimal", + "cmath", + "datetime", + "numpy_financial", + "py_vollib", + "pypfopt", + "empyrical", + "arch", + "statsmodels", + ] + + self._allowed_imports = set(allowed_imports) + self._timeout = timeout + + @property + def supported_libraries(self) -> List[str]: + """List of supported libraries/modules.""" + return list(self._allowed_imports) + + def validate_syntax(self, code: str) -> tuple[bool, Optional[str]]: + """Validate Python syntax. + + Parameters + ---------- + code : str + Python code to validate. + + Returns + ------- + tuple[bool, Optional[str]] + (is_valid, error_message) + """ + try: + ast.parse(code) + return True, None + except SyntaxError as e: + return False, f"Syntax error at line {e.lineno}: {e.msg}" + except Exception as e: + return False, f"Validation error: {str(e)}" + + def _validate_imports(self, code: str) -> tuple[bool, Optional[str]]: + """Validate that only allowed imports are used. + + Parameters + ---------- + code : str + Python code to validate. + + Returns + ------- + tuple[bool, Optional[str]] + (is_valid, error_message) + """ + try: + tree = ast.parse(code) + for node in ast.walk(tree): + if isinstance(node, (ast.Import, ast.ImportFrom)): + if isinstance(node, ast.ImportFrom): + module_name = node.module + else: + module_name = node.names[0].name if node.names else None + + if module_name: + base_module = module_name.split(".")[0] + if base_module not in self._allowed_imports: + return False, ( + f"Import of '{module_name}' is not allowed. " + f"Allowed modules: {', '.join(sorted(self._allowed_imports))}" + ) + return True, None + except Exception as e: + return False, f"Import validation error: {str(e)}" + + def _create_restricted_globals(self) -> Dict[str, Any]: + """Create a restricted global namespace for code execution. + + Returns + ------- + Dict[str, Any] + Dictionary with restricted builtins and allowed imports. + """ + # Restricted builtins - only safe operations + safe_builtins = { + "__import__": __import__, + "print": print, + "range": range, + "len": len, + "abs": abs, + "min": min, + "max": max, + "sum": sum, + "round": round, + "float": float, + "int": int, + "str": str, + "bool": bool, + "list": list, + "dict": dict, + "tuple": tuple, + "set": set, + "enumerate": enumerate, + "zip": zip, + "sorted": sorted, + "reversed": reversed, + "map": map, + "filter": filter, + "any": any, + "all": all, + "pow": pow, + "divmod": divmod, + "isinstance": isinstance, + "type": type, + "True": True, + "False": False, + "None": None, + } + + return { + "__builtins__": safe_builtins, + } + + def execute( + self, code: str, context: Optional[Dict[str, Any]] = None + ) -> CodeExecutionResult: + """Execute Python code with safety restrictions. + + Parameters + ---------- + code : str + Python code to execute. + context : Optional[Dict[str, Any]] + Additional context (currently unused). + + Returns + ------- + CodeExecutionResult + Result object containing success status, output, and any errors. + """ + # Validate syntax first + is_valid, error_msg = self.validate_syntax(code) + if not is_valid: + return CodeExecutionResult(success=False, output="", error=error_msg) + + # Validate imports + is_valid, error_msg = self._validate_imports(code) + if not is_valid: + return CodeExecutionResult(success=False, output="", error=error_msg) + + # Create restricted execution environment + restricted_globals = self._create_restricted_globals() + + # Pre-import allowed modules to make them available + for module_name in self._allowed_imports: + with contextlib.suppress(ImportError): + restricted_globals[module_name] = __import__(module_name) + + # Capture stdout + old_stdout = sys.stdout + sys.stdout = captured_output = StringIO() + + try: + # Execute the code + exec(code, restricted_globals) + output = captured_output.getvalue() + + return CodeExecutionResult( + success=True, output=output, error=None, metadata={"executor": "python"} + ) + + except Exception as e: + # Capture any execution errors + error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}" + + return CodeExecutionResult( + success=False, + output=captured_output.getvalue(), + error=error_msg, + metadata={"executor": "python"}, + ) + + finally: + # Restore stdout + sys.stdout = old_stdout + + +def execute_python_code( + code: str, allowed_imports: Optional[List[str]] = None, timeout: int = 30 +) -> Dict[str, Any]: + """Execute Python code. + + Parameters + ---------- + code : str + Python code to execute. + allowed_imports : Optional[List[str]] + List of allowed imports. + timeout : int + Maximum execution time in seconds. + + Returns + ------- + Dict[str, Any] + Dictionary with 'success', 'output', 'error', and 'metadata' keys. + """ + executor = PythonExecutor(allowed_imports=allowed_imports, timeout=timeout) + result = executor.execute(code) + return result.to_dict() diff --git a/src/tools/toolkit.py b/src/tools/toolkit.py new file mode 100644 index 0000000..2c9d07a --- /dev/null +++ b/src/tools/toolkit.py @@ -0,0 +1,269 @@ +"""Scientific Toolkit - Unified tool management for research code. + +All prompts, logic, and execution in one place for easy iteration. +Structure-aware RAG implementation for scientific computing. +""" + +import logging +from pathlib import Path +from typing import Any, Dict + +from autogen_core.models import ChatCompletionClient, SystemMessage, UserMessage + +from src.tools.docs import ScientificDocRetriever +from src.tools.executor import PythonExecutor +from src.utils.json_utils import parse_llm_json_response + + +log = logging.getLogger("tools.toolkit") + + +# ============================================================================== +# PROMPTS - Edit these directly to iterate on tool selection behavior +# ============================================================================== + +TOOL_SELECTION_PROMPT = """Analyze the following problem and identify the specific Library MODULES required to solve it. + +PROBLEM: +{problem_text} + +AVAILABLE LIBRARIES: +{tools_description} + +Respond with a JSON object: +{{ + "tool_necessity": true/false, // Set to true if ANY computation is required + "reasoning": "Brief explanation", + "selected_modules": [ + {{ + "library": "library_name", // e.g., "numpy" + "module": "module_name" // e.g., "linalg" + }} + ] +}} + +Return only valid JSON, no markdown formatting.""" + + +# Static description for Parametric Mode (saves tokens, relies on model knowledge) +PARAMETRIC_OVERVIEW = """ +- **NumPy**: Linear algebra, Fourier transforms, random number generation, matrices. +- **SciPy**: Optimization, integration, interpolation, eigenvalue problems, signal processing, statistical distributions. +- **SymPy**: Symbolic mathematics, equation solving, calculus, matrices, physics. +- **Standard Python**: Math, decimal, fractions. +""" + + +# ============================================================================== +# Main Toolkit Class +# ============================================================================== + + +class ScientificToolKit: + """Unified toolkit for scientific computing with Python. + + Primary mode (Parametric): Uses model's built-in knowledge for fast code generation. + Tool selection is disabled by default for direct code execution. + + Note: RAG mode (enable_rag=True) is experimental/WIP and not fully functional. + """ + + def __init__( + self, + model_client: ChatCompletionClient, + docs_path: Path = Path("materials"), + enable_tool_selection: bool = False, + enable_rag: bool = False, + ): + """Initialize toolkit. + + Parameters + ---------- + model_client : ChatCompletionClient + LLM for tool selection prompts (only used if enable_tool_selection=True). + docs_path : Path + Path to documentation files (only used if enable_rag=True). + enable_tool_selection : bool + If True, uses LLM to detect tool necessity. Default: False + enable_rag : bool + If True, retrieves HTML documentation. Default: False. + """ + self.model_client = model_client + self.enable_tool_selection = enable_tool_selection + self.enable_rag = enable_rag + + # Only initialize the heavy retriever if RAG is enabled + self.doc_retriever = ScientificDocRetriever(docs_path) if enable_rag else None + + # Setup code executor (always available) + self.executor = PythonExecutor( + allowed_imports=[ + "numpy", + "scipy", + "sympy", + "math", + "fractions", + "decimal", + "cmath", + "datetime", + "statsmodels", + # Financial libraries + "numpy_financial", + "py_vollib", + "pypfopt", + "empyrical", + "arch", + ] + ) + + log.info( + f"Initialized ScientificToolKit - " + f"Selection: {'enabled' if enable_tool_selection else 'disabled'}, " + f"RAG: {'enabled' if enable_rag else 'disabled (parametric)'}" + ) + + async def prepare_tools(self, problem_text: str) -> Dict[str, Any]: + """Analyze problem and prepare tool context. + + Default (Parametric mode): Uses model's built-in knowledge. + Experimental (RAG mode): Retrieves documentation from HTML files. + + Parameters + ---------- + problem_text : str + Problem statement to analyze. + + Returns + ------- + Dict[str, Any] + Tool context with keys: + - needs_tools: bool + - selected_modules: List[Dict] + - documentation: str (full module context or parametric note) + - reasoning: str + """ + # If tool selection is disabled, skip the entire selection process + if not self.enable_tool_selection: + log.info("Tool selection disabled - skipping module selection step") + return { + "needs_tools": True, + "selected_modules": [], + "documentation": "All libraries available. Use parametric knowledge to write code.", + "reasoning": "Tool selection disabled - direct code execution mode", + } + + # 1. Get Library Overview + if self.enable_rag and self.doc_retriever: + # High precision: exact list of files on disk + overview = self.doc_retriever.get_library_overview() + else: + # High speed: generic description of capabilities + overview = PARAMETRIC_OVERVIEW + + # 2. Module Selection via LLM (Same for both modes) + response = await self.model_client.create( + [ + SystemMessage(content="You are an expert scientific programmer."), + UserMessage( + content=TOOL_SELECTION_PROMPT.format( + problem_text=problem_text, tools_description=overview + ), + source="user", + ), + ], + json_output=True, + ) + + selection = parse_llm_json_response(response.content) + + # 3. Check Necessity (if enabled) + necessity = str(selection.get("tool_necessity", "false")).lower() in ( + "true", + "1", + "yes", + ) + + if self.enable_tool_selection and not necessity: + log.info("Tool necessity check: No tools needed") + return { + "needs_tools": False, + "selected_modules": [], + "documentation": "", + "reasoning": selection.get( + "reasoning", "No computational tools required" + ), + } + + # 4. Context Construction + doc_context = [] + selected_modules = selection.get("selected_modules", []) + + log.info(f"Selected modules: {selected_modules}") + + # BRANCH: Only fetch HTML content if RAG is enabled + if self.enable_rag and self.doc_retriever: + for item in selected_modules: + lib = item.get("library") + mod = item.get("module") + if lib and mod: + # Retrieve actual signatures from HTML + module_docs = self.doc_retriever.get_full_module_context(lib, mod) + doc_context.append(module_docs) + else: + # Parametric Mode: Add a lightweight reminder instead of full docs + doc_context.append( + "Tool selection based on parametric knowledge. Write code assuming standard library versions." + ) + + full_documentation = "\n\n".join(doc_context) + + log.info(f"Generated documentation context: {len(full_documentation)} chars") + + return { + "needs_tools": True, + "selected_modules": selected_modules, + "documentation": full_documentation, + "reasoning": selection.get("reasoning", "Computational tools selected"), + } + + def execute_code(self, code: str) -> Dict[str, Any]: + """Execute Python code using the executor. + + Parameters + ---------- + code : str + Python code to execute. + + Returns + ------- + Dict[str, Any] + Result with keys: success, output, error + """ + result = self.executor.execute(code) + return result.to_dict() + + def format_tool_context(self, tool_context: Dict[str, Any]) -> str: + """Format tool context for inclusion in scientist prompt. + + Parameters + ---------- + tool_context : Dict[str, Any] + Result from prepare_tools(). + + Returns + ------- + str + Formatted string for prompt. + """ + if not tool_context["needs_tools"]: + return f"Note: {tool_context['reasoning']}" + + parts = [ + "**AVAILABLE TOOLS:**", + f"Reasoning: {tool_context['reasoning']}", + "", + "**MODULE DOCUMENTATION:**", + tool_context["documentation"], + ] + + return "\n".join(parts) diff --git a/src/utils/json_utils.py b/src/utils/json_utils.py index 3d2fd77..1648ad6 100644 --- a/src/utils/json_utils.py +++ b/src/utils/json_utils.py @@ -44,7 +44,104 @@ def fix_common_json_errors(content: str) -> str: """Fix common JSON syntax errors.""" content = re.sub(r':\s*=\s*"', ':"', content) content = re.sub(r'(\w+):\s*"', r'"\1":"', content) - content = re.sub(r'\\(?!["\\/bfnrtu])', r"\\\\", content) + + # Fix LaTeX backslashes: escape backslashes that are not part of + # valid JSON escape sequences + # Valid JSON escapes: \", \\, \/, \b, \f, \n, \r, \t, \uXXXX + # LaTeX uses \(, \), \[, \], \epsilon, \delta, etc. + # which need to be escaped as \\(, \\), etc. + + # Process the content character by character to properly handle string boundaries + result = [] + i = 0 + in_string = False + + while i < len(content): + char = content[i] + + # Track when we enter/exit string values + if char == '"': + # Check if this quote is escaped by counting backslashes before it + # An escaped quote has an odd number of backslashes before it + backslash_count = 0 + j = i - 1 + while j >= 0 and content[j] == "\\": + backslash_count += 1 + j -= 1 + + # If odd number of backslashes, the quote is escaped + # (part of string content) + if backslash_count % 2 == 1: + result.append(char) + i += 1 + continue + + # This is a real quote - toggle string state + in_string = not in_string + result.append(char) + i += 1 + continue + + # Handle backslashes inside string values + if char == "\\" and in_string: + if i + 1 < len(content): + next_char = content[i + 1] + # Check for valid JSON escape sequences + # For single-character escapes: ", \, /, b, f, n, r, t + # We need to ensure they're not part of a longer sequence + # (e.g., \to should not match \t) + if next_char in '"\\/': + # Always valid single-char escapes + result.append(char) + result.append(next_char) + i += 2 + continue + if next_char in "bfnrt": + # Check if this is a complete escape (not part of longer sequence) + # Valid if followed by non-alphanumeric or end of string + if i + 2 >= len(content) or not ( + content[i + 2].isalnum() or content[i + 2] == "_" + ): + # Complete escape sequence (e.g., \t, \n, \r, \b, \f) + result.append(char) + result.append(next_char) + i += 2 + continue + # Otherwise it's part of a longer sequence + # (e.g., \to, \lim) - escape it + result.append("\\\\") + result.append(next_char) + i += 2 + continue + if next_char == "u" and i + 5 < len(content): + # Check for \uXXXX pattern + hex_part = content[i + 2 : i + 6] + if all(c in "0123456789abcdefABCDEF" for c in hex_part): + # Valid \uXXXX escape + result.append(char) + result.append(next_char) + result.append(hex_part) + i += 6 + continue + + # Invalid escape sequence + # (like LaTeX \(, \), \[, \], \epsilon, \to, etc.) + # Double-escape the backslash + result.append("\\\\") + result.append(next_char) + i += 2 + continue + # Backslash at end of content - escape it + result.append("\\\\") + i += 1 + continue + + # Regular character + result.append(char) + i += 1 + + content = "".join(result) + return re.sub(r",(\s*[}\]])", r"\1", content) @@ -67,6 +164,39 @@ def parse_llm_json_response(raw_content: Union[str, Any]) -> Dict[str, Any]: log.error(f"Failed to parse JSON response: {e}") log.error(f"Content length: {len(cleaned_content)} characters") + # Try to fix LaTeX backslash issues if the error is about invalid escape + if "Invalid \\escape" in str(e) or "Invalid escape" in str(e): + try: + log.warning("Attempting to fix LaTeX backslash escape issues") + # Apply more aggressive LaTeX backslash fixing + # Escape all backslashes in string values that aren't + # valid JSON escapes + fixed_content = cleaned_content + + # Use regex to find and fix invalid escapes in string values + # This pattern finds backslashes in string values + # and escapes invalid ones + def fix_escapes_in_string(match: re.Match[str]) -> str: + """Fix escapes within a JSON string value.""" + string_content = match.group(1) + # Escape backslashes not followed by valid JSON escape chars + fixed = re.sub( + r'\\(?!["\\/bfnrtu]|u[0-9a-fA-F]{4})', r"\\\\", string_content + ) + return f'"{fixed}"' + + # Find string values and fix them + # Pattern: "([^"\\]|\\.)*" - matches JSON string values + fixed_content = re.sub( + r'"((?:[^"\\]|\\.)*)"', fix_escapes_in_string, fixed_content + ) + + result = json.loads(fixed_content) + log.info("Successfully fixed LaTeX backslash issues") + return result if isinstance(result, dict) else {} + except Exception as fix_error: + log.error(f"Failed to fix LaTeX escape issues: {fix_error}") + try: if "Unterminated string" in str(e): last_complete = cleaned_content.rfind('"},') diff --git a/src/utils/tool_assisted_prompts.py b/src/utils/tool_assisted_prompts.py new file mode 100644 index 0000000..e7ae5d2 --- /dev/null +++ b/src/utils/tool_assisted_prompts.py @@ -0,0 +1,421 @@ +"""Prompts for tool-assisted task solving with code execution capabilities.""" + +# ============================================================================= +# TOOL-ASSISTED TASK SOLVING PROMPTS +# ============================================================================= + +TOOL_ASSISTED_SYSTEM_MESSAGE = """You are an expert problem solver with access to Python code execution capabilities. You can use the following libraries to assist in solving mathematical and financial problems: + +**Available Libraries:** +- **SymPy**: For symbolic mathematics (symbolic integration, differentiation, equation solving, etc.) +- **NumPy**: For numerical computations (arrays, linear algebra, numerical methods) +- **SciPy**: For scientific computing (numerical integration, optimization, ODE solving) +- **Math/Fractions/Decimal**: Standard Python mathematical libraries +- **Financial Libraries**: numpy_financial, py_vollib, pypfopt, empyrical, arch (for finance-specific computations) + +Always use the SIMPLEST tool that solves the problem correctly: +1. If basic arithmetic works → use standard Python operators +2. If you need standard math functions → use math library +3. If you need arrays/matrices → use NumPy +4. If you need symbolic manipulation → use SymPy +5. ONLY use financial libraries (numpy_financial, py_vollib, etc.) when the problem EXPLICITLY involves: + - Time value of money calculations (NPV, IRR, annuities) + - Option pricing models (Black-Scholes, Greeks) + - Portfolio optimization or risk metrics + - Bond pricing with yield curves + +**When to Use Code:** +- For complex calculations that benefit from computational tools +- When the problem requires numerical precision +- For problems involving symbolic manipulation or calculus + +**Response Format:** +You will respond in two stages: +1. First, generate code to solve the problem +2. After seeing the code output, format the final answer appropriately + +This ensures you can properly format answers for different question types (boolean, multiple choice, or numerical).""" + +# ============================================================================= +# STAGE 1: CODE GENERATION PROMPT +# ============================================================================= + +TOOL_ASSISTED_CODE_GENERATION_PROMPT = """PROBLEM: {problem_text} + +{tool_context} + +**CRITICAL - SIMPLICITY PRINCIPLE:** +Use the simplest approach that correctly solves the problem. Do NOT use specialized libraries unless the problem explicitly requires them: +- For basic arithmetic (percentages, ratios, simple formulas): Use Python operators or math library +- For algebra/calculus: Use SymPy +- For numerical arrays/linear algebra: Use NumPy +- For statistical calculations: Use SciPy +- For financial derivatives pricing (options, swaps): Use py_vollib or numpy_financial ONLY if the problem explicitly involves these instruments + +**Stage 1 Instructions - Generate Code:** +1. Analyze the problem and identify the simplest appropriate approach +2. Write Python code to solve the problem +3. Make sure your code prints the key results clearly + +DO NOT provide final_answer or numerical_answer yet - you will do that in Stage 2 after seeing the code output. + +**Available Tools:** +```python +# Standard Math (USE FIRST for basic calculations) +import math +from fractions import Fraction +from decimal import Decimal +from datetime import datetime, timedelta + +# SymPy - Symbolic Mathematics (for algebra/calculus) +from sympy import symbols, Function, Eq, dsolve, diff, integrate, simplify, solve, limit, series, sqrt, exp, log, sin, cos, tan, pi, E, I, oo +from sympy import Matrix, eye, zeros, ones, det, eigenvals, eigenvects +from sympy.abc import x, y, z, t, a, b, c, n + +# NumPy - Numerical Computing (for arrays/linear algebra) +import numpy as np +# Use: np.array, np.linalg.eig, np.linalg.det, np.linalg.inv, np.linalg.solve, etc. + +# SciPy - Scientific Computing (for optimization/integration) +from scipy import integrate, optimize, linalg +from scipy.integrate import odeint, solve_ivp, quad, dblquad +from scipy.stats import norm +# Use: integrate.quad, optimize.fsolve, scipy.stats.norm, etc. + +# Financial Computing Libraries (USE ONLY for derivative pricing problems) +import numpy_financial as npf +# Use ONLY for: Time value of money with given formulas (NPV, IRR, PMT) +# Example: npf.npv(rate, cashflows), npf.irr(cashflows) + +import py_vollib.black_scholes as bs +from py_vollib.black_scholes.greeks.analytical import delta, gamma, theta, vega +# Use ONLY for: Black-Scholes option pricing (calls/puts with volatility) +# Example: bs.black_scholes('c', S, K, t, r, sigma) + +from arch import arch_model +from arch.univariate import HARX +# Use ONLY for: GARCH/volatility modeling problems +# For multi-step forecasts: use method='simulation' + +import statsmodels.api as sm +# Use ONLY for: Regression, time series analysis +``` + +IMPORTANT: Return your response as raw JSON only. Do not wrap it in markdown code blocks. + +CRITICAL: Escape all backslashes in code with double backslashes (\\\\). + +**Stage 1 Response Format (thought + code only):** +{{ + "thought": "Your detailed reasoning about the approach", + "code": "Python code to solve the problem" +}} + +Respond with valid JSON only.""" + +# ============================================================================= +# STAGE 2: ANSWER FORMATTING PROMPT +# ============================================================================= + +TOOL_ASSISTED_ANSWER_FORMATTING_PROMPT = """Your code has been executed. Here is the output: + +``` +{code_output} +``` + +**Stage 2 Instructions - Format Final Answer:** +Based on the code output above, provide the final answer in the format appropriate for this question type: +- For **boolean questions**: Answer with Yes/No, True/False, or 1/0 as appropriate +- For **multiple choice questions**: Provide the letter (A/B/C/D) and/or the full text of the correct option +- For **numerical questions**: Provide the number with appropriate units and context + +**Question:** {problem_text} + +**Response Format:** +{{ + "final_answer": "Your complete answer formatted appropriately for the question type", + "numerical_answer": "The final numerical result if applicable, otherwise null" +}} + +IMPORTANT: Return raw JSON only. Do not wrap in markdown code blocks. + +Respond with valid JSON only.""" + +# ============================================================================= +# LEGACY PROMPTS (kept for backward compatibility with subsequent rounds) +# ============================================================================= + +TOOL_ASSISTED_ROUND_1_PROMPT_LEGACY = """PROBLEM: {problem_text} + +{tool_context} + +**CRITICAL - SIMPLICITY PRINCIPLE:** +Use the simplest approach that correctly solves the problem. Do NOT use specialized libraries unless the problem explicitly requires them: +- For basic arithmetic (percentages, ratios, simple formulas): Use Python operators or math library +- For algebra/calculus: Use SymPy +- For numerical arrays/linear algebra: Use NumPy +- For statistical calculations: Use SciPy +- For financial derivatives pricing (options, swaps): Use py_vollib or numpy_financial ONLY if the problem explicitly involves these instruments + +**Instructions:** +1. Explain your mathematical reasoning and approach +2. Write Python code using the SIMPLEST appropriate tools +3. Use the code output to inform your final answer +4. Provide a clear final numerical answer + +**Available Tools:** +```python +# Standard Math (USE FIRST for basic calculations) +import math +from fractions import Fraction +from decimal import Decimal +from datetime import datetime, timedelta + +# SymPy - Symbolic Mathematics (for algebra/calculus) +from sympy import symbols, Function, Eq, dsolve, diff, integrate, simplify, solve, limit, series, sqrt, exp, log, sin, cos, tan, pi, E, I, oo +from sympy import Matrix, eye, zeros, ones, det, eigenvals, eigenvects +from sympy.abc import x, y, z, t, a, b, c, n + +# NumPy - Numerical Computing (for arrays/linear algebra) +import numpy as np +# Use: np.array, np.linalg.eig, np.linalg.det, np.linalg.inv, np.linalg.solve, etc. + +# SciPy - Scientific Computing (for optimization/integration) +from scipy import integrate, optimize, linalg +from scipy.integrate import odeint, solve_ivp, quad, dblquad +from scipy.stats import norm +# Use: integrate.quad, optimize.fsolve, scipy.stats.norm, etc. + +# Financial Computing Libraries (USE ONLY for derivative pricing problems) +import numpy_financial as npf +# Use ONLY for: Time value of money with given formulas (NPV, IRR, PMT) +# Example: npf.npv(rate, cashflows), npf.irr(cashflows) + +import py_vollib.black_scholes as bs +from py_vollib.black_scholes.greeks.analytical import delta, gamma, theta, vega +# Use ONLY for: Black-Scholes option pricing (calls/puts with volatility) +# Example: bs.black_scholes('c', S, K, t, r, sigma) + +from arch import arch_model +from arch.univariate import HARX +# Use ONLY for: GARCH/volatility modeling problems +# For multi-step forecasts: use method='simulation' + +import statsmodels.api as sm +# Use ONLY for: Regression, time series analysis +``` + +IMPORTANT: Return your response as raw JSON only. Do not wrap it in markdown code blocks or add any formatting. + +CRITICAL: You MUST escape all backslashes in LaTeX expressions within JSON strings. Use double backslashes (\\\\). + +Provide your solution in JSON format: +{{ + "thought": "Your detailed mathematical reasoning. Identify which tool is most appropriate and why.", + "code": "Python code using simplest appropriate tools", + "code_output": null, + "final_answer": "Your complete answer", + "numerical_answer": "The final numerical result (if applicable, otherwise null)" +}} + +Respond with valid JSON only.""" + +TOOL_ASSISTED_SUBSEQUENT_ROUNDS_PROMPT = """These are the reasoning and solutions to the problem from other agents: + +{other_solutions} + +Using the solutions from other agents as additional information, can you provide your answer to the problem? + +The original problem is: {problem_text} + +**CRITICAL - SIMPLICITY PRINCIPLE:** +Use the simplest approach that correctly solves the problem. Review other agents' approaches - if they used complex libraries unnecessarily, prefer the simpler solution. + +**Instructions:** +1. Classify the problem type and identify the simplest correct approach +2. Review other agents' solutions - identify which tools they used and whether they were appropriate +3. Provide your solution using the simplest effective approach +4. Your answer may agree with, disagree with, or synthesize the other solutions + +**Available Tools:** +```python +# Standard Math (USE FIRST for basic calculations) +import math +from fractions import Fraction +from decimal import Decimal + +# SymPy (for algebra/calculus) +from sympy import symbols, solve, simplify, diff, integrate + +# NumPy (for arrays/linear algebra) +import numpy as np + +# SciPy (for optimization/integration) +from scipy import optimize, integrate, stats + +# Financial Libraries (USE ONLY for derivative pricing) +import numpy_financial as npf +import py_vollib.black_scholes as bs +from arch import arch_model +``` + +IMPORTANT: Return your response as raw JSON only. Use the simplicity principle - prefer basic Python over specialized libraries unless truly needed. + +CRITICAL: Escape all backslashes in LaTeX with double backslashes (\\\\). + +Provide your solution in JSON format: +{{ + "thought": "Your detailed reasoning. Review what other agents tried and choose the simplest effective approach.", + "code": "Python code using simplest appropriate tools", + "code_output": null, + "final_answer": "Your complete answer", + "numerical_answer": "The final numerical result (if applicable, otherwise null)" +}} + +Respond with valid JSON only.""" + +# ============================================================================= +# CODE EXECUTION EXAMPLES FOR DIFFERENT CAPABILITIES +# ============================================================================= + +CODE_EXAMPLES_ODE = """ +# Example: Solving a first-order ODE +from sympy import * +x = symbols('x') +y = Function('y') + +# Define ODE: dy/dx = xy with y(0) = 1 +ode = Eq(y(x).diff(x), x*y(x)) +general_solution = dsolve(ode, y(x)) +print("General solution:", general_solution) + +# Apply initial condition +C1 = symbols('C1') +particular = general_solution.rhs.subs(x, 0) +C_value = solve(particular - 1, C1)[0] +print("C value:", C_value) + +final_solution = general_solution.rhs.subs(C1, C_value) +print("Particular solution:", final_solution) + +# Verify by substitution +dy_dx = diff(final_solution, x) +rhs = x * final_solution +print("Verification:", simplify(dy_dx - rhs) == 0) +""" + +CODE_EXAMPLES_LINEAR_ALGEBRA = """ +# Example: Matrix operations and eigenvalues +import numpy as np +from scipy import linalg + +# Define matrix +A = np.array([[4, 2], [1, 3]]) +print("Matrix A:") +print(A) + +# Compute eigenvalues and eigenvectors +eigenvalues, eigenvectors = np.linalg.eig(A) +print("\\nEigenvalues:", eigenvalues) +print("Eigenvectors:") +print(eigenvectors) + +# Verify: A*v = λ*v +for i in range(len(eigenvalues)): + v = eigenvectors[:, i] + lam = eigenvalues[i] + print(f"\\nVerification for λ={lam}:") + print("A*v =", A @ v) + print("λ*v =", lam * v) + print("Equal?", np.allclose(A @ v, lam * v)) +""" + +CODE_EXAMPLES_INTEGRATION = """ +# Example: Symbolic and numerical integration +from sympy import * +from scipy import integrate as scipy_integrate + +x = symbols('x') + +# Symbolic integration +expr = sin(x) * exp(-x) +symbolic_result = integrate(expr, (x, 0, oo)) +print("Symbolic result:", symbolic_result) +print("Numerical value:", float(symbolic_result)) + +# Numerical integration for comparison +def integrand(x): + return np.sin(x) * np.exp(-x) + +numerical_result, error = scipy_integrate.quad(integrand, 0, np.inf) +print("\\nNumerical integration:", numerical_result) +print("Error estimate:", error) +""" + +CODE_EXAMPLES_DYNAMICAL_SYSTEMS = """ +# Example: Numerical simulation of ODE system +import numpy as np +from scipy.integrate import odeint + +# Define system: Lotka-Volterra equations +def lotka_volterra(state, t, alpha, beta, gamma, delta): + x, y = state + dx_dt = alpha * x - beta * x * y + dy_dt = delta * x * y - gamma * y + return [dx_dt, dy_dt] + +# Parameters +alpha, beta, gamma, delta = 1.0, 0.1, 1.5, 0.075 + +# Initial conditions +x0, y0 = 10, 5 +initial_state = [x0, y0] + +# Time points +t = np.linspace(0, 50, 1000) + +# Solve ODE +solution = odeint(lotka_volterra, initial_state, t, args=(alpha, beta, gamma, delta)) + +print("Initial state:", initial_state) +print("Final state:", solution[-1]) +print("Max prey population:", np.max(solution[:, 0])) +print("Max predator population:", np.max(solution[:, 1])) +""" + +CODE_EXAMPLES_COMPLEX_ANALYSIS = """ +# Example: Complex number operations +from sympy import * + +# Define complex numbers +z1 = 3 + 4*I +z2 = 1 - 2*I + +print("z1 =", z1) +print("z2 =", z2) + +# Operations +print("\\nz1 + z2 =", z1 + z2) +print("z1 * z2 =", expand(z1 * z2)) +print("z1 / z2 =", simplify(z1 / z2)) + +# Magnitude and argument +print("\\n|z1| =", abs(z1)) +print("arg(z1) =", arg(z1)) + +# Complex conjugate +print("\\nconjugate(z1) =", conjugate(z1)) + +# Euler's formula +theta = symbols('theta', real=True) +euler = exp(I * theta) +print("\\ne^(iθ) =", euler) +print("Expanded:", expand(euler, complex=True)) +""" + +# ============================================================================= +# BACKWARD COMPATIBILITY ALIAS +# ============================================================================= +# For existing code that imports TOOL_ASSISTED_ROUND_1_PROMPT +# This now uses the new two-stage architecture (Code Generation -> Answer Formatting) +TOOL_ASSISTED_ROUND_1_PROMPT = TOOL_ASSISTED_CODE_GENERATION_PROMPT diff --git a/tests/src/utils/test_json_utils_latex.py b/tests/src/utils/test_json_utils_latex.py new file mode 100644 index 0000000..72d1b03 --- /dev/null +++ b/tests/src/utils/test_json_utils_latex.py @@ -0,0 +1,183 @@ +r""" +Tests for the JSON utility functions related to LaTeX backslash handling. + +This module contains tests specifically for the changes made to handle LaTeX +expressions with backslashes in JSON strings. The main changes were: +- fix_common_json_errors: Character-by-character processing to correctly escape + LaTeX backslashes while preserving valid JSON escape sequences +- parse_llm_json_response: Fallback mechanism for "Invalid escape" errors + +The tests verify that: +- LaTeX expressions with backslashes are properly escaped + (e.g., \\(, \\), \\[, \\], \\to, \\lim) +- Valid JSON escape sequences (e.g., \\n, \\t, \\", \\\\) are preserved +- The fallback mechanism correctly handles invalid escape errors +""" + +import json + +from src.utils.json_utils import ( + fix_common_json_errors, + parse_llm_json_response, +) + + +class TestFixCommonJsonErrorsLaTeX: + """Test cases for fix_common_json_errors function - LaTeX backslash handling.""" + + def test_latex_parentheses_escaped(self): + """Test that LaTeX parentheses are properly escaped.""" + content = '{"thought": "The equation \\(x + y\\) is important"}' + result = fix_common_json_errors(content) + # Should double-escape: \\( becomes \\\\( + assert "\\\\(x + y\\\\)" in result or "\\\\(" in result + # Verify it can be parsed + parsed = json.loads(result) + assert "(" in parsed["thought"] or "\\(" in parsed["thought"] + + def test_latex_brackets_escaped(self): + """Test that LaTeX brackets are properly escaped.""" + content = '{"thought": "The equation \\[x + y = z\\] is important"}' + result = fix_common_json_errors(content) + # Should double-escape: \\[ becomes \\\\[ + assert "\\\\[" in result or "\\[" in result + # Verify it can be parsed + parsed = json.loads(result) + assert "[" in parsed["thought"] or "\\[" in parsed["thought"] + + def test_latex_commands_escaped(self): + r"""Test that LaTeX commands like \to, \lim are properly escaped.""" + content = '{"thought": "As x \\to 0, we have \\lim f(x)"}' + result = fix_common_json_errors(content) + # Should escape \to and \lim + assert "\\to" in result or "\\\\to" in result + # Verify it can be parsed + parsed = json.loads(result) + assert "to" in parsed["thought"] or "\\to" in parsed["thought"] + + def test_valid_json_escapes_preserved(self): + """Test that valid JSON escape sequences are preserved.""" + # Use raw string to ensure \n and \t are literal backslash+n and backslash+t + content = r'{"text": "Line 1\nLine 2\tTabbed"}' + result = fix_common_json_errors(content) + # Valid escapes should remain (as single backslash sequences) + assert r"\n" in result or "\n" in result + assert r"\t" in result or "\t" in result + # Verify it can be parsed and produces actual newline/tab characters + parsed = json.loads(result) + assert "\n" in parsed["text"] or "\\n" in parsed["text"] + assert "\t" in parsed["text"] or "\\t" in parsed["text"] + + def test_valid_json_quote_escape_preserved(self): + """Test that escaped quotes are preserved.""" + content = '{"text": "He said \\"hello\\""}' + result = fix_common_json_errors(content) + # Escaped quotes should remain + assert '\\"' in result + # Verify it can be parsed + parsed = json.loads(result) + assert '"' in parsed["text"] + + def test_valid_json_backslash_escape_preserved(self): + """Test that escaped backslashes are preserved.""" + content = '{"path": "C:\\\\Users\\\\file.txt"}' + result = fix_common_json_errors(content) + # Escaped backslashes should remain + assert "\\\\" in result + # Verify it can be parsed + parsed = json.loads(result) + assert "\\" in parsed["path"] + + def test_latex_in_nested_string(self): + """Test LaTeX handling in nested JSON strings.""" + content = '{"outer": {"inner": "The formula \\(a + b\\) is key"}}' + result = fix_common_json_errors(content) + # Should handle LaTeX in nested strings + parsed = json.loads(result) + assert "(" in parsed["outer"]["inner"] or "\\(" in parsed["outer"]["inner"] + + def test_mixed_valid_and_invalid_escapes(self): + """Test handling of mixed valid and invalid escape sequences.""" + # Use raw string to ensure proper escape handling + content = r'{"text": "Valid\nNewline and invalid\to LaTeX"}' + result = fix_common_json_errors(content) + # Valid escape should remain, invalid should be escaped + assert r"\n" in result or "\n" in result + # Verify it can be parsed + parsed = json.loads(result) + # Should contain newline character or escaped newline + assert "\n" in parsed["text"] or "\\n" in parsed["text"] + + +class TestParseLlmJsonResponseLaTeX: + """Test cases for parse_llm_json_response function - LaTeX and fallback handling.""" + + def test_parse_json_with_latex(self): + """Test parsing JSON containing LaTeX expressions.""" + content = '{"thought": "The equation \\(x + y\\) is important", "answer": "42"}' + result = parse_llm_json_response(content) + assert "thought" in result + assert "answer" in result + # The thought should contain the LaTeX (possibly escaped) + assert "x + y" in result["thought"] or "(" in result["thought"] + + def test_parse_json_with_latex_complex(self): + """Test parsing complex JSON with multiple LaTeX expressions.""" + content = ( + '{"thought": "We need to solve \\[x^2 + 2x + 1 = 0\\] ' + 'and find \\lim_{x \\to 0} f(x)", "answer": "x = -1"}' + ) + result = parse_llm_json_response(content) + assert "thought" in result + assert "answer" in result + assert "x = -1" in result["answer"] + + def test_parse_json_fallback_on_invalid_escape(self): + """Test that fallback mechanism works for invalid escape errors.""" + # This should trigger the fallback mechanism + content = '{"text": "Invalid escape\\to here"}' + result = parse_llm_json_response(content) + assert "text" in result + # Should successfully parse after fallback fix + + def test_parse_json_with_latex_in_multiple_fields(self): + """Test parsing JSON with LaTeX in multiple fields.""" + content = ( + '{"thought": "Consider \\(a + b\\)", ' + '"solution": "The answer is \\[x = 5\\]", ' + '"reasoning": "Using \\lim_{x \\to 0}"}' + ) + result = parse_llm_json_response(content) + assert "thought" in result + assert "solution" in result + assert "reasoning" in result + + +class TestLaTeXEdgeCases: + """Test edge cases for LaTeX backslash handling.""" + + def test_latex_at_string_boundary(self): + """Test LaTeX expressions at string boundaries.""" + content = '{"text": "\\(start\\) and \\(end\\)"}' + result = parse_llm_json_response(content) + assert "text" in result + + def test_latex_with_numbers(self): + """Test LaTeX expressions containing numbers.""" + content = '{"formula": "\\[x^2 + 2x + 1\\]"}' + result = parse_llm_json_response(content) + assert "formula" in result + + def test_escaped_quotes_with_latex(self): + """Test combination of escaped quotes and LaTeX.""" + content = '{"text": "He said \\"Consider \\(x + y\\)\\""}' + result = parse_llm_json_response(content) + assert "text" in result + assert '"' in result["text"] or "Consider" in result["text"] + + def test_newline_characters_in_latex(self): + """Test newline characters near LaTeX expressions.""" + content = '{"text": "Line 1\\n\\(x + y\\)\\nLine 2"}' + result = parse_llm_json_response(content) + assert "text" in result + assert "\n" in result["text"] diff --git a/tests/tools/__init__.py b/tests/tools/__init__.py new file mode 100644 index 0000000..2c7aff5 --- /dev/null +++ b/tests/tools/__init__.py @@ -0,0 +1 @@ +"""Unit tests package init.""" diff --git a/tests/tools/test_executor.py b/tests/tools/test_executor.py new file mode 100644 index 0000000..deda83c --- /dev/null +++ b/tests/tools/test_executor.py @@ -0,0 +1,116 @@ +"""Unit tests for Python executor.""" + +from src.tools.executor import PythonExecutor, execute_python_code + + +class TestPythonExecutor: + """Test suite for PythonExecutor.""" + + def test_simple_execution(self): + """Test executing simple Python code.""" + executor = PythonExecutor() + + code = "print('Hello, World!')" + result = executor.execute(code) + + assert result.success + assert "Hello, World!" in result.output + assert result.error is None + + def test_math_computation(self): + """Test mathematical computation.""" + executor = PythonExecutor() + + code = """ +import math +result = math.sqrt(16) +print(f"Result: {result}") +""" + result = executor.execute(code) + + assert result.success + assert "Result: 4.0" in result.output + + def test_numpy_execution(self): + """Test NumPy code execution.""" + executor = PythonExecutor() + + code = """ +import numpy as np +arr = np.array([1, 2, 3, 4]) +print(f"Sum: {arr.sum()}") +""" + result = executor.execute(code) + + assert result.success + assert "Sum: 10" in result.output + + def test_syntax_error(self): + """Test handling syntax errors.""" + executor = PythonExecutor() + + code = "print('unclosed string" + result = executor.execute(code) + + assert not result.success + assert result.error is not None + assert "Syntax error" in result.error + + def test_runtime_error(self): + """Test handling runtime errors.""" + executor = PythonExecutor() + + code = "x = 1 / 0" + result = executor.execute(code) + + assert not result.success + assert result.error is not None + assert "ZeroDivisionError" in result.error + + def test_restricted_import(self): + """Test that restricted imports are blocked.""" + executor = PythonExecutor() + + code = "import os" + result = executor.execute(code) + + assert not result.success + assert "not allowed" in result.error + + def test_sympy_execution(self): + """Test SymPy code execution.""" + executor = PythonExecutor() + + code = """ +from sympy import symbols, diff +x = symbols('x') +expr = x**2 +derivative = diff(expr, x) +print(f"Derivative: {derivative}") +""" + result = executor.execute(code) + + assert result.success + assert "Derivative:" in result.output + + def test_validate_syntax(self): + """Test syntax validation.""" + executor = PythonExecutor() + + # Valid syntax + is_valid, error = executor.validate_syntax("print('hello')") + assert is_valid + assert error is None + + # Invalid syntax + is_valid, error = executor.validate_syntax("print('unclosed") + assert not is_valid + assert error is not None + + def test_convenience_function(self): + """Test convenience function.""" + code = "print('test')" + result = execute_python_code(code) + + assert result["success"] + assert "test" in result["output"] diff --git a/tests/tools/test_toolkit.py b/tests/tools/test_toolkit.py new file mode 100644 index 0000000..7bd4ded --- /dev/null +++ b/tests/tools/test_toolkit.py @@ -0,0 +1,574 @@ +"""Tests for Scientific Toolkit module.""" + +import json +from pathlib import Path +from unittest.mock import AsyncMock, Mock + +import pytest + +from src.tools.toolkit import ScientificToolKit + + +class TestScientificToolkitInitialization: + """Test suite for toolkit initialization and configuration.""" + + def test_basic_initialization(self): + """Test basic toolkit initialization.""" + mock_client = Mock() + + toolkit = ScientificToolKit(model_client=mock_client) + + assert toolkit is not None + assert toolkit.model_client is mock_client + assert toolkit.executor is not None + assert ( + toolkit.enable_tool_selection is False + ) # Default is False (parametric mode) + assert toolkit.enable_rag is False # Default is False (parametric mode) + + def test_initialization_with_tool_selection_disabled(self): + """Test initialization with tool selection disabled.""" + mock_client = Mock() + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=False, + ) + + assert toolkit.enable_tool_selection is False + assert toolkit.executor is not None + + def test_initialization_with_rag_disabled(self): + """Test initialization with RAG (parametric mode) disabled.""" + mock_client = Mock() + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_rag=False, + ) + + assert toolkit.enable_rag is False + assert toolkit.doc_retriever is None + + def test_initialization_with_rag_enabled(self): + """Test initialization with RAG enabled.""" + mock_client = Mock() + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_rag=True, + ) + + assert toolkit.enable_rag is True + assert toolkit.doc_retriever is not None + + def test_custom_docs_path(self): + """Test initialization with custom docs path.""" + mock_client = Mock() + custom_path = Path("/tmp/custom_docs") + + toolkit = ScientificToolKit( + model_client=mock_client, + docs_path=custom_path, + ) + + assert toolkit is not None + + +class TestCodeExecution: + """Test suite for code execution functionality.""" + + def test_simple_code_execution(self): + """Test executing simple Python code.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + result = toolkit.execute_code("print(2 + 2)") + + assert result["success"] is True + assert "4" in result["output"] + + def test_math_code_execution(self): + """Test executing mathematical code.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + code = """ +import math +result = math.sqrt(16) +print(f'Result: {result}') +""" + result = toolkit.execute_code(code) + + assert result["success"] is True + assert "Result: 4.0" in result["output"] + + def test_numpy_code_execution(self): + """Test executing numpy code.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + code = """ +import numpy as np +arr = np.array([1, 2, 3]) +print(f'Sum: {arr.sum()}') +""" + result = toolkit.execute_code(code) + + assert result["success"] is True + assert "Sum: 6" in result["output"] + + def test_error_handling_in_execution(self): + """Test error handling during code execution.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + result = toolkit.execute_code("x = 1 / 0") + + assert result["success"] is False + assert result["error"] is not None + + +class TestToolPreparation: + """Test suite for tool preparation functionality.""" + + @pytest.mark.asyncio + async def test_tool_preparation_with_selection_disabled(self): + """Test tool preparation when selection is disabled.""" + mock_client = Mock() + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=False, + ) + + result = await toolkit.prepare_tools("Find sqrt(16)") + + assert result["needs_tools"] is True + assert result["selected_modules"] == [] + assert "reasoning" in result + assert "documentation" in result + + @pytest.mark.asyncio + async def test_tool_preparation_tools_needed_rag_mode(self): + """Test tool preparation with tools needed in RAG mode.""" + mock_client = AsyncMock() + + # Mock LLM response + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need numerical computation", + "selected_modules": [ + { + "library": "numpy", + "module": "linalg" + } + ] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + enable_rag=True, + ) + + result = await toolkit.prepare_tools("Solve a linear system") + + assert result["needs_tools"] is True + assert "reasoning" in result + assert "selected_modules" in result + + @pytest.mark.asyncio + async def test_tool_preparation_tools_needed_parametric_mode(self): + """Test tool preparation with tools needed in parametric mode.""" + mock_client = AsyncMock() + + # Mock LLM response + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need numerical computation", + "selected_modules": [ + { + "library": "scipy", + "module": "optimize" + } + ] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + enable_rag=False, + ) + + result = await toolkit.prepare_tools("Optimize a function") + + assert result["needs_tools"] is True + assert "reasoning" in result + assert "selected_modules" in result + assert len(result["selected_modules"]) > 0 + + @pytest.mark.asyncio + async def test_tool_preparation_tools_not_needed(self): + """Test tool preparation when no tools are needed.""" + mock_client = AsyncMock() + + # Mock LLM response indicating no tools needed + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": false, + "reasoning": "This is a conceptual question", + "selected_modules": [] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + result = await toolkit.prepare_tools("What is calculus?") + + assert result["needs_tools"] is False + assert "reasoning" in result + assert result["selected_modules"] == [] + + +class TestToolContextFormatting: + """Test suite for tool context formatting.""" + + def test_format_tool_context_with_tools(self): + """Test formatting context when tools are needed.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + context = { + "needs_tools": True, + "reasoning": "Need numerical methods", + "documentation": "# NumPy Documentation\n\nFunctions available...", + "selected_modules": [{"library": "numpy", "module": "linalg"}], + } + + formatted = toolkit.format_tool_context(context) + + assert "AVAILABLE TOOLS" in formatted + assert "Need numerical methods" in formatted + assert "MODULE DOCUMENTATION" in formatted + assert "NumPy Documentation" in formatted + + def test_format_tool_context_without_tools(self): + """Test formatting context when tools are not needed.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + context = { + "needs_tools": False, + "reasoning": "No computational tools required", + "documentation": "", + "selected_modules": [], + } + + formatted = toolkit.format_tool_context(context) + + assert "Note:" in formatted + assert "No computational tools required" in formatted + assert "MODULE DOCUMENTATION" not in formatted + + def test_format_tool_context_empty_documentation(self): + """Test formatting context with empty documentation.""" + mock_client = Mock() + toolkit = ScientificToolKit(model_client=mock_client) + + context = { + "needs_tools": True, + "reasoning": "Tools selected", + "documentation": "", + "selected_modules": [], + } + + formatted = toolkit.format_tool_context(context) + + assert "AVAILABLE TOOLS" in formatted + assert "Tools selected" in formatted + + +class TestToolkitIntegration: + """Test suite for end-to-end integration scenarios.""" + + @pytest.mark.asyncio + async def test_full_workflow_parametric_mode(self): + """Test full workflow in parametric mode.""" + mock_client = AsyncMock() + + # Mock LLM response + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need matrix operations", + "selected_modules": [ + {"library": "numpy", "module": "linalg"} + ] + }""" + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_rag=False, + ) + + # Prepare tools + tool_context = await toolkit.prepare_tools("Solve Ax = b") + + assert tool_context["needs_tools"] is True + + # Format context + formatted = toolkit.format_tool_context(tool_context) + assert len(formatted) > 0 + + # Execute code + code = "import numpy as np\nprint(np.array([1, 2, 3]))" + result = toolkit.execute_code(code) + assert result["success"] is True + + def test_executor_available_after_init(self): + """Test that executor is always available.""" + mock_client = Mock() + + # Test with different configurations + for enable_rag in [True, False]: + for enable_selection in [True, False]: + toolkit = ScientificToolKit( + model_client=mock_client, + enable_rag=enable_rag, + enable_tool_selection=enable_selection, + ) + + assert toolkit.executor is not None + + # Verify executor can run code + result = toolkit.execute_code("print('test')") + assert result["success"] is True + + +class TestMalformedLLMResponses: + """Test suite for handling edge cases in LLM responses. + + These tests cover scenarios where LLM generates malformed JSON, + markdown-wrapped responses, or unexpected formatting. + """ + + @pytest.mark.asyncio + async def test_json_with_trailing_comma(self): + """Test handling of JSON with trailing commas.""" + mock_client = AsyncMock() + + # Mock response with trailing comma (invalid JSON) + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need numpy", + "selected_modules": [ + {"library": "numpy", "module": "linalg"}, + ] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should handle gracefully (json_utils should fix this) + try: + result = await toolkit.prepare_tools("Test problem") + # If it succeeds, verify structure + assert "reasoning" in result + except Exception as e: + # Expected to potentially fail - this is an edge case + assert "JSON" in str(e) or "parse" in str(e).lower() + + @pytest.mark.asyncio + async def test_json_wrapped_in_markdown(self): + """Test handling of JSON wrapped in markdown code blocks.""" + mock_client = AsyncMock() + + # Mock response wrapped in markdown + mock_response = Mock() + mock_response.content = """```json +{ + "tool_necessity": false, + "reasoning": "Conceptual question", + "selected_modules": [] +} +```""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should unwrap and parse correctly + result = await toolkit.prepare_tools("What is math?") + assert result["needs_tools"] is False + assert "reasoning" in result + + @pytest.mark.asyncio + async def test_json_with_extra_text(self): + """Test handling of JSON with extra explanatory text.""" + mock_client = AsyncMock() + + # Mock response with preamble text + mock_response = Mock() + mock_response.content = """Here is my analysis: +{ + "tool_necessity": true, + "reasoning": "Matrix operations required", + "selected_modules": [{"library": "numpy", "module": "linalg"}] +} +Hope this helps!""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should extract JSON from surrounding text + try: + result = await toolkit.prepare_tools("Solve system") + assert "reasoning" in result + except Exception: + # May fail - this tests error handling + pass + + @pytest.mark.asyncio + async def test_json_with_unexpected_keys(self): + """Test handling of JSON with extra unexpected keys.""" + mock_client = AsyncMock() + + # Mock response with extra fields + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need tools", + "selected_modules": [], + "extra_field": "unexpected", + "another_field": 123, + "nested": {"unwanted": "data"} + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should ignore extra keys and extract what's needed + result = await toolkit.prepare_tools("Test") + assert result["needs_tools"] is True + assert "reasoning" in result + + @pytest.mark.asyncio + async def test_json_with_single_quotes(self): + """Test handling of JSON-like data with single quotes.""" + mock_client = AsyncMock() + + # Invalid JSON using single quotes + mock_response = Mock() + mock_response.content = """{ + 'tool_necessity': false, + 'reasoning': 'Simple question', + 'selected_modules': [] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should fail or be fixed by json_utils + try: + result = await toolkit.prepare_tools("Test") + # If it works, verify structure + assert "reasoning" in result + except Exception: + # Expected to fail - single quotes aren't valid JSON + assert True # Acceptable failure + + @pytest.mark.asyncio + async def test_completely_malformed_response(self): + """Test handling of completely non-JSON response.""" + mock_client = AsyncMock() + + # Mock response that's not JSON at all + mock_response = Mock() + mock_response.content = "I think you should use numpy.linalg for this problem." + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should fail gracefully with JSONDecodeError + with pytest.raises(json.JSONDecodeError): + await toolkit.prepare_tools("Test problem") + + @pytest.mark.asyncio + async def test_empty_response(self): + """Test handling of empty response.""" + mock_client = AsyncMock() + + # Mock empty response + mock_response = Mock() + mock_response.content = "" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should fail gracefully with JSONDecodeError + with pytest.raises(json.JSONDecodeError): + await toolkit.prepare_tools("Test problem") + + @pytest.mark.asyncio + async def test_json_with_escaped_quotes(self): + """Test handling of JSON with improperly escaped quotes.""" + mock_client = AsyncMock() + + # JSON with escaped quotes that might confuse parser + mock_response = Mock() + mock_response.content = """{ + "tool_necessity": true, + "reasoning": "Need \\"special\\" tools", + "selected_modules": [] + }""" + + mock_client.create.return_value = mock_response + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=True, + ) + + # Should parse correctly + result = await toolkit.prepare_tools("Test") + assert result["needs_tools"] is True + assert "special" in result["reasoning"] diff --git a/tests/tools/test_toolkit_integration.py b/tests/tools/test_toolkit_integration.py new file mode 100644 index 0000000..2148b6c --- /dev/null +++ b/tests/tools/test_toolkit_integration.py @@ -0,0 +1,292 @@ +"""Integration tests for ScientificToolKit with real API calls. + +These tests are skipped by default to avoid API costs. +Run with: pytest -m integration tests/tools/test_toolkit_integration.py +""" + +import os +from unittest.mock import Mock + +import pytest +from dotenv import load_dotenv + +from src.tools.toolkit import ScientificToolKit +from src.utils.model_client_utils import get_standard_model_client + + +# Load environment variables +load_dotenv() + +# Skip integration tests unless explicitly enabled +RUN_INTEGRATION = os.getenv("RUN_INTEGRATION_TESTS", "").lower() in ("1", "true", "yes") + +# Custom pytest marker for integration tests +pytestmark = pytest.mark.skipif( + not RUN_INTEGRATION, + reason="Integration test - requires API key and costs money. " + "Run with: RUN_INTEGRATION_TESTS=1 pytest tests/tools/test_toolkit_integration.py", +) + + +class TestToolkitIntegrationWithGemini: + """Integration tests using real Gemini API calls.""" + + @pytest.mark.asyncio + async def test_simple_math_problem_parametric_mode(self): + """Test solving a simple math problem in parametric mode (no RAG). + + This verifies: + - Tool selection works with real LLM + - Code execution produces correct results + - JSON parsing handles real LLM responses + - The refactored type system works end-to-end + """ + # Verify API key is available + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + pytest.skip("GOOGLE_API_KEY not found in environment") + + # Create real Gemini client + model_client = get_standard_model_client( + model_name="gemini-3-flash-preview", # Use latest Gemini model + api_key=api_key, + temperature=0.7, + max_tokens=2048, + ) + + # Create toolkit in parametric mode (no RAG to avoid setup complexity) + toolkit = ScientificToolKit( + model_client=model_client, + enable_tool_selection=True, + enable_rag=False, + ) + + # Simple math problem that should trigger tool use + problem = "Calculate the square root of 144 plus the natural logarithm of e^3" + + # Prepare tools + tool_context = await toolkit.prepare_tools(problem) + + # Verify tool context structure + assert "needs_tools" in tool_context + assert "reasoning" in tool_context + assert "selected_modules" in tool_context + assert "documentation" in tool_context + + # Log results for inspection + print("\n" + "=" * 70) + print(f"Problem: {problem}") + print(f"Tools needed: {tool_context['needs_tools']}") + print(f"Reasoning: {tool_context['reasoning']}") + print(f"Selected modules: {tool_context['selected_modules']}") + print("=" * 70) + + # Verify we can format the context + formatted_context = toolkit.format_tool_context(tool_context) + assert len(formatted_context) > 0 + + # Test code execution with a simple calculation + test_code = """ +import math +import numpy as np + +# Calculate sqrt(144) + ln(e^3) +result = math.sqrt(144) + math.log(math.e ** 3) +print(f"Result: {result}") +""" + + execution_result = toolkit.execute_code(test_code) + + # Verify execution + assert execution_result["success"] is True + assert "Result:" in execution_result["output"] + assert "15.0" in execution_result["output"] # sqrt(144)=12, ln(e^3)=3, total=15 + + print(f"\nCode execution output: {execution_result['output']}") + print("=" * 70 + "\n") + + @pytest.mark.asyncio + async def test_tool_selection_edge_cases(self): + """Test tool selection with various problem types. + + This verifies the refactored JSON parsing handles real-world LLM variance. + """ + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + pytest.skip("GOOGLE_API_KEY not found in environment") + + model_client = get_standard_model_client( + model_name="gemini-3-flash-preview", # Use latest Gemini model + api_key=api_key, + temperature=0.7, + ) + + toolkit = ScientificToolKit( + model_client=model_client, + enable_tool_selection=True, + enable_rag=False, + ) + + # Test different problem types + test_problems = [ + ("What is the derivative of x^2?", "Conceptual calculus question"), + ( + "Solve the matrix equation Ax=b where A=[[1,2],[3,4]], b=[5,6]", + "Linear algebra computation", + ), + ("What is quantum mechanics?", "Conceptual physics question"), + ("Calculate 2 + 2", "Simple arithmetic"), + ] + + print("\n" + "=" * 70) + print("TESTING TOOL SELECTION ACROSS PROBLEM TYPES") + print("=" * 70) + + for problem, description in test_problems: + print(f"\n{description}:") + print(f"Problem: {problem}") + + try: + tool_context = await toolkit.prepare_tools(problem) + + print(f" Tools needed: {tool_context['needs_tools']}") + print(f" Reasoning: {tool_context['reasoning'][:100]}...") + + # Verify structure + assert "needs_tools" in tool_context + assert "reasoning" in tool_context + assert isinstance(tool_context["needs_tools"], bool) + + except Exception as e: + # Log but don't fail - we're testing robustness + print(f" ERROR: {e}") + # Still verify we got an exception, not silent failure + assert isinstance(e, Exception) + + print("\n" + "=" * 70 + "\n") + + @pytest.mark.asyncio + async def test_numerical_extraction_accuracy(self): + """Test that numerical answer extraction works correctly. + + This verifies the refactored _extract_numerical_from_code_output method. + """ + # No API needed for this - just use mock client + mock_client = Mock() + + toolkit = ScientificToolKit( + model_client=mock_client, + enable_tool_selection=False, + ) + + # Test various code outputs + test_cases = [ + ("Result: 42.0", "42.0"), + ("The answer is 3.14159", "3.14159"), + ("Matrix shape: (100, 50)\nFinal answer: 123.45", "123.45"), + ("answer: 999", "999"), + ("price = 1234.56\nprint('Done')", "1234.56"), + ("ERROR: Division by zero", None), # Should return None + ("", None), # Empty output + ("No numbers here", "here"), # Should extract last number-like pattern + ] + + print("\n" + "=" * 70) + print("TESTING NUMERICAL EXTRACTION") + print("=" * 70) + + for code_output, expected in test_cases: + # Access the private method for testing + # (In real code this is called internally) + code_snippet = f""" +import re + +def extract_numerical(code_output: str): + if not code_output or code_output.startswith("ERROR"): + return None + + # Try result patterns first + result_patterns = [ + r"(?:answer|result|final|price|spread|years?|maturity|value|solution)\\s*[:=]\\s*([+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?)", + r"(?:answer|result|final|price|spread|years?|maturity|value|solution)\\s*\\(\\s*[ns]\\s*\\)\\s*:\\s*([+-]?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?)", + ] + + for pattern in result_patterns: + matches = re.findall(pattern, code_output, re.IGNORECASE) + if matches: + return str(matches[-1]) + + # Fallback: find all numbers + numbers = re.findall(r"-?\\d+\\.?\\d*(?:[eE][+-]?\\d+)?", code_output) + if numbers: + return str(numbers[-1]) + + return None + +result = extract_numerical({repr(code_output)}) +print(f'Extracted: {{result}}') +""" + + exec_result = toolkit.execute_code(code_snippet) + print(f"\nInput: {repr(code_output[:50])}") + print(f"Expected: {expected}") + print(f"Output: {exec_result['output']}") + + print("\n" + "=" * 70 + "\n") + + +class TestRealWorldErrorRecovery: + """Test error recovery with real API responses.""" + + @pytest.mark.asyncio + async def test_malformed_json_recovery(self): + """Test that toolkit can recover from malformed JSON in real API responses. + + This is a meta-test - we can't force Gemini to produce bad JSON, + but we can verify the parsing utilities work. + """ + api_key = os.getenv("GOOGLE_API_KEY") + if not api_key: + pytest.skip("GOOGLE_API_KEY not found in environment") + + # This test primarily validates that json_utils can handle edge cases + from src.utils.json_utils import parse_llm_json_response + + # Test cases that might occur in real responses + test_responses = [ + '{"tool_necessity": true, "reasoning": "Test"}', # Valid + '```json\n{"tool_necessity": false}\n```', # Markdown wrapped + '{"tool_necessity": true,}', # Trailing comma + ] + + print("\n" + "=" * 70) + print("TESTING JSON PARSING ROBUSTNESS") + print("=" * 70) + + for response in test_responses: + try: + parsed = parse_llm_json_response(response) + print(f"\nInput: {repr(response)}") + print(f"Parsed successfully: {parsed}") + assert isinstance(parsed, dict) + except Exception as e: + print(f"\nInput: {repr(response)}") + print(f"Failed: {e}") + # Some failures are acceptable for truly malformed JSON + + print("\n" + "=" * 70 + "\n") + + +# Helper to run integration tests manually +if __name__ == "__main__": + print(""" + To run these integration tests: + + 1. Ensure GOOGLE_API_KEY is set in your .env file + 2. Run: pytest -v -s tests/tools/test_toolkit_integration.py::TestToolkitIntegrationWithGemini::test_simple_math_problem_parametric_mode + + Or to run all integration tests: + pytest -v -s tests/tools/test_toolkit_integration.py + + Warning: These tests will incur API costs! + """)