├── .github ├── dependabot.yml ├── logo.png └── workflows │ ├── ci_linux.yml │ ├── ci_mac.yml │ └── python-checks.yml ├── .gitignore ├── LICENSE ├── README.md ├── example.py ├── poetry.lock ├── pyproject.toml ├── src └── mlir_egglog │ ├── __init__.py │ ├── basic_simplify.py │ ├── builtin_functions.py │ ├── dispatcher.py │ ├── egglog_optimizer.py │ ├── expr_model.py │ ├── ir_to_mlir.py │ ├── jit_engine.py │ ├── llvm_runtime.py │ ├── memory_descriptors.py │ ├── mlir_backend.py │ ├── mlir_gen.py │ ├── python_to_ir.py │ ├── term_ir.py │ └── trig_simplify.py ├── tests ├── test_basic_expressions.py ├── test_dispatch.py └── test_simplify.py └── uv.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdiehl/mlir-egglog/957366ad8c22ca4c67dc6f19d17a8ac1672bef7f/.github/logo.png -------------------------------------------------------------------------------- /.github/workflows/ci_linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci-linux: 12 | runs-on: ubuntu-22.04 13 | 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: Set up Python 3.12 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: '3.12' 22 | 23 | # Create a cache key based on the LLVM version and Ubuntu version 24 | - name: Set LLVM cache key 25 | id: llvm-cache-key 26 | run: | 27 | UBUNTU_VERSION=$(lsb_release -rs) 28 | echo "key=llvm-20-ubuntu-$UBUNTU_VERSION" >> $GITHUB_OUTPUT 29 | 30 | # Cache apt packages 31 | - name: Cache LLVM apt packages 32 | uses: actions/cache@v3 33 | id: cache-llvm 34 | with: 35 | path: ~/llvm-cache 36 | key: ${{ steps.llvm-cache-key.outputs.key }} 37 | 38 | - name: Restore LLVM from cache 39 | if: steps.cache-llvm.outputs.cache-hit == 'true' 40 | run: | 41 | echo "Restoring LLVM from cache" 42 | sudo dpkg -i ~/llvm-cache/*.deb || true 43 | sudo apt-get install -f -y # Fix any broken dependencies 44 | 45 | - name: Install LLVM tools 46 | if: steps.cache-llvm.outputs.cache-hit != 'true' 47 | run: | 48 | # Get Ubuntu codename for repository setup 49 | UBUNTU_CODENAME=$(lsb_release -cs) 50 | sudo apt-get update 51 | sudo apt-get install -y wget gnupg software-properties-common git 52 | 53 | # Install LLVM 20 tools 54 | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/llvm.gpg 55 | sudo chmod 644 /etc/apt/keyrings/llvm.gpg 56 | echo "deb [signed-by=/etc/apt/keyrings/llvm.gpg] http://apt.llvm.org/$UBUNTU_CODENAME/ llvm-toolchain-$UBUNTU_CODENAME-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list 57 | sudo apt-get update 58 | 59 | # Download packages without installing 60 | mkdir -p ~/llvm-cache 61 | sudo apt-get install -y --download-only llvm-20 llvm-20-dev llvm-20-tools mlir-20-tools 62 | sudo cp /var/cache/apt/archives/*.deb ~/llvm-cache/ || true 63 | sudo chown -R $USER:$USER ~/llvm-cache 64 | 65 | # Install the packages 66 | sudo apt-get install -y llvm-20 llvm-20-dev llvm-20-tools mlir-20-tools 67 | 68 | # Always create the symlinks 69 | - name: Create LLVM symlinks 70 | run: | 71 | sudo ln -sf /usr/bin/llc-20 /usr/bin/llc 72 | sudo ln -sf /usr/bin/mlir-translate-20 /usr/bin/mlir-translate 73 | sudo ln -sf /usr/bin/mlir-opt-20 /usr/bin/mlir-opt 74 | 75 | # Install Poetry using the snok action 76 | - name: Install Poetry 77 | uses: snok/install-poetry@v1 78 | with: 79 | version: latest 80 | virtualenvs-create: true 81 | virtualenvs-in-project: false 82 | 83 | # Cache Poetry dependencies 84 | - name: Cache Poetry dependencies 85 | uses: actions/cache@v3 86 | with: 87 | path: ~/.cache/pypoetry 88 | key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }} 89 | restore-keys: | 90 | ${{ runner.os }}-poetry- 91 | 92 | - name: Install dependencies 93 | run: | 94 | poetry env use python3.12 95 | poetry install 96 | 97 | - name: Run Tests 98 | run: | 99 | poetry run pytest tests -v 100 | -------------------------------------------------------------------------------- /.github/workflows/ci_mac.yml: -------------------------------------------------------------------------------- 1 | name: macOS CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ci-macos: 12 | strategy: 13 | matrix: 14 | os: [macos-13, macos-15] 15 | runs-on: ${{ matrix.os }} 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Set up Python 3.12 22 | uses: actions/setup-python@v5 23 | with: 24 | python-version: '3.12' 25 | 26 | # Cache Homebrew packages 27 | - name: Cache Homebrew packages 28 | uses: actions/cache@v3 29 | id: cache-brew 30 | with: 31 | path: ~/Library/Caches/Homebrew 32 | key: ${{ runner.os }}-${{ matrix.os }}-brew-llvm-${{ hashFiles('.github/workflows/ci_mac.yml') }} 33 | restore-keys: | 34 | ${{ runner.os }}-${{ matrix.os }}-brew-llvm- 35 | 36 | - name: Install LLVM via Homebrew 37 | run: | 38 | brew update 39 | brew install llvm 40 | 41 | # Set the correct LLVM path based on architecture 42 | if [[ "${{ matrix.os }}" == "macos-13" ]]; then 43 | # Intel macOS (x86_64) 44 | echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> $GITHUB_ENV 45 | echo "/usr/local/opt/llvm/bin" >> $GITHUB_PATH 46 | else 47 | # ARM macOS (M1/M2) 48 | echo 'export PATH="/opt/homebrew/opt/llvm/bin:$PATH"' >> $GITHUB_ENV 49 | echo "/opt/homebrew/opt/llvm/bin" >> $GITHUB_PATH 50 | fi 51 | 52 | # Verify mlir-opt is available 53 | which mlir-opt || echo "mlir-opt not found in PATH" 54 | 55 | # Install Poetry using the snok action 56 | - name: Install Poetry 57 | uses: snok/install-poetry@v1 58 | with: 59 | version: latest 60 | virtualenvs-create: true 61 | virtualenvs-in-project: false 62 | 63 | # Cache Poetry dependencies 64 | - name: Cache Poetry dependencies 65 | uses: actions/cache@v3 66 | with: 67 | path: ~/Library/Caches/pypoetry 68 | key: ${{ runner.os }}-${{ matrix.os }}-poetry-${{ hashFiles('**/poetry.lock') }} 69 | restore-keys: | 70 | ${{ runner.os }}-${{ matrix.os }}-poetry- 71 | 72 | - name: Install dependencies 73 | run: | 74 | poetry env use python3.12 75 | poetry install 76 | 77 | - name: Run Tests 78 | run: | 79 | # Print PATH for debugging 80 | echo "PATH: $PATH" 81 | 82 | # Verify mlir-opt is available before running tests 83 | which mlir-opt || echo "mlir-opt not found in PATH" 84 | 85 | poetry run pytest tests -v -------------------------------------------------------------------------------- /.github/workflows/python-checks.yml: -------------------------------------------------------------------------------- 1 | name: Python Checks 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | quality: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.12" 20 | 21 | - name: Install uv 22 | run: | 23 | curl -LsSf https://astral.sh/uv/install.sh | sh 24 | echo "$HOME/.uv/bin" >> $GITHUB_PATH 25 | 26 | - name: Install dependencies 27 | run: | 28 | mkdir -p output 29 | uv pip install --system . 30 | uv pip install --system ".[dev]" 31 | 32 | - name: Run black 33 | run: uv run black . --check 34 | 35 | - name: Run ruff 36 | run: uv run ruff check . 37 | 38 | - name: Run mypy 39 | run: uv run mypy . --exclude build/ 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.py[co] 3 | *.swp 4 | *.html 5 | __pycache__ 6 | *.egg-info 7 | .venv 8 | .pytest_cache 9 | .ruff_cache 10 | .mypy_cache 11 | simple 12 | *.o 13 | *.so 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025, Stephen Diehl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | mlir-egglog 3 |

4 | 5 | # MLIR Egglog 6 | 7 | A toy specializing compiler for NumPy expressions that uses MLIR as a target and can use equality saturation (e-graphs) to do term rewriting on the intermediate representation, enabling extremely precise and composable optimizations of mathematical expressions before lowering to MLIR. 8 | 9 | We use the embedded Datalog DSL [`egglog`](https://github.com/egraphs-good/egglog) to express and compose rewrite rules in pure Python and the [`egg`](https://docs.rs/egg/latest/egg/) library to extract optimized syntax trees from the e-graph. 10 | 11 | The whole project is just under 1500 lines of code, and is designed to be a simple and easy to understand example of how to integrate e-graphs into a compiler pipeline. 12 | 13 | ## What is Egg and Equality Saturation? 14 | 15 | Think of an e-graph as this magical data structure that's like a super-powered hash table of program expressions. Instead of just storing one way to write a program, it efficiently stores ALL equivalent ways to write it. 16 | 17 | Equality saturation is the process of filling this e-graph with all possible equivalent programs by applying rewrite rules until we can't find any more rewrites (that's the "saturation" part). The cool part? We can explore tons of different optimizations simultaneously, rather than having to pick a specific sequence of transformations. The you can apply a cost function over the entire e-graph to find the best solution. 18 | 19 | Traditionally you'd have to muddle through with a fixed-point iteration system and tons of top-down/bottom-up rewrite rule contingent on application orders, but e-graphs make it much more efficient and declarative. 20 | 21 | ## Installation 22 | 23 | On MacOS, install LLVM 20 which includes MLIR: 24 | 25 | ```shell 26 | brew install llvm@20 27 | ``` 28 | 29 | On Linux, install the dependencies (setup instructions [here](https://apt.llvm.org/)): 30 | 31 | ```shell 32 | sudo apt-get install -y llvm-20 llvm-20-dev llvm-20-tools mlir-20-tools 33 | ``` 34 | 35 | Then to use the library: 36 | 37 | ```shell 38 | git clone https://github.com/sdiehl/mlir-egglog.git 39 | cd mlir-egglog 40 | poetry install 41 | poetry run python example.py 42 | ``` 43 | 44 | ## Usage 45 | 46 | ```python 47 | from mlir_egglog import kernel 48 | 49 | @kernel("float32(float32)") 50 | def fn(x : float) -> float: 51 | # sinh(x + y) = sinh(x) * cosh(y) + cosh(x) * sinh(y) 52 | return np.sinh(x) * np.cosh(x) + np.cosh(x) * np.sinh(x) 53 | 54 | out = fn(np.array([1, 2, 3])) 55 | print(out) 56 | ``` 57 | 58 | ## Custom Rewrite Rules 59 | 60 | You can create your own optimization rules using the `ruleset` decorator. Here's a complete example that optimizes away addition with zero: 61 | 62 | ```python 63 | from mlir_egglog import kernel 64 | from mlir_egglog.term_ir import Term, Add 65 | from egglog import rewrite, ruleset, RewriteOrRule, i64, f64 66 | from typing import Generator 67 | 68 | @ruleset 69 | def float_rules(x: Term, y: Term, z: Term, i: i64, f: f64): 70 | yield rewrite(Add(x, Term.lit_f32(0.0))).to(x) 71 | yield rewrite(Add(Term.lit_f32(0.0), x)).to(x) 72 | 73 | @kernel("float32(float32)", rewrites=(basic_math, float_rules)) 74 | def custom_fn(x): 75 | return x + 0.0 # This addition will be optimized away! 76 | 77 | test_input = np.array([1.0, 2.0, 3.0], dtype=np.float32) 78 | result = custom_fn(test_input) 79 | print(result) 80 | ``` 81 | 82 | The rewrite rules are applied during compilation, so there's no runtime overhead. The generated MLIR code will be as if you just wrote `return x`. You can combine multiple rulesets to build up more complex program optimizations. 83 | 84 | ## Codebase 85 | 86 | Here's the recommended order to understand the codebase: 87 | 88 | **Foundation Layer** - Expression representation and manipulation 89 | 90 | 1. [`memory_descriptors.py`](src/mlir_egglog/memory_descriptors.py) - Basic memory management utilities for handling NumPy arrays and MLIR memory references 91 | 2. [`expr_model.py`](src/mlir_egglog/expr_model.py) - Core expression model defining the base classes for mathematical expressions 92 | 3. [`builtin_functions.py`](src/mlir_egglog/builtin_functions.py) - Implementation of basic mathematical functions and operations 93 | 4. [`term_ir.py`](src/mlir_egglog/term_ir.py) - Intermediate representation for the egraph system with cost models for operations 94 | 95 | **Transformation Layer** - Code transformation and lowering 96 | 97 | 5. [`python_to_ir.py`](src/mlir_egglog/python_to_ir.py) - Converts Python functions to the internal IR representation 98 | 6. [`ir_to_mlir.py`](src/mlir_egglog/ir_to_mlir.py) - Transforms internal IR to MLIR representation 99 | 7. [`basic_simplify.py`](src/mlir_egglog/basic_simplify.py) - Basic mathematical simplification rules 100 | 8. [`trig_simplify.py`](src/mlir_egglog/trig_simplify.py) - Trigonometric function simplification rules 101 | 102 | **Optimization Layer** - Optimization and compilation 103 | 104 | 9. [`egglog_optimizer.py`](src/mlir_egglog/egglog_optimizer.py) - Core optimization engine using egg-rewrite rules 105 | 10. [`mlir_backend.py`](src/mlir_egglog/mlir_backend.py) - MLIR compilation pipeline and optimization passes 106 | 11. [`llvm_runtime.py`](src/mlir_egglog/llvm_runtime.py) - LLVM runtime initialization and management 107 | 108 | **Execution Layer** - Runtime execution 109 | 110 | 12. [`jit_engine.py`](src/mlir_egglog/jit_engine.py) - JIT compilation engine for executing optimized code 111 | 13. [`dispatcher.py`](src/mlir_egglog/dispatcher.py) - High-level interface for function compilation and execution 112 | 113 | ## License 114 | 115 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 116 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | import llvmlite 2 | import numpy as np # noqa: E402 3 | 4 | llvmlite.opaque_pointers_enabled = True 5 | 6 | from mlir_egglog import kernel # noqa: E402 7 | 8 | 9 | @kernel("float32(float32)") 10 | def fn(a): 11 | return np.sin(a) * np.cos(a) + np.cos(a) * np.sin(a) 12 | 13 | 14 | def ref_fn(a): 15 | return np.sin(a) * np.cos(a) + np.cos(a) * np.sin(a) 16 | 17 | 18 | out = fn(np.array([1.0], dtype=np.float32)) 19 | ref = ref_fn(np.array([1.0], dtype=np.float32)) 20 | print(out) 21 | print(ref) 22 | 23 | assert np.allclose(out, ref) 24 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anywidget" 5 | version = "0.9.13" 6 | description = "custom jupyter widgets made easy" 7 | optional = false 8 | python-versions = ">=3.7" 9 | groups = ["main"] 10 | files = [ 11 | {file = "anywidget-0.9.13-py3-none-any.whl", hash = "sha256:43d1658f1043b8c95cd350b2f5deccb123fd37810a36f656d6163aefe8163705"}, 12 | {file = "anywidget-0.9.13.tar.gz", hash = "sha256:c655455bf51f82182eb23c5947d37cc41f0b1ffacaf7e2b763147a2332cb3f07"}, 13 | ] 14 | 15 | [package.dependencies] 16 | ipywidgets = ">=7.6.0" 17 | psygnal = ">=0.8.1" 18 | typing-extensions = ">=4.2.0" 19 | 20 | [package.extras] 21 | dev = ["comm (>=0.1.0)", "watchfiles (>=0.18.0)"] 22 | test = ["ipython (<8.13) ; python_version < \"3.9\"", "msgspec ; python_version > \"3.7\"", "mypy (==1.10.0) ; python_version > \"3.7\"", "pydantic", "pytest", "pytest-cov", "ruff"] 23 | 24 | [[package]] 25 | name = "asttokens" 26 | version = "3.0.0" 27 | description = "Annotate AST trees with source code positions" 28 | optional = false 29 | python-versions = ">=3.8" 30 | groups = ["main"] 31 | files = [ 32 | {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, 33 | {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, 34 | ] 35 | 36 | [package.extras] 37 | astroid = ["astroid (>=2,<4)"] 38 | test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] 39 | 40 | [[package]] 41 | name = "black" 42 | version = "25.1.0" 43 | description = "The uncompromising code formatter." 44 | optional = false 45 | python-versions = ">=3.9" 46 | groups = ["main", "dev"] 47 | files = [ 48 | {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, 49 | {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, 50 | {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, 51 | {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, 52 | {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, 53 | {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, 54 | {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, 55 | {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, 56 | {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, 57 | {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, 58 | {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, 59 | {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, 60 | {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, 61 | {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, 62 | {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, 63 | {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, 64 | {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, 65 | {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, 66 | {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, 67 | {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, 68 | {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, 69 | {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, 70 | ] 71 | 72 | [package.dependencies] 73 | click = ">=8.0.0" 74 | mypy-extensions = ">=0.4.3" 75 | packaging = ">=22.0" 76 | pathspec = ">=0.9.0" 77 | platformdirs = ">=2" 78 | 79 | [package.extras] 80 | colorama = ["colorama (>=0.4.3)"] 81 | d = ["aiohttp (>=3.10)"] 82 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 83 | uvloop = ["uvloop (>=0.15.2)"] 84 | 85 | [[package]] 86 | name = "click" 87 | version = "8.1.8" 88 | description = "Composable command line interface toolkit" 89 | optional = false 90 | python-versions = ">=3.7" 91 | groups = ["main", "dev"] 92 | files = [ 93 | {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, 94 | {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, 95 | ] 96 | 97 | [package.dependencies] 98 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 99 | 100 | [[package]] 101 | name = "colorama" 102 | version = "0.4.6" 103 | description = "Cross-platform colored terminal text." 104 | optional = false 105 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 106 | groups = ["main", "dev"] 107 | markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" 108 | files = [ 109 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 110 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 111 | ] 112 | 113 | [[package]] 114 | name = "comm" 115 | version = "0.2.2" 116 | description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." 117 | optional = false 118 | python-versions = ">=3.8" 119 | groups = ["main"] 120 | files = [ 121 | {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, 122 | {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, 123 | ] 124 | 125 | [package.dependencies] 126 | traitlets = ">=4" 127 | 128 | [package.extras] 129 | test = ["pytest"] 130 | 131 | [[package]] 132 | name = "decorator" 133 | version = "5.1.1" 134 | description = "Decorators for Humans" 135 | optional = false 136 | python-versions = ">=3.5" 137 | groups = ["main"] 138 | files = [ 139 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 140 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 141 | ] 142 | 143 | [[package]] 144 | name = "egglog" 145 | version = "10.0.1" 146 | description = "e-graphs in Python built around the the egglog rust library" 147 | optional = false 148 | python-versions = ">=3.10" 149 | groups = ["main"] 150 | files = [ 151 | {file = "egglog-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9574cd9715d3a636f98d001053f46bc1ce4d2b210431821d4559d038fe4991eb"}, 152 | {file = "egglog-10.0.1-cp310-cp310-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6c0d190f47096dbae7fb7ece1bfea6be9fda59500fe4eb855177aaa9e55df124"}, 153 | {file = "egglog-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1238d9484eb4727fb364bb435a5e741ada380cc05210525e090e394a0012e9ca"}, 154 | {file = "egglog-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:1179d6ff9fb4577f41900aac25c3f0337507b8be0a2cb530fd10dd331384b157"}, 155 | {file = "egglog-10.0.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2e94e24dc9e52b3ac589240030f914a2c8301bccc14f80835f12189e5f16b01b"}, 156 | {file = "egglog-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a35547d41defc3640f078f6239f384f07e27c67006a3a6f96a427db4e845314"}, 157 | {file = "egglog-10.0.1-cp311-cp311-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ed2a86b42b84c4cfeb922bdc81e836314e921c11299416650ad1d34c397be15e"}, 158 | {file = "egglog-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1da95d5dc2017589bac292d326319f70fe85677d113e69f280f861378370631b"}, 159 | {file = "egglog-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:d09458305d4e0b2b66131f4f77f3ed13864477cbccb9ba2a371cc01a3c92dbf2"}, 160 | {file = "egglog-10.0.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:41c86dcf3608babbf55c5c4cc987a00667ac9913276fd37ba9de22befb89bd90"}, 161 | {file = "egglog-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e21e8a46006dd9e9de570aa287d1dcf3364bd4f869c5d30966404e8fe46d9721"}, 162 | {file = "egglog-10.0.1-cp312-cp312-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:491d792e6594eed7dc0c48cb61a6e1d3a19e9ed4876cad22002679112c739746"}, 163 | {file = "egglog-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de40752264922045166d7b3594c95409f34dd0f5c173898c2f9b9dcfe6752a1e"}, 164 | {file = "egglog-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2b3fec01bb35b3a7f46c094ba00a1b63d3045f40c839879a4d038ace26083c68"}, 165 | {file = "egglog-10.0.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:1480d05e7a69f63cd0047c3d4e6c1fd65c0b14a3dc581e3c4f2e236102c60609"}, 166 | {file = "egglog-10.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbfad8a824fdb725e80d19c5117615e9c2b7f6a4dde9b97404d1af9b6a3e3684"}, 167 | {file = "egglog-10.0.1-cp313-cp313-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8bef32b89d7cfbd62214c8d745d3fc8c7b4ee87c77681dee204a6a933e0cafb6"}, 168 | {file = "egglog-10.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:195d7f0877c00f10918141afba428d8affd6d436cdb28c0f1242eb1f81b8c113"}, 169 | {file = "egglog-10.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:f7f70f6a47d966f0d51906c3efab238b3177c50ae7ab112535270b1dbb614fae"}, 170 | {file = "egglog-10.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:053b626dfc7291daa6455b4cbe3e623a4aaaf734f0a46bd0ad4188da3aff44e7"}, 171 | {file = "egglog-10.0.1-cp313-cp313t-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6c50babe02c9b20741ee3787fcb00c34c13d1a6ca4d0b9d3f13de72f83cf2371"}, 172 | {file = "egglog-10.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:995a2c28cbef813b189d97b40494ab71e813d16a0dd45b4b7ea4c97f87f552fe"}, 173 | {file = "egglog-10.0.1-pp310-pypy310_pp73-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d1a85b6a66648fc32e2bc3ab3de659d33813e11da5ce26fb8085ca95caa67c1f"}, 174 | {file = "egglog-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db5bdaffb00ef1a38b8b9b9b0127f87308afbf0546142ffad24d7813d55e7f4c"}, 175 | {file = "egglog-10.0.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddbfae258d2b24a570f76582139df221b6c6847fab4ae5fac51f65a6d6c8024"}, 176 | {file = "egglog-10.0.1-pp311-pypy311_pp73-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d36bb08809a674045cf378f95f845c82f4253fd3200853094ed4a7abd550533c"}, 177 | {file = "egglog-10.0.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80b07961a29f6c09f552672f31d87e0260bb559b6d389a9e80393d6e146d21c4"}, 178 | {file = "egglog-10.0.1.tar.gz", hash = "sha256:ddc8bdc30dd5a9f1049850ec0c967200397c043b07171031021ff3d1b83c3da1"}, 179 | ] 180 | 181 | [package.dependencies] 182 | anywidget = "*" 183 | black = "*" 184 | graphviz = "*" 185 | typing-extensions = "*" 186 | 187 | [package.extras] 188 | array = ["array-api-compat", "llvmlite (>=0.42.0)", "numba (>=0.59.1)", "numpy (>2)", "scikit-learn"] 189 | dev = ["anywidget[dev]", "egglog[docs,test]", "jupyterlab", "mypy", "pre-commit", "ruff"] 190 | docs = ["ablog", "anywidget", "egglog[array]", "line-profiler", "matplotlib", "myst-nb", "nbconvert", "pydata-sphinx-theme", "seaborn", "sphinx-autodoc-typehints", "sphinx-gallery", "sphinxcontrib-mermaid"] 191 | test = ["egglog[array]", "mypy", "pytest", "pytest-benchmark", "pytest-codspeed", "pytest-xdist", "syrupy"] 192 | 193 | [[package]] 194 | name = "executing" 195 | version = "2.2.0" 196 | description = "Get the currently executing AST node of a frame, and other information" 197 | optional = false 198 | python-versions = ">=3.8" 199 | groups = ["main"] 200 | files = [ 201 | {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, 202 | {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, 203 | ] 204 | 205 | [package.extras] 206 | tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] 207 | 208 | [[package]] 209 | name = "graphviz" 210 | version = "0.20.3" 211 | description = "Simple Python interface for Graphviz" 212 | optional = false 213 | python-versions = ">=3.8" 214 | groups = ["main"] 215 | files = [ 216 | {file = "graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5"}, 217 | {file = "graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d"}, 218 | ] 219 | 220 | [package.extras] 221 | dev = ["flake8", "pep8-naming", "tox (>=3)", "twine", "wheel"] 222 | docs = ["sphinx (>=5,<7)", "sphinx-autodoc-typehints", "sphinx-rtd-theme"] 223 | test = ["coverage", "pytest (>=7,<8.1)", "pytest-cov", "pytest-mock (>=3)"] 224 | 225 | [[package]] 226 | name = "iniconfig" 227 | version = "2.0.0" 228 | description = "brain-dead simple config-ini parsing" 229 | optional = false 230 | python-versions = ">=3.7" 231 | groups = ["dev"] 232 | files = [ 233 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 234 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 235 | ] 236 | 237 | [[package]] 238 | name = "ipython" 239 | version = "8.32.0" 240 | description = "IPython: Productive Interactive Computing" 241 | optional = false 242 | python-versions = ">=3.10" 243 | groups = ["main"] 244 | files = [ 245 | {file = "ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa"}, 246 | {file = "ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251"}, 247 | ] 248 | 249 | [package.dependencies] 250 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 251 | decorator = "*" 252 | jedi = ">=0.16" 253 | matplotlib-inline = "*" 254 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} 255 | prompt_toolkit = ">=3.0.41,<3.1.0" 256 | pygments = ">=2.4.0" 257 | stack_data = "*" 258 | traitlets = ">=5.13.0" 259 | 260 | [package.extras] 261 | all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] 262 | black = ["black"] 263 | doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] 264 | kernel = ["ipykernel"] 265 | matplotlib = ["matplotlib"] 266 | nbconvert = ["nbconvert"] 267 | nbformat = ["nbformat"] 268 | notebook = ["ipywidgets", "notebook"] 269 | parallel = ["ipyparallel"] 270 | qtconsole = ["qtconsole"] 271 | test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] 272 | test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] 273 | 274 | [[package]] 275 | name = "ipywidgets" 276 | version = "8.1.5" 277 | description = "Jupyter interactive widgets" 278 | optional = false 279 | python-versions = ">=3.7" 280 | groups = ["main"] 281 | files = [ 282 | {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, 283 | {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, 284 | ] 285 | 286 | [package.dependencies] 287 | comm = ">=0.1.3" 288 | ipython = ">=6.1.0" 289 | jupyterlab-widgets = ">=3.0.12,<3.1.0" 290 | traitlets = ">=4.3.1" 291 | widgetsnbextension = ">=4.0.12,<4.1.0" 292 | 293 | [package.extras] 294 | test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] 295 | 296 | [[package]] 297 | name = "jedi" 298 | version = "0.19.2" 299 | description = "An autocompletion tool for Python that can be used for text editors." 300 | optional = false 301 | python-versions = ">=3.6" 302 | groups = ["main"] 303 | files = [ 304 | {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, 305 | {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, 306 | ] 307 | 308 | [package.dependencies] 309 | parso = ">=0.8.4,<0.9.0" 310 | 311 | [package.extras] 312 | docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] 313 | qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] 314 | testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] 315 | 316 | [[package]] 317 | name = "jupyterlab-widgets" 318 | version = "3.0.13" 319 | description = "Jupyter interactive widgets for JupyterLab" 320 | optional = false 321 | python-versions = ">=3.7" 322 | groups = ["main"] 323 | files = [ 324 | {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, 325 | {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, 326 | ] 327 | 328 | [[package]] 329 | name = "llvmlite" 330 | version = "0.44.0" 331 | description = "lightweight wrapper around basic LLVM functionality" 332 | optional = false 333 | python-versions = ">=3.10" 334 | groups = ["main"] 335 | files = [ 336 | {file = "llvmlite-0.44.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614"}, 337 | {file = "llvmlite-0.44.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791"}, 338 | {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8"}, 339 | {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408"}, 340 | {file = "llvmlite-0.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2"}, 341 | {file = "llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3"}, 342 | {file = "llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427"}, 343 | {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1"}, 344 | {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610"}, 345 | {file = "llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955"}, 346 | {file = "llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad"}, 347 | {file = "llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db"}, 348 | {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9"}, 349 | {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d"}, 350 | {file = "llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1"}, 351 | {file = "llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516"}, 352 | {file = "llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e"}, 353 | {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf"}, 354 | {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc"}, 355 | {file = "llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930"}, 356 | {file = "llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4"}, 357 | ] 358 | 359 | [[package]] 360 | name = "matplotlib-inline" 361 | version = "0.1.7" 362 | description = "Inline Matplotlib backend for Jupyter" 363 | optional = false 364 | python-versions = ">=3.8" 365 | groups = ["main"] 366 | files = [ 367 | {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, 368 | {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, 369 | ] 370 | 371 | [package.dependencies] 372 | traitlets = "*" 373 | 374 | [[package]] 375 | name = "mypy" 376 | version = "1.16.0" 377 | description = "Optional static typing for Python" 378 | optional = false 379 | python-versions = ">=3.9" 380 | groups = ["dev"] 381 | files = [ 382 | {file = "mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c"}, 383 | {file = "mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571"}, 384 | {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491"}, 385 | {file = "mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777"}, 386 | {file = "mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b"}, 387 | {file = "mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93"}, 388 | {file = "mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab"}, 389 | {file = "mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2"}, 390 | {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff"}, 391 | {file = "mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666"}, 392 | {file = "mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c"}, 393 | {file = "mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b"}, 394 | {file = "mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13"}, 395 | {file = "mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090"}, 396 | {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1"}, 397 | {file = "mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8"}, 398 | {file = "mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730"}, 399 | {file = "mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec"}, 400 | {file = "mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b"}, 401 | {file = "mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0"}, 402 | {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b"}, 403 | {file = "mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d"}, 404 | {file = "mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52"}, 405 | {file = "mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb"}, 406 | {file = "mypy-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f56236114c425620875c7cf71700e3d60004858da856c6fc78998ffe767b73d3"}, 407 | {file = "mypy-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15486beea80be24ff067d7d0ede673b001d0d684d0095803b3e6e17a886a2a92"}, 408 | {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2ed0e0847a80655afa2c121835b848ed101cc7b8d8d6ecc5205aedc732b1436"}, 409 | {file = "mypy-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb5fbc8063cb4fde7787e4c0406aa63094a34a2daf4673f359a1fb64050e9cb2"}, 410 | {file = "mypy-1.16.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a5fcfdb7318c6a8dd127b14b1052743b83e97a970f0edb6c913211507a255e20"}, 411 | {file = "mypy-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:2e7e0ad35275e02797323a5aa1be0b14a4d03ffdb2e5f2b0489fa07b89c67b21"}, 412 | {file = "mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031"}, 413 | {file = "mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab"}, 414 | ] 415 | 416 | [package.dependencies] 417 | mypy_extensions = ">=1.0.0" 418 | pathspec = ">=0.9.0" 419 | typing_extensions = ">=4.6.0" 420 | 421 | [package.extras] 422 | dmypy = ["psutil (>=4.0)"] 423 | faster-cache = ["orjson"] 424 | install-types = ["pip"] 425 | mypyc = ["setuptools (>=50)"] 426 | reports = ["lxml"] 427 | 428 | [[package]] 429 | name = "mypy-extensions" 430 | version = "1.0.0" 431 | description = "Type system extensions for programs checked with the mypy type checker." 432 | optional = false 433 | python-versions = ">=3.5" 434 | groups = ["main", "dev"] 435 | files = [ 436 | {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, 437 | {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, 438 | ] 439 | 440 | [[package]] 441 | name = "numpy" 442 | version = "2.2.6" 443 | description = "Fundamental package for array computing in Python" 444 | optional = false 445 | python-versions = ">=3.10" 446 | groups = ["main"] 447 | files = [ 448 | {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, 449 | {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, 450 | {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163"}, 451 | {file = "numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf"}, 452 | {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83"}, 453 | {file = "numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915"}, 454 | {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680"}, 455 | {file = "numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289"}, 456 | {file = "numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d"}, 457 | {file = "numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3"}, 458 | {file = "numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae"}, 459 | {file = "numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a"}, 460 | {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42"}, 461 | {file = "numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491"}, 462 | {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a"}, 463 | {file = "numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf"}, 464 | {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1"}, 465 | {file = "numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab"}, 466 | {file = "numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47"}, 467 | {file = "numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303"}, 468 | {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"}, 469 | {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"}, 470 | {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"}, 471 | {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"}, 472 | {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"}, 473 | {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"}, 474 | {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"}, 475 | {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"}, 476 | {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"}, 477 | {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"}, 478 | {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"}, 479 | {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"}, 480 | {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"}, 481 | {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"}, 482 | {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"}, 483 | {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"}, 484 | {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"}, 485 | {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"}, 486 | {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"}, 487 | {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"}, 488 | {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"}, 489 | {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"}, 490 | {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"}, 491 | {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"}, 492 | {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"}, 493 | {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"}, 494 | {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"}, 495 | {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"}, 496 | {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"}, 497 | {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"}, 498 | {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d"}, 499 | {file = "numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db"}, 500 | {file = "numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543"}, 501 | {file = "numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00"}, 502 | {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"}, 503 | ] 504 | 505 | [[package]] 506 | name = "packaging" 507 | version = "24.2" 508 | description = "Core utilities for Python packages" 509 | optional = false 510 | python-versions = ">=3.8" 511 | groups = ["main", "dev"] 512 | files = [ 513 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, 514 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, 515 | ] 516 | 517 | [[package]] 518 | name = "parso" 519 | version = "0.8.4" 520 | description = "A Python Parser" 521 | optional = false 522 | python-versions = ">=3.6" 523 | groups = ["main"] 524 | files = [ 525 | {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, 526 | {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, 527 | ] 528 | 529 | [package.extras] 530 | qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] 531 | testing = ["docopt", "pytest"] 532 | 533 | [[package]] 534 | name = "pathspec" 535 | version = "0.12.1" 536 | description = "Utility library for gitignore style pattern matching of file paths." 537 | optional = false 538 | python-versions = ">=3.8" 539 | groups = ["main", "dev"] 540 | files = [ 541 | {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, 542 | {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, 543 | ] 544 | 545 | [[package]] 546 | name = "pexpect" 547 | version = "4.9.0" 548 | description = "Pexpect allows easy control of interactive console applications." 549 | optional = false 550 | python-versions = "*" 551 | groups = ["main"] 552 | markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" 553 | files = [ 554 | {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, 555 | {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, 556 | ] 557 | 558 | [package.dependencies] 559 | ptyprocess = ">=0.5" 560 | 561 | [[package]] 562 | name = "platformdirs" 563 | version = "4.3.6" 564 | description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." 565 | optional = false 566 | python-versions = ">=3.8" 567 | groups = ["main", "dev"] 568 | files = [ 569 | {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, 570 | {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, 571 | ] 572 | 573 | [package.extras] 574 | docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] 575 | test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] 576 | type = ["mypy (>=1.11.2)"] 577 | 578 | [[package]] 579 | name = "pluggy" 580 | version = "1.5.0" 581 | description = "plugin and hook calling mechanisms for python" 582 | optional = false 583 | python-versions = ">=3.8" 584 | groups = ["dev"] 585 | files = [ 586 | {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, 587 | {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, 588 | ] 589 | 590 | [package.extras] 591 | dev = ["pre-commit", "tox"] 592 | testing = ["pytest", "pytest-benchmark"] 593 | 594 | [[package]] 595 | name = "prompt-toolkit" 596 | version = "3.0.50" 597 | description = "Library for building powerful interactive command lines in Python" 598 | optional = false 599 | python-versions = ">=3.8.0" 600 | groups = ["main"] 601 | files = [ 602 | {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, 603 | {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, 604 | ] 605 | 606 | [package.dependencies] 607 | wcwidth = "*" 608 | 609 | [[package]] 610 | name = "psygnal" 611 | version = "0.12.0" 612 | description = "Fast python callback/event system modeled after Qt Signals" 613 | optional = false 614 | python-versions = ">=3.9" 615 | groups = ["main"] 616 | files = [ 617 | {file = "psygnal-0.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5e6c338e9ccb712f27bf74ae3bbfcf276308b8b01c293342255d69b7eeba61f"}, 618 | {file = "psygnal-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df404cfaedff3e58e81407371e596a1a0f0f81e8e5b16e85ea420e030ff558b6"}, 619 | {file = "psygnal-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c455c972071bc06403795f802623b54b0ba3217b7399520aee5e1e4ca71908e0"}, 620 | {file = "psygnal-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2998e59d12f113ed961e11b30ade9a37b3e0263157cac99d7ab11f5730d4febb"}, 621 | {file = "psygnal-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:a708d8af50b11d55c58bb36cc7e042f02561011398b869b749a62832a656706c"}, 622 | {file = "psygnal-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a9ee1e6c441074fe71765b0a96c75b19d72c8198ec5bdea7e97e06a6fe9bd41"}, 623 | {file = "psygnal-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cdb387d1d6f00649c970a8084e4ae3fcd3e38ac12b5c51d086fc9e01d8f7530"}, 624 | {file = "psygnal-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b410dab639353320044856cef68bd9aa940f8e1399da2f57522356b42bc4cf5d"}, 625 | {file = "psygnal-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd075d7bbe82f0615cfef953ed19ca54feba08f1686b42655c02d6ade0b0beb5"}, 626 | {file = "psygnal-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc763dbab05fb75f4517c8bd31ede6a4f27e68c59adca55b81a9d7bc875156e0"}, 627 | {file = "psygnal-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dac134c8890e3d0e413ab701fcb56a882f9b151a6a9d625080736c36833b26ed"}, 628 | {file = "psygnal-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc2324cef7ba3f4d30d32895f8cb7d5cf9ad7bcfdb7955aa92a0fbfe7537ec3f"}, 629 | {file = "psygnal-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae2bd6edcf911fbff34ed75150e8f8dfb246ebf203514c7c1e4397cabbb1368a"}, 630 | {file = "psygnal-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d6fbeee192beab90ca23d9d3ee3bf1eb7ef5f00e815fa53e23e402feee617242"}, 631 | {file = "psygnal-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:25a9f2db710a6cd2566b3e0e03cf6e04d56276f36ac86b42fa22d81f9a4ac0f2"}, 632 | {file = "psygnal-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2f4c1fed9337f57778109c397b6b9591961123ce4bbeb068115c0468964fc2b4"}, 633 | {file = "psygnal-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d5a953a50fc8263bb23bc558b926cf691f70c9c781c68c64c983fb8cbead910"}, 634 | {file = "psygnal-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a67ec8e0c8a6553dd56ed653f87c46ef652b0c512bb8c8f8c5adcff3907751f"}, 635 | {file = "psygnal-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:742abb2d0e230521b208161eeab06abb682a19239e734e543a269214c84a54d2"}, 636 | {file = "psygnal-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:d779f20c6977ec9d5b9fece23b4b28bbcf0a7773539a4a176b5527aea5da27c7"}, 637 | {file = "psygnal-0.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa1ce36ff7872d32f281ca6aab027c6d64abdef19ed269bc73a82e80f2a4fbb0"}, 638 | {file = "psygnal-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:33946be4b9b4efe9e5ced2800b040314c113e898e1310a02751c840af02674d5"}, 639 | {file = "psygnal-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69d74edb336e5e959ef5680c5393f8f6c7b7561fdcb9014dc2535c6ef3ea194"}, 640 | {file = "psygnal-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7986828b57861b3341803e519e09189cd93800adede9cfc450e72e1c4ea93f35"}, 641 | {file = "psygnal-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:ef3cae9af7a22f3c855cbc5b2cb219c5410b1076eaa6541f48cdb2c901a06ad7"}, 642 | {file = "psygnal-0.12.0-py3-none-any.whl", hash = "sha256:15f39abd8bee2926e79da76bec31a258d03dbe3e61d22d6251f65caefbae5d54"}, 643 | {file = "psygnal-0.12.0.tar.gz", hash = "sha256:8d2a99803f3152c469d3642d36c04d680213a20e114245558e026695adf9a9c2"}, 644 | ] 645 | 646 | [package.extras] 647 | dev = ["attrs", "dask[array] (>=2024.0.0)", "ipython", "msgspec", "mypy", "mypy-extensions", "numpy (>1.21.6)", "pre-commit", "pydantic", "pyinstaller (>=4.0)", "pyqt6", "pytest (>=6.0)", "pytest-cov", "pytest-mypy-plugins", "pytest-qt", "qtpy", "rich", "ruff", "toolz", "typing-extensions", "wrapt"] 648 | docs = ["griffe (==0.25.5)", "mkdocs (==1.4.2)", "mkdocs-material (==8.5.10)", "mkdocs-minify-plugin", "mkdocs-spellcheck[all]", "mkdocstrings (==0.20.0)", "mkdocstrings-python (==0.8.3)"] 649 | proxy = ["wrapt"] 650 | pydantic = ["pydantic"] 651 | test = ["attrs", "dask[array] (>=2024.0.0)", "msgspec", "numpy (>1.21.6)", "pydantic", "pyinstaller (>=4.0)", "pytest (>=6.0)", "pytest-cov", "toolz", "wrapt"] 652 | testqt = ["pytest-qt", "qtpy"] 653 | 654 | [[package]] 655 | name = "ptyprocess" 656 | version = "0.7.0" 657 | description = "Run a subprocess in a pseudo terminal" 658 | optional = false 659 | python-versions = "*" 660 | groups = ["main"] 661 | markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" 662 | files = [ 663 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 664 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 665 | ] 666 | 667 | [[package]] 668 | name = "pure-eval" 669 | version = "0.2.3" 670 | description = "Safely evaluate AST nodes without side effects" 671 | optional = false 672 | python-versions = "*" 673 | groups = ["main"] 674 | files = [ 675 | {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, 676 | {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, 677 | ] 678 | 679 | [package.extras] 680 | tests = ["pytest"] 681 | 682 | [[package]] 683 | name = "pygments" 684 | version = "2.19.1" 685 | description = "Pygments is a syntax highlighting package written in Python." 686 | optional = false 687 | python-versions = ">=3.8" 688 | groups = ["main"] 689 | files = [ 690 | {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, 691 | {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, 692 | ] 693 | 694 | [package.extras] 695 | windows-terminal = ["colorama (>=0.4.6)"] 696 | 697 | [[package]] 698 | name = "pytest" 699 | version = "8.3.5" 700 | description = "pytest: simple powerful testing with Python" 701 | optional = false 702 | python-versions = ">=3.8" 703 | groups = ["dev"] 704 | files = [ 705 | {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, 706 | {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, 707 | ] 708 | 709 | [package.dependencies] 710 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 711 | iniconfig = "*" 712 | packaging = "*" 713 | pluggy = ">=1.5,<2" 714 | 715 | [package.extras] 716 | dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 717 | 718 | [[package]] 719 | name = "pyyaml" 720 | version = "6.0.2" 721 | description = "YAML parser and emitter for Python" 722 | optional = false 723 | python-versions = ">=3.8" 724 | groups = ["main"] 725 | files = [ 726 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, 727 | {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, 728 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, 729 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, 730 | {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, 731 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, 732 | {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, 733 | {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, 734 | {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, 735 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, 736 | {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, 737 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, 738 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, 739 | {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, 740 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, 741 | {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, 742 | {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, 743 | {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, 744 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, 745 | {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, 746 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, 747 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, 748 | {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, 749 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, 750 | {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, 751 | {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, 752 | {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, 753 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, 754 | {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, 755 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, 756 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, 757 | {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, 758 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, 759 | {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, 760 | {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, 761 | {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, 762 | {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, 763 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, 764 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, 765 | {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, 766 | {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, 767 | {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, 768 | {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, 769 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, 770 | {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, 771 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, 772 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, 773 | {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, 774 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, 775 | {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, 776 | {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, 777 | {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, 778 | {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, 779 | ] 780 | 781 | [[package]] 782 | name = "ruff" 783 | version = "0.11.12" 784 | description = "An extremely fast Python linter and code formatter, written in Rust." 785 | optional = false 786 | python-versions = ">=3.7" 787 | groups = ["dev"] 788 | files = [ 789 | {file = "ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc"}, 790 | {file = "ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3"}, 791 | {file = "ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa"}, 792 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012"}, 793 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a"}, 794 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7"}, 795 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a"}, 796 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13"}, 797 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be"}, 798 | {file = "ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd"}, 799 | {file = "ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef"}, 800 | {file = "ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5"}, 801 | {file = "ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02"}, 802 | {file = "ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c"}, 803 | {file = "ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6"}, 804 | {file = "ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832"}, 805 | {file = "ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5"}, 806 | {file = "ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603"}, 807 | ] 808 | 809 | [[package]] 810 | name = "stack-data" 811 | version = "0.6.3" 812 | description = "Extract data from python stack frames and tracebacks for informative displays" 813 | optional = false 814 | python-versions = "*" 815 | groups = ["main"] 816 | files = [ 817 | {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, 818 | {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, 819 | ] 820 | 821 | [package.dependencies] 822 | asttokens = ">=2.1.0" 823 | executing = ">=1.2.0" 824 | pure-eval = "*" 825 | 826 | [package.extras] 827 | tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] 828 | 829 | [[package]] 830 | name = "traitlets" 831 | version = "5.14.3" 832 | description = "Traitlets Python configuration system" 833 | optional = false 834 | python-versions = ">=3.8" 835 | groups = ["main"] 836 | files = [ 837 | {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, 838 | {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, 839 | ] 840 | 841 | [package.extras] 842 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 843 | test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] 844 | 845 | [[package]] 846 | name = "typing-extensions" 847 | version = "4.12.2" 848 | description = "Backported and Experimental Type Hints for Python 3.8+" 849 | optional = false 850 | python-versions = ">=3.8" 851 | groups = ["main", "dev"] 852 | files = [ 853 | {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, 854 | {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, 855 | ] 856 | 857 | [[package]] 858 | name = "wcwidth" 859 | version = "0.2.13" 860 | description = "Measures the displayed width of unicode strings in a terminal" 861 | optional = false 862 | python-versions = "*" 863 | groups = ["main"] 864 | files = [ 865 | {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, 866 | {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, 867 | ] 868 | 869 | [[package]] 870 | name = "widgetsnbextension" 871 | version = "4.0.13" 872 | description = "Jupyter interactive widgets for Jupyter Notebook" 873 | optional = false 874 | python-versions = ">=3.7" 875 | groups = ["main"] 876 | files = [ 877 | {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, 878 | {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, 879 | ] 880 | 881 | [metadata] 882 | lock-version = "2.1" 883 | python-versions = "^3.12" 884 | content-hash = "a448ef731a849d88eecac209509c21eac6efd18c5fd220fb7568fcf3b9159ef5" 885 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Poetry 2 | [tool.poetry] 3 | name = "mlir-egglog" 4 | version = "0.1.0" 5 | description = "" 6 | authors = ["sdiehl "] 7 | readme = "README.md" 8 | packages = [{include = "mlir_egglog", from = "src"}] 9 | 10 | # UV 11 | [project] 12 | name = "mlir-egglog" 13 | version = "0.1.0" 14 | requires-python = ">=3.12" 15 | dependencies = [ 16 | "egglog>=8.0.1", 17 | "llvmlite>=0.44.0", 18 | "numpy>=2.2.5", 19 | "pyyaml>=6.0.2", 20 | ] 21 | 22 | # Poetry 23 | [tool.poetry.dependencies] 24 | python = "^3.12" 25 | egglog = "^10.0.1" 26 | llvmlite = "^0.44.0" 27 | numpy = "^2.2.6" 28 | pyyaml = "^6.0.2" 29 | 30 | [tool.poetry.group.dev.dependencies] 31 | ruff = "^0.11.12" 32 | mypy = "^1.16.0" 33 | black = "^25.1.0" 34 | pytest = "^8.3.5" 35 | 36 | # UV 37 | [dependency-groups] 38 | dev = [ 39 | "black>=25.1.0", 40 | "mypy>=1.15.0", 41 | "ruff>=0.9.10", 42 | "pytest>=8.3.5", 43 | ] 44 | 45 | [build-system] 46 | requires = ["poetry-core"] 47 | build-backend = "poetry.core.masonry.api" 48 | 49 | [tool.mypy] 50 | disable_error_code = ["import-untyped"] 51 | disallow_incomplete_defs = "false" 52 | disallow_untyped_defs = "false" 53 | pretty = "true" 54 | # strict = "true" 55 | warn_unused_configs = "true" 56 | warn_redundant_casts = "true" 57 | warn_unused_ignores = "true" 58 | ignore_missing_imports = "true" 59 | no_implicit_reexport = "true" 60 | strict_optional = "true" 61 | strict_equality = "true" 62 | extra_checks = "true" 63 | disallow_subclassing_any = "false" 64 | disallow_untyped_decorators = "false" 65 | disallow_any_generics = "true" 66 | follow_imports = "silent" 67 | -------------------------------------------------------------------------------- /src/mlir_egglog/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | MLIR-Egglog: A Python library for optimizing and compiling numerical expressions using MLIR and egglog. 3 | """ 4 | 5 | import llvmlite 6 | 7 | llvmlite.opaque_pointers_enabled = True 8 | 9 | # Version of the mlir-egglog package 10 | __version__ = "0.1.0" 11 | 12 | from mlir_egglog.expr_model import ( # noqa: E402 13 | BinaryOp, 14 | FloatLiteral, 15 | IntLiteral, 16 | Symbol, 17 | ) 18 | from mlir_egglog.term_ir import Term, as_egraph # noqa: E402 19 | from mlir_egglog.dispatcher import kernel # noqa: E402 20 | 21 | __all__ = [ 22 | "BinaryOp", 23 | "FloatLiteral", 24 | "IntLiteral", 25 | "Symbol", 26 | "Term", 27 | "as_egraph", 28 | "kernel", 29 | ] 30 | -------------------------------------------------------------------------------- /src/mlir_egglog/basic_simplify.py: -------------------------------------------------------------------------------- 1 | from mlir_egglog.term_ir import Term, Add, Mul, Div, Pow, PowConst, birewrite_subsume 2 | from egglog import RewriteOrRule, ruleset, rewrite, i64, f64 3 | from typing import Generator 4 | 5 | 6 | @ruleset 7 | def basic_math( 8 | x: Term, y: Term, z: Term, i: i64, f: f64 9 | ) -> Generator[RewriteOrRule, None, None]: 10 | # Allow us to translate Term into their specializations 11 | yield from birewrite_subsume(x + y, Add(x, y)) 12 | yield from birewrite_subsume(x * y, Mul(x, y)) 13 | yield from birewrite_subsume(x / y, Div(x, y)) 14 | yield from birewrite_subsume(x**y, Pow(x, y)) 15 | 16 | # x + 0 = x (integer case) 17 | yield rewrite(Add(x, Term.lit_i64(0))).to(x) 18 | # x + 0.0 = x (float case) 19 | yield rewrite(Add(x, Term.lit_f32(0.0))).to(x) 20 | # 0.0 + x = x (float case) 21 | yield rewrite(Add(Term.lit_f32(0.0), x)).to(x) 22 | 23 | # x * 1 = x 24 | yield rewrite(Mul(x, Term.lit_i64(1))).to(x) 25 | 26 | # x * 0 = 0 27 | yield rewrite(Mul(x, Term.lit_i64(0))).to(Term.lit_i64(0)) 28 | 29 | # (x + y) + z = x + (y + z) 30 | yield rewrite(Add(x, Add(y, z))).to(Add(Add(x, y), z)) 31 | 32 | # (x * y) * z = x * (y * z) 33 | yield rewrite(Mul(x, Mul(y, z))).to(Mul(Mul(x, y), z)) 34 | 35 | # x + x = 2 * x 36 | yield rewrite(Add(x, x)).to(Mul(Term.lit_i64(2), x)) 37 | 38 | # x * x = x^2 39 | yield rewrite(Mul(x, x)).to(Pow(x, Term.lit_i64(2))) 40 | 41 | # (x^y) * (x^z) = x^(y + z) 42 | yield rewrite(Pow(x, y) * Pow(x, z)).to(Pow(x, Add(y, z))) 43 | 44 | # x^i = x * x^(i - 1) 45 | yield rewrite(Pow(x, Term.lit_i64(i))).to(PowConst(x, i)) 46 | 47 | # x^0 = 1 48 | yield rewrite(PowConst(x, 0)).to(Term.lit_f32(1.0)) 49 | 50 | # x^1 = x 51 | yield rewrite(PowConst(x, 1)).to(x) 52 | 53 | # x^i = x * x^(i - 1) 54 | yield rewrite(PowConst(x, i)).to(Mul(x, PowConst(x, i - 1)), i > 1) 55 | -------------------------------------------------------------------------------- /src/mlir_egglog/builtin_functions.py: -------------------------------------------------------------------------------- 1 | # A mock NumPy namespace that we convert into our own expression model 2 | 3 | import math 4 | from mlir_egglog.expr_model import ( 5 | sin, 6 | cos, 7 | tan, 8 | asin, 9 | acos, 10 | atan, 11 | tanh, 12 | sinh, 13 | cosh, 14 | sqrt, 15 | exp, 16 | log, 17 | log10, 18 | log2, 19 | float32, 20 | int64, 21 | maximum, 22 | ) # noq 23 | 24 | # Constants 25 | e = math.e 26 | pi = math.pi 27 | 28 | 29 | # Define abs function 30 | def abs(x): 31 | return maximum(x, -x) 32 | 33 | 34 | def relu(x): 35 | return maximum(x, 0.0) 36 | 37 | 38 | def sigmoid(x): 39 | return 1.0 / (1.0 + exp(-x)) 40 | 41 | 42 | __all__ = [ 43 | "sin", 44 | "cos", 45 | "tan", 46 | "asin", 47 | "acos", 48 | "atan", 49 | "tanh", 50 | "sinh", 51 | "cosh", 52 | "sqrt", 53 | "exp", 54 | "log", 55 | "log10", 56 | "log2", 57 | "float32", 58 | "int64", 59 | "e", 60 | "pi", 61 | "maximum", 62 | "abs", 63 | ] 64 | -------------------------------------------------------------------------------- /src/mlir_egglog/dispatcher.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ctypes 4 | import types 5 | import numpy as np 6 | from egglog import RewriteOrRule 7 | 8 | from mlir_egglog.jit_engine import JITEngine 9 | from mlir_egglog.memory_descriptors import as_memref_descriptor 10 | 11 | 12 | class Dispatcher: 13 | """ 14 | Dispatch a python function to a compiled vectorized kernel. 15 | """ 16 | 17 | _compiled_func: bytes | None 18 | _compiler: JITEngine | None 19 | py_func: types.FunctionType | types.MethodType 20 | rewrites: tuple[RewriteOrRule, ...] | None 21 | 22 | def __init__( 23 | self, 24 | py_func: types.FunctionType, 25 | rewrites: tuple[RewriteOrRule, ...] | None = None, 26 | ): 27 | self.py_func = py_func 28 | self._compiled_func = None 29 | self._compiler = None 30 | self.rewrites = rewrites 31 | 32 | def compile(self): 33 | self._compiler = JITEngine() 34 | binary = self._compiler.jit_compile(self.py_func, self.rewrites) 35 | self._compiled_func = binary 36 | return binary 37 | 38 | def __call__(self, *args, **kwargs): 39 | assert not kwargs 40 | if self._compiled_func is None: 41 | raise RuntimeError("Function must be compiled before calling") 42 | 43 | # Get the input array and its shape 44 | input_array = args[0] 45 | original_shape = input_array.shape 46 | 47 | # Flatten the input array 48 | flattened_input = input_array.flatten() 49 | 50 | # Create a flattened result array 51 | flattened_output = np.empty_like(flattened_input) 52 | 53 | # Convert to memrefs 54 | memrefs = [ 55 | as_memref_descriptor(flattened_input, ctypes.c_float), 56 | as_memref_descriptor(flattened_output, ctypes.c_float), 57 | ] 58 | 59 | # Create a prototype for the C function 60 | prototype = ctypes.CFUNCTYPE(None, *[ctypes.POINTER(type(x)) for x in memrefs]) 61 | 62 | # Call the compiled function 63 | cfunc = prototype(self._compiled_func) 64 | cfunc(*[ctypes.byref(x) for x in memrefs]) 65 | 66 | # Reshape the output to match the input shape 67 | output = flattened_output.reshape(original_shape) 68 | return output 69 | 70 | 71 | def kernel(func_or_sig, rewrites: tuple[RewriteOrRule, ...] | None = None): 72 | """ 73 | Decorator to compile a Python function into a vectorized kernel. 74 | """ 75 | if isinstance(func_or_sig, types.FunctionType): 76 | return Dispatcher(func_or_sig, rewrites) 77 | elif isinstance(func_or_sig, str): 78 | 79 | def make_dispatcher(py_func: types.FunctionType) -> Dispatcher: 80 | disp = Dispatcher(py_func, rewrites) 81 | disp.compile() 82 | return disp 83 | 84 | return make_dispatcher 85 | else: 86 | raise TypeError("Expected a python function or a string signature") 87 | -------------------------------------------------------------------------------- /src/mlir_egglog/egglog_optimizer.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from types import FunctionType 3 | 4 | from egglog import EGraph, RewriteOrRule, Ruleset 5 | from egglog.egraph import UnstableCombinedRuleset 6 | 7 | from mlir_egglog.term_ir import Term, as_egraph 8 | from mlir_egglog.python_to_ir import interpret 9 | from mlir_egglog import builtin_functions as ns 10 | from mlir_egglog.expr_model import Expr 11 | from mlir_egglog.ir_to_mlir import convert_term_to_mlir 12 | 13 | # Rewrite rules 14 | from mlir_egglog.basic_simplify import basic_math 15 | from mlir_egglog.trig_simplify import trig_simplify 16 | 17 | OPTS: tuple[Ruleset | RewriteOrRule, ...] = (basic_math, trig_simplify) 18 | 19 | 20 | def extract(ast: Expr, rules: tuple[RewriteOrRule | Ruleset, ...], debug=False) -> Term: 21 | root = as_egraph(ast) 22 | 23 | egraph = EGraph() 24 | egraph.let("root", root) 25 | 26 | # The user can compose rules as (rule1 | rule2) to apply them in parallel 27 | # or (rule1, rule2) to apply them sequentially 28 | for opt in rules: 29 | if isinstance(opt, Ruleset): 30 | egraph.run(opt.saturate()) 31 | elif isinstance(opt, UnstableCombinedRuleset): 32 | egraph.run(opt.saturate()) 33 | else: 34 | # For individual rules, create a temporary ruleset 35 | temp_ruleset = Ruleset("temp") 36 | temp_ruleset.append(opt) 37 | egraph.run(temp_ruleset.saturate()) 38 | 39 | extracted = egraph.extract(root) 40 | 41 | # if debug: 42 | # egraph.display() 43 | 44 | return extracted 45 | 46 | 47 | def compile( 48 | fn: FunctionType, rewrites: tuple[RewriteOrRule | Ruleset, ...] = OPTS, debug=True 49 | ) -> str: 50 | # Convert np functions accordinging to the namespace map 51 | exprtree = interpret(fn, {"np": ns}) 52 | extracted = extract(exprtree, rewrites, debug) 53 | 54 | # Get the argument spec 55 | argspec = inspect.signature(fn) 56 | params = ",".join(map(str, argspec.parameters)) 57 | 58 | return convert_term_to_mlir(extracted, params) 59 | -------------------------------------------------------------------------------- /src/mlir_egglog/expr_model.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | from typing import Any 3 | from dataclasses import dataclass 4 | 5 | 6 | @dataclass(frozen=True) 7 | class Expr: 8 | def __add__(self, other: Expr) -> Expr: 9 | return Add(self, as_expr(other)) 10 | 11 | def __radd__(self, other: Expr) -> Expr: 12 | return Add(as_expr(other), self) 13 | 14 | def __mul__(self, other: Expr) -> Expr: 15 | return Mul(self, as_expr(other)) 16 | 17 | def __rmul__(self, other: Expr) -> Expr: 18 | return Mul(as_expr(other), self) 19 | 20 | def __truediv__(self, other: Expr) -> Expr: 21 | return Div(self, as_expr(other)) 22 | 23 | def __rtruediv__(self, other: Expr) -> Expr: 24 | return Div(as_expr(other), self) 25 | 26 | def __sub__(self, other: Expr) -> Expr: 27 | return Sub(self, as_expr(other)) 28 | 29 | def __rsub__(self, other: Expr) -> Expr: 30 | return Sub(as_expr(other), self) 31 | 32 | def __pow__(self, other: Expr) -> Expr: 33 | return Pow(self, as_expr(other)) 34 | 35 | def __neg__(self) -> Expr: 36 | return Neg(self) 37 | 38 | 39 | @dataclass(frozen=True) 40 | class FloatLiteral(Expr): 41 | """ 42 | A floating-point constant in the expression tree. 43 | """ 44 | 45 | fval: float 46 | 47 | 48 | @dataclass(frozen=True) 49 | class IntLiteral(Expr): 50 | """ 51 | An integer constant in the expression tree. 52 | """ 53 | 54 | ival: float 55 | 56 | 57 | @dataclass(frozen=True) 58 | class Symbol(Expr): 59 | """ 60 | A variable or function name in the expression tree. 61 | """ 62 | 63 | name: str 64 | 65 | 66 | @dataclass(frozen=True) 67 | class UnaryOp(Expr): 68 | """ 69 | Base class for unary operations in the expression tree. 70 | """ 71 | 72 | operand: Expr 73 | 74 | 75 | @dataclass(frozen=True) 76 | class BinaryOp(Expr): 77 | """ 78 | Base class for binary operations in the expression tree. 79 | """ 80 | 81 | lhs: Expr 82 | rhs: Expr 83 | 84 | 85 | @dataclass(frozen=True) 86 | class Add(BinaryOp): 87 | """Addition operation""" 88 | 89 | pass 90 | 91 | 92 | @dataclass(frozen=True) 93 | class Mul(BinaryOp): 94 | """Multiplication operation""" 95 | 96 | pass 97 | 98 | 99 | @dataclass(frozen=True) 100 | class Div(BinaryOp): 101 | """Division operation""" 102 | 103 | pass 104 | 105 | 106 | @dataclass(frozen=True) 107 | class Pow(BinaryOp): 108 | """Power operation""" 109 | 110 | pass 111 | 112 | 113 | # Transcendental Functions 114 | @dataclass(frozen=True) 115 | class Sin(UnaryOp): 116 | """Sine operation""" 117 | 118 | pass 119 | 120 | 121 | @dataclass(frozen=True) 122 | class Cos(UnaryOp): 123 | """Cosine operation""" 124 | 125 | pass 126 | 127 | 128 | @dataclass(frozen=True) 129 | class Tan(UnaryOp): 130 | """Tangent operation""" 131 | 132 | pass 133 | 134 | 135 | @dataclass(frozen=True) 136 | class ASin(UnaryOp): 137 | """Arc sine operation""" 138 | 139 | pass 140 | 141 | 142 | @dataclass(frozen=True) 143 | class ACos(UnaryOp): 144 | """Arc cosine operation""" 145 | 146 | pass 147 | 148 | 149 | @dataclass(frozen=True) 150 | class ATan(UnaryOp): 151 | """Arc tangent operation""" 152 | 153 | pass 154 | 155 | 156 | @dataclass(frozen=True) 157 | class Sqrt(UnaryOp): 158 | """Square root operation""" 159 | 160 | pass 161 | 162 | 163 | @dataclass(frozen=True) 164 | class Tanh(UnaryOp): 165 | """Hyperbolic tangent operation""" 166 | 167 | pass 168 | 169 | 170 | @dataclass(frozen=True) 171 | class Sinh(UnaryOp): 172 | """Hyperbolic sine operation""" 173 | 174 | pass 175 | 176 | 177 | @dataclass(frozen=True) 178 | class Cosh(UnaryOp): 179 | """Hyperbolic cosine operation""" 180 | 181 | pass 182 | 183 | 184 | @dataclass(frozen=True) 185 | class Exp(UnaryOp): 186 | """Exponential operation""" 187 | 188 | pass 189 | 190 | 191 | @dataclass(frozen=True) 192 | class Log(UnaryOp): 193 | """Natural logarithm operation""" 194 | 195 | pass 196 | 197 | 198 | @dataclass(frozen=True) 199 | class Log10(UnaryOp): 200 | """Base-10 logarithm operation""" 201 | 202 | pass 203 | 204 | 205 | @dataclass(frozen=True) 206 | class Log2(UnaryOp): 207 | """Base-2 logarithm operation""" 208 | 209 | pass 210 | 211 | 212 | # Type Casting Operations 213 | @dataclass(frozen=True) 214 | class CastF32(UnaryOp): 215 | """Cast to float32 operation""" 216 | 217 | pass 218 | 219 | 220 | @dataclass(frozen=True) 221 | class CastI64(UnaryOp): 222 | """Cast to int64 operation""" 223 | 224 | pass 225 | 226 | 227 | @dataclass(frozen=True) 228 | class Maximum(BinaryOp): 229 | """Maximum operation""" 230 | 231 | pass 232 | 233 | 234 | @dataclass(frozen=True) 235 | class Neg(UnaryOp): 236 | """Negation operation""" 237 | 238 | pass 239 | 240 | 241 | @dataclass(frozen=True) 242 | class Sub(BinaryOp): 243 | """Subtraction operation""" 244 | 245 | pass 246 | 247 | 248 | # Helper functions for creating operations 249 | def sin(x: Expr) -> Expr: 250 | return Sin(as_expr(x)) 251 | 252 | 253 | def cos(x: Expr) -> Expr: 254 | return Cos(as_expr(x)) 255 | 256 | 257 | def tan(x: Expr) -> Expr: 258 | return Tan(as_expr(x)) 259 | 260 | 261 | def asin(x: Expr) -> Expr: 262 | return ASin(as_expr(x)) 263 | 264 | 265 | def acos(x: Expr) -> Expr: 266 | return ACos(as_expr(x)) 267 | 268 | 269 | def atan(x: Expr) -> Expr: 270 | return ATan(as_expr(x)) 271 | 272 | 273 | def sqrt(x: Expr) -> Expr: 274 | return Sqrt(as_expr(x)) 275 | 276 | 277 | def tanh(x: Expr) -> Expr: 278 | return Tanh(as_expr(x)) 279 | 280 | 281 | def sinh(x: Expr) -> Expr: 282 | return Sinh(as_expr(x)) 283 | 284 | 285 | def cosh(x: Expr) -> Expr: 286 | return Cosh(as_expr(x)) 287 | 288 | 289 | def exp(x: Expr) -> Expr: 290 | return Exp(as_expr(x)) 291 | 292 | 293 | def log(x: Expr) -> Expr: 294 | return Log(as_expr(x)) 295 | 296 | 297 | def log10(x: Expr) -> Expr: 298 | return Log10(as_expr(x)) 299 | 300 | 301 | def log2(x: Expr) -> Expr: 302 | return Log2(as_expr(x)) 303 | 304 | 305 | def float32(x: Expr) -> Expr: 306 | return CastF32(as_expr(x)) 307 | 308 | 309 | def int64(x: Expr) -> Expr: 310 | return CastI64(as_expr(x)) 311 | 312 | 313 | def maximum(x: Expr, y: Expr) -> Expr: 314 | return Maximum(as_expr(x), as_expr(y)) 315 | 316 | 317 | def as_expr(val: Any) -> Expr: 318 | """ 319 | Convert dynamic Python values to Expr nodes. 320 | """ 321 | match val: 322 | case float(x): 323 | return FloatLiteral(x) 324 | case int(x): 325 | return IntLiteral(x) 326 | case Expr() as x: 327 | return x 328 | case _: 329 | raise TypeError(type(val)) 330 | -------------------------------------------------------------------------------- /src/mlir_egglog/ir_to_mlir.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from typing import Any 3 | 4 | from mlir_egglog import expr_model as ir 5 | from mlir_egglog.term_ir import Term as IRTerm, PowConst as IRPowConst 6 | from mlir_egglog import builtin_functions as builtins # noqa: F401 7 | from mlir_egglog.mlir_gen import MLIRGen 8 | 9 | 10 | class Term: 11 | """ 12 | A class for creating expressions from terms. 13 | """ 14 | 15 | @classmethod 16 | def lit_f32(cls, v: float) -> ir.Expr: 17 | return ir.FloatLiteral(v) 18 | 19 | @classmethod 20 | def lit_f64(cls, v: float) -> ir.Expr: 21 | return ir.FloatLiteral(v) 22 | 23 | @classmethod 24 | def lit_i64(cls, v: int) -> ir.Expr: 25 | return ir.IntLiteral(v) 26 | 27 | @classmethod 28 | def var(cls, k: str) -> ir.Expr: 29 | return ir.Symbol(k) 30 | 31 | 32 | function_map = { 33 | "Term": Term, 34 | "Add": ir.Expr.__add__, 35 | "Sub": ir.Expr.__sub__, 36 | "Mul": ir.Expr.__mul__, 37 | "Div": ir.Expr.__truediv__, 38 | "Pow": ir.Expr.__pow__, 39 | "PowConst": IRPowConst, 40 | "Neg": ir.Expr.__neg__, 41 | "Exp": builtins.exp, 42 | "Log": builtins.log, 43 | "Sin": builtins.sin, 44 | "Cos": builtins.cos, 45 | "Tan": builtins.tan, 46 | "Tanh": builtins.tanh, 47 | "Sinh": builtins.sinh, 48 | "Cosh": builtins.cosh, 49 | "Sqrt": builtins.sqrt, 50 | "CastF32": builtins.float32, 51 | "CastI64": builtins.int64, 52 | "Maximum": builtins.maximum, 53 | "Symbol": ir.Symbol, 54 | "Const": ir.IntLiteral, 55 | } 56 | 57 | 58 | def mangle_assignment(tree: ast.AST) -> ast.AST: 59 | """ 60 | Mangle an AST tree to attach the result variable to the end of the tree assigned to a 61 | variable named `_out`. 62 | 63 | >>> mangle_assignment(ast.parse("1+2")) 64 | _out = 1 + 2 65 | """ 66 | 67 | match tree: 68 | case ast.Expr() as expr: 69 | return ast.Assign( 70 | targets=[ast.Name("_out", ast.Store())], 71 | value=expr.value, 72 | ) 73 | return tree 74 | 75 | 76 | def convert_term_to_expr(tree: IRTerm) -> ir.Expr: 77 | """ 78 | Convert a term to an expression. 79 | """ 80 | 81 | # Parse the term into an AST 82 | astree = ast.parse(str(tree)) 83 | 84 | # Mangle the assignment 85 | astree.body[-1] = ast.fix_missing_locations(mangle_assignment(astree.body[-1])) # type: ignore 86 | 87 | # Execute the AST 88 | globals: dict[str, Any] = {} 89 | exec(compile(astree, "", "exec"), function_map, globals) 90 | 91 | # Get the result 92 | result = globals["_out"] 93 | return result 94 | 95 | 96 | def convert_term_to_mlir(tree: IRTerm, argspec: str) -> str: 97 | """ 98 | Convert a term to MLIR. 99 | """ 100 | 101 | expr = convert_term_to_expr(tree) 102 | argnames = map(lambda x: x.strip(), argspec.split(",")) 103 | argmap = {k: f"%arg_{k}" for k in argnames} 104 | source = MLIRGen(expr, argmap).generate() 105 | return source 106 | -------------------------------------------------------------------------------- /src/mlir_egglog/jit_engine.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import ctypes 4 | import ctypes.util 5 | import os 6 | import sys 7 | from types import FunctionType 8 | from egglog import RewriteOrRule, Ruleset 9 | 10 | import llvmlite.binding as llvm 11 | 12 | from mlir_egglog.llvm_runtime import ( 13 | create_execution_engine, 14 | init_llvm, 15 | compile_mod, 16 | ) 17 | from mlir_egglog.mlir_gen import KERNEL_NAME 18 | from mlir_egglog.mlir_backend import MLIRCompiler, Target 19 | from mlir_egglog.egglog_optimizer import compile, OPTS 20 | 21 | 22 | def find_omp_path(): 23 | if sys.platform.startswith("linux"): 24 | omppath = ctypes.util.find_library("libgomp.so") 25 | elif sys.platform.startswith("darwin"): 26 | omppath = ctypes.util.find_library("iomp5") 27 | else: 28 | raise RuntimeError(f"Unsupported platform: {sys.platform}") 29 | return omppath 30 | 31 | 32 | class JITEngine: 33 | def __init__(self): 34 | init_llvm() 35 | omppath = find_omp_path() 36 | ctypes.CDLL(omppath, mode=os.RTLD_NOW) 37 | 38 | self.ee = create_execution_engine() 39 | 40 | def run_frontend( 41 | self, 42 | fn: FunctionType, 43 | rewrites: tuple[RewriteOrRule | Ruleset, ...] | None = None, 44 | ) -> str: 45 | actual_rewrites = rewrites if rewrites is not None else OPTS 46 | return compile(fn, rewrites=actual_rewrites, debug=False) 47 | 48 | def run_backend(self, mlir_src: str) -> bytes: 49 | mlir_compiler = MLIRCompiler(debug=False) 50 | 51 | mlir_omp = mlir_compiler.to_llvm_dialect(mlir_src, target=Target.BASIC_LOOPS) 52 | llvm_ir = mlir_compiler.mlir_translate_to_llvm_ir(mlir_omp) 53 | 54 | print(llvm_ir) 55 | print("Parsing LLVM assembly.") 56 | 57 | try: 58 | # Clean up the LLVM IR by ensuring proper line endings and formatting 59 | llvm_ir = llvm_ir.strip() 60 | 61 | # Clean up problematic attribute strings (hack for divergence in modern LLVM IR syntax with old llvmlite) 62 | llvm_ir = llvm_ir.replace("captures(none)", " ") 63 | llvm_ir = llvm_ir.replace("memory(argmem: readwrite)", "") 64 | llvm_ir = llvm_ir.replace("memory(none)", "") 65 | llvm_ir += "\n" 66 | 67 | mod = llvm.parse_assembly(llvm_ir) 68 | mod = compile_mod(self.ee, mod) 69 | 70 | # Resolve the function address 71 | func_name = f"_mlir_ciface_{KERNEL_NAME}" 72 | address = self.ee.get_function_address(func_name) 73 | 74 | assert address, "Function must be compiled successfully." 75 | return address 76 | except Exception as e: 77 | print(f"Error during LLVM IR parsing/compilation: {str(e)}") 78 | print("LLVM IR that failed to parse:") 79 | print(llvm_ir) 80 | raise 81 | 82 | def jit_compile( 83 | self, 84 | fn: FunctionType, 85 | rewrites: tuple[RewriteOrRule | Ruleset, ...] | None = None, 86 | ) -> bytes: 87 | mlir = self.run_frontend(fn, rewrites) 88 | address = self.run_backend(mlir) 89 | return address 90 | -------------------------------------------------------------------------------- /src/mlir_egglog/llvm_runtime.py: -------------------------------------------------------------------------------- 1 | import llvmlite.binding as llvm 2 | import llvmlite 3 | from functools import cache 4 | 5 | 6 | @cache 7 | def init_llvm(): 8 | print(llvmlite.__version__) 9 | llvm.initialize() 10 | llvm.initialize_all_targets() 11 | llvm.initialize_native_asmprinter() 12 | llvm.initialize_native_asmparser() 13 | return None 14 | 15 | 16 | def compile_mod(engine, mod): 17 | mod.verify() 18 | engine.add_module(mod) 19 | engine.finalize_object() 20 | engine.run_static_constructors() 21 | return mod 22 | 23 | 24 | def create_execution_engine(): 25 | target = llvm.Target.from_default_triple() 26 | target_machine = target.create_target_machine() 27 | backing_mod = llvm.parse_assembly("") 28 | engine = llvm.create_mcjit_compiler(backing_mod, target_machine) 29 | return engine 30 | 31 | 32 | def compile_ir(engine, llvm_ir): 33 | mod = llvm.parse_assembly(llvm_ir) 34 | return compile_mod(engine, mod) 35 | -------------------------------------------------------------------------------- /src/mlir_egglog/memory_descriptors.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | from typing import Type, Any 3 | import numpy.typing as npt 4 | 5 | 6 | def build_struct(ty_ptr, intptr_t, N) -> Type[ctypes.Structure]: 7 | """ 8 | Build a ctypes structure for a NumPy array of a given element type. 9 | """ 10 | 11 | class MemRefDescriptor(ctypes.Structure): 12 | _fields_ = [ 13 | ("allocated", ty_ptr), 14 | ("aligned", ty_ptr), 15 | ("offset", intptr_t), 16 | ("sizes", intptr_t * N), 17 | ("strides", intptr_t * N), 18 | ] 19 | 20 | return MemRefDescriptor 21 | 22 | 23 | def as_memref_descriptor(arr: npt.NDArray[Any], ty: Type[Any]) -> ctypes.Structure: 24 | """ 25 | Convert a numpy array to a memref descriptor 26 | """ 27 | N = arr.ndim 28 | ty_ptr = ctypes.POINTER(ty) 29 | 30 | intptr_t = getattr(ctypes, f"c_int{8 * ctypes.sizeof(ctypes.c_void_p)}") 31 | struct_constructor = build_struct(ty_ptr, intptr_t, N) 32 | 33 | allocated = ctypes.cast(arr.ctypes.data, ty_ptr) 34 | aligned = allocated 35 | offset = intptr_t(0) 36 | sizes = (intptr_t * N)(*arr.shape) 37 | strides = (intptr_t * N)(*arr.strides) 38 | 39 | # Return the memref descriptor 40 | return struct_constructor(allocated, aligned, offset, sizes, strides) 41 | -------------------------------------------------------------------------------- /src/mlir_egglog/mlir_backend.py: -------------------------------------------------------------------------------- 1 | from tempfile import NamedTemporaryFile 2 | import subprocess 3 | import enum 4 | 5 | 6 | class Target(enum.Enum): 7 | OPENMP = "openmp" 8 | BASIC_LOOPS = "loops" 9 | 10 | 11 | # MLIR_BIN = "/Users/sdiehl/Downloads/bin/mlir-opt" 12 | # MLIR_TRANSLATE_BIN = "/Users/sdiehl/Downloads/bin/mlir-translate" 13 | 14 | MLIR_BIN = "mlir-opt" 15 | MLIR_TRANSLATE_BIN = "mlir-translate" 16 | 17 | # Debug options for MLIR compilation 18 | DEBUG_OPTIONS = ( 19 | "--mlir-print-debuginfo", 20 | "--mlir-print-ir-after-all", 21 | "--debug-pass=Details", 22 | ) 23 | 24 | # Common initial transformations for both paths 25 | COMMON_INITIAL_OPTIONS = ( 26 | "--debugify-level=locations", 27 | "--snapshot-op-locations", 28 | "--inline", 29 | "-affine-loop-normalize", 30 | "-affine-parallelize", 31 | "-affine-super-vectorize", 32 | "--affine-scalrep", 33 | "-lower-affine", 34 | "-convert-vector-to-scf", 35 | "-convert-linalg-to-loops", 36 | "-lower-affine", 37 | ) 38 | 39 | # OpenMP lowering sequence 40 | OPENMP_OPTIONS = ( 41 | "-convert-scf-to-openmp", 42 | "-convert-scf-to-cf", 43 | "-cse", 44 | "-convert-openmp-to-llvm", 45 | "-convert-vector-to-llvm", 46 | "-convert-math-to-llvm", 47 | "-expand-strided-metadata", 48 | "-finalize-memref-to-llvm", 49 | "-convert-func-to-llvm", 50 | "-convert-index-to-llvm", 51 | "-convert-arith-to-llvm", 52 | "-reconcile-unrealized-casts", 53 | "--llvm-request-c-wrappers", 54 | ) 55 | 56 | # Basic loops lowering sequence 57 | BASIC_LOOPS_OPTIONS = ( 58 | "-convert-scf-to-cf", 59 | "-cse", 60 | "-convert-vector-to-llvm", 61 | "-convert-math-to-llvm", 62 | "-expand-strided-metadata", 63 | "-finalize-memref-to-llvm", 64 | "-convert-func-to-llvm", 65 | "-convert-index-to-llvm", 66 | "-convert-arith-to-llvm", 67 | "-convert-cf-to-llvm", 68 | "-reconcile-unrealized-casts", 69 | "--llvm-request-c-wrappers", 70 | ) 71 | 72 | # MLIR to LLVM IR translation options 73 | MLIR_TRANSLATE_OPTIONS = ( 74 | "--mlir-print-local-scope", 75 | "--mlir-print-debuginfo=false", 76 | "--print-after-all", 77 | "--mlir-to-llvmir", 78 | "--verify-diagnostics", 79 | ) 80 | 81 | 82 | class MLIRCompiler: 83 | 84 | def __init__(self, debug=False): 85 | self._debug = debug 86 | 87 | def to_llvm_dialect(self, mlir_src: str, target: Target = Target.OPENMP) -> str: 88 | """ 89 | Convert MLIR to LLVM dialect. 90 | 91 | Args: 92 | mlir_src: The MLIR source code 93 | target: Target compilation mode (openmp or basic_loops) 94 | """ 95 | if self._debug: 96 | print(mlir_src) 97 | binary = (MLIR_BIN,) 98 | dbg_cmd = DEBUG_OPTIONS if self._debug else () 99 | 100 | # Choose compilation path based on target 101 | target_options = ( 102 | OPENMP_OPTIONS if target == Target.OPENMP else BASIC_LOOPS_OPTIONS 103 | ) 104 | shell_cmd = binary + dbg_cmd + COMMON_INITIAL_OPTIONS + target_options 105 | return self._run_shell(shell_cmd, "t", "t", mlir_src) 106 | 107 | def mlir_translate_to_llvm_ir(self, mlir_src): 108 | if self._debug: 109 | print(mlir_src) 110 | binary = (MLIR_TRANSLATE_BIN,) 111 | shell_cmd = binary + MLIR_TRANSLATE_OPTIONS 112 | return self._run_shell(shell_cmd, "t", "t", mlir_src) 113 | 114 | def llvm_ir_to_bitcode(self, llvmir_src): 115 | if self._debug: 116 | print(llvmir_src) 117 | binary = ("llvm-as",) 118 | shell_cmd = binary 119 | return self._run_shell(shell_cmd, "t", "b", llvmir_src) 120 | 121 | def _run_shell(self, cmd, in_mode, out_mode, src): 122 | assert in_mode in "tb" 123 | assert out_mode in "tb" 124 | 125 | with ( 126 | NamedTemporaryFile(mode=f"w{in_mode}") as src_file, 127 | NamedTemporaryFile(mode=f"r{out_mode}") as out_file, 128 | ): 129 | src_file.write(src) 130 | src_file.flush() 131 | 132 | shell_cmd = *cmd, src_file.name, "-o", out_file.name 133 | if self._debug: 134 | print(shell_cmd) 135 | subprocess.run(shell_cmd) 136 | out_file.flush() 137 | return out_file.read() 138 | -------------------------------------------------------------------------------- /src/mlir_egglog/mlir_gen.py: -------------------------------------------------------------------------------- 1 | from textwrap import indent 2 | from typing import Callable 3 | import llvmlite.binding as llvm 4 | from mlir_egglog import expr_model as ir 5 | from mlir_egglog.llvm_runtime import init_llvm 6 | 7 | KERNEL_NAME = "kernel_worker" 8 | 9 | 10 | def get_target_info(): 11 | init_llvm() 12 | 13 | # Get the default triple for the current system 14 | triple = llvm.get_default_triple() 15 | 16 | # Create target and target machine to get the data layout 17 | target = llvm.Target.from_triple(triple) 18 | target_machine = target.create_target_machine() 19 | layout = str(target_machine.target_data) 20 | 21 | return triple, layout 22 | 23 | 24 | # Module wrapper with LLVM target information 25 | def get_module_prologue(): 26 | """Generate module prologue with target triple and data layout from runtime system.""" 27 | triple, layout = get_target_info() 28 | return f"""module attributes {{llvm.data_layout = "{layout}", 29 | llvm.target_triple = "{triple}"}} {{ 30 | """ 31 | 32 | 33 | module_epilogue = """ 34 | } 35 | """ 36 | 37 | # Numpy vectorized kernel that supports N-dimensional arrays 38 | kernel_prologue = f""" 39 | func.func @{KERNEL_NAME}( 40 | %arg0: memref, 41 | %arg1: memref 42 | ) attributes {{llvm.emit_c_interface}} {{ 43 | %c0 = index.constant 0 44 | 45 | // Get dimension of input array 46 | %dim = memref.dim %arg0, %c0 : memref 47 | 48 | // Process each element in a flattened manner 49 | affine.for %idx = %c0 to %dim {{ 50 | """ 51 | 52 | kernel_epilogue = """ 53 | } 54 | return 55 | } 56 | """ 57 | 58 | 59 | class MLIRGen: 60 | """ 61 | Generate textual MLIR from a symbolic expression. 62 | """ 63 | 64 | root: ir.Expr 65 | cache: dict[ir.Expr, str] 66 | subexprs: dict[str, str] 67 | vars: list[str] # local variables 68 | temp_counter: int # Counter for generating unique variable names 69 | 70 | def __init__(self, root: ir.Expr, argmap: dict[str, str]): 71 | # Use the keys from argmap as the variable names 72 | self.root = root 73 | self.cache = {} 74 | self.vars = list(argmap.keys()) 75 | self.subexprs = {} 76 | self.temp_counter = 0 77 | 78 | def generate(self): 79 | """ 80 | Generate MLIR code for the root expression. 81 | """ 82 | subexprs = list(self.unfold(self.root)) 83 | subexprs.sort(key=lambda x: len(str(x))) 84 | 85 | buf = [] 86 | # First load input arguments from memref 87 | for var in self.vars: 88 | buf.append(f"%arg_{var} = affine.load %arg0[%idx] : memref") 89 | 90 | for i, subex in enumerate(subexprs): 91 | # Skip if this is just a variable reference 92 | if isinstance(subex, ir.Symbol) and subex.name in self.vars: 93 | continue 94 | 95 | # Handle maximums separately 96 | if isinstance(subex, ir.Maximum): 97 | self._handle_maximum(subex, buf) 98 | continue 99 | 100 | # Recurse and cache the subexpression 101 | self.walk(subex) 102 | orig = self.cache[subex] 103 | 104 | # Generate a unique name for the subexpression 105 | k = f"%v{i}" 106 | self.cache[subex] = k 107 | self.subexprs[k] = orig 108 | 109 | # Append the subexpression to the buffer 110 | buf.append(f"{k} = {orig}") 111 | 112 | self.walk(self.root) 113 | res = self.cache[self.root] 114 | 115 | # Handle the output 116 | buf.append(f"affine.store {res}, %arg1[%idx] : memref") 117 | 118 | # Format the kernel body 119 | kernel_body = indent("\n".join(buf), " " * 2) 120 | kernel_code = kernel_prologue + kernel_body + kernel_epilogue 121 | 122 | # Wrap kernel in module with target information 123 | return get_module_prologue() + indent(kernel_code, " ") + module_epilogue 124 | 125 | def _handle_maximum(self, expr: ir.Maximum, buf: list[str]): 126 | """ 127 | Special handler for Maximum operations. 128 | This creates a comparison and selection sequence that's compatible with all architectures. 129 | """ 130 | # Process the operands first 131 | if expr.lhs not in self.cache: 132 | if isinstance(expr.lhs, ir.Maximum): 133 | self._handle_maximum(expr.lhs, buf) 134 | else: 135 | self.walk(expr.lhs) 136 | 137 | if expr.rhs not in self.cache: 138 | if isinstance(expr.rhs, ir.Maximum): 139 | self._handle_maximum(expr.rhs, buf) 140 | else: 141 | self.walk(expr.rhs) 142 | 143 | # Get the operand values 144 | lhs_val = self.cache[expr.lhs] 145 | rhs_val = self.cache[expr.rhs] 146 | 147 | # Create unique variable names for this maximum operation 148 | self.temp_counter += 1 149 | cmp_var = f"%cmp_{self.temp_counter}" 150 | res_var = f"%max_{self.temp_counter}" 151 | 152 | # Add the comparison operation 153 | buf.append(f"{cmp_var} = arith.cmpf ogt, {lhs_val}, {rhs_val} : f32") 154 | 155 | # Add the select operation 156 | buf.append(f"{res_var} = arith.select {cmp_var}, {lhs_val}, {rhs_val} : f32") 157 | 158 | # Cache the result for future use 159 | self.cache[expr] = res_var 160 | 161 | def unfold(self, expr: ir.Expr): 162 | """ 163 | Unfold an expression into a set of subexpressions. 164 | """ 165 | visited = set() 166 | all_subexprs = set() 167 | to_visit = [expr] 168 | while to_visit: 169 | current = to_visit.pop() 170 | all_subexprs.add(current) 171 | if current in visited: 172 | continue 173 | visited.add(current) 174 | to_visit.extend(get_children(current)) 175 | 176 | return all_subexprs 177 | 178 | def walk(self, expr: ir.Expr): 179 | """ 180 | Walk an expression recursively and generate MLIR code for subexpressions, 181 | caching the intermediate expressions in a lookup table. 182 | """ 183 | if expr in self.cache: 184 | return 185 | 186 | def lookup(e): 187 | return self.cache.get(e) or as_source(e, self.vars, lookup) 188 | 189 | self.cache[expr] = as_source(expr, self.vars, lookup) 190 | 191 | 192 | def get_children(expr: ir.Expr): 193 | """Get child expressions for an AST node.""" 194 | match expr: 195 | case ir.BinaryOp(): 196 | return {expr.lhs, expr.rhs} 197 | case ir.UnaryOp(): 198 | return {expr.operand} 199 | case ir.FloatLiteral() | ir.IntLiteral() | ir.Symbol(): 200 | return set() 201 | case _: 202 | raise NotImplementedError(f"Unsupported expression type: {type(expr)}") 203 | 204 | 205 | def as_source( 206 | expr: ir.Expr, vars: list[str], lookup_fn: Callable[[ir.Expr], str] 207 | ) -> str: 208 | """ 209 | Convert expressions to MLIR source code using arith and math dialects. 210 | """ 211 | match expr: 212 | # Literals and Symbols 213 | case ir.FloatLiteral(fval=val): 214 | return f"arith.constant {val:e} : f32" 215 | case ir.IntLiteral(ival=val): 216 | return f"arith.constant {val} : i32" 217 | case ir.Symbol(name=name) if name in vars: 218 | return f"%arg_{name}" 219 | case ir.Symbol(name=name): 220 | return f"%{name}" 221 | 222 | # Binary Operations 223 | case ir.Add(lhs=lhs, rhs=rhs): 224 | return f"arith.addf {lookup_fn(lhs)}, {lookup_fn(rhs)} : f32" 225 | case ir.Mul(lhs=lhs, rhs=rhs): 226 | return f"arith.mulf {lookup_fn(lhs)}, {lookup_fn(rhs)} : f32" 227 | case ir.Div(lhs=lhs, rhs=rhs): 228 | return f"arith.divf {lookup_fn(lhs)}, {lookup_fn(rhs)} : f32" 229 | case ir.Maximum(lhs=lhs, rhs=rhs): 230 | # Maximum is handled via _handle_maximum in the MLIRGen class 231 | # This case should not be triggered during normal operation 232 | return "ERROR_MAXIMUM_HANDLED_SEPARATELY" 233 | 234 | # Unary Math Operations 235 | case ( 236 | ir.Sin() 237 | | ir.Cos() 238 | | ir.Log() 239 | | ir.Sqrt() 240 | | ir.Exp() 241 | | ir.Sinh() 242 | | ir.Cosh() 243 | | ir.Tanh() 244 | ) as op: 245 | op_name = type(op).__name__.lower() 246 | return f"math.{op_name} {lookup_fn(op.operand)} : f32" 247 | case ir.Neg(operand=op): 248 | return f"arith.negf {lookup_fn(op)} : f32" 249 | 250 | # Type Casting 251 | case ir.CastF32(operand=op): 252 | return f"arith.sitofp {lookup_fn(op)} : i64 to f32" 253 | case ir.CastI64(operand=op): 254 | return f"arith.fptosi {lookup_fn(op)} : f32 to i64" 255 | 256 | case _: 257 | raise NotImplementedError(f"Unsupported expression type: {type(expr)}") 258 | -------------------------------------------------------------------------------- /src/mlir_egglog/python_to_ir.py: -------------------------------------------------------------------------------- 1 | import types 2 | import inspect 3 | 4 | from mlir_egglog import expr_model as ir 5 | 6 | 7 | def interpret(fn: types.FunctionType, globals: dict[str, object]): 8 | """ 9 | Symbolically interpret a python function. 10 | """ 11 | # Get the function's signature 12 | sig = inspect.signature(fn) 13 | 14 | # Create symbolic parameters for each of the function's arguments 15 | params = [n for n in sig.parameters] 16 | symbolic_params = [ir.Symbol(name=n) for n in params] 17 | 18 | # Bind the symbolic parameters to the function's arguments 19 | ba = sig.bind(*symbolic_params) 20 | 21 | # Inject our globals (i.e. np) into the function's globals 22 | custom_globals = fn.__globals__.copy() 23 | custom_globals.update(globals) 24 | 25 | # Create a temporary function with our custom globals 26 | tfn = types.FunctionType( 27 | fn.__code__, 28 | custom_globals, 29 | fn.__name__, 30 | fn.__defaults__, 31 | fn.__closure__, 32 | ) 33 | return tfn(*ba.args, **ba.kwargs) 34 | -------------------------------------------------------------------------------- /src/mlir_egglog/term_ir.py: -------------------------------------------------------------------------------- 1 | # mypy: disable-error-code=empty-body 2 | 3 | from __future__ import annotations 4 | 5 | import egglog 6 | from egglog import StringLike, i64, f64, i64Like, f64Like # noqa: F401 7 | from egglog import RewriteOrRule, rewrite 8 | from typing import Generator 9 | from mlir_egglog.expr_model import Expr, FloatLiteral, Symbol, IntLiteral 10 | from abc import abstractmethod 11 | 12 | # Operation costs based on LLVM instruction complexity 13 | # Basic arithmetic (single CPU instruction) 14 | COST_BASIC_ARITH = 1 15 | 16 | # Type conversion operations 17 | COST_CAST = 2 18 | 19 | # More expensive arithmetic operations 20 | COST_DIV = 5 21 | COST_POW_INTEGER = 10 22 | 23 | # Hardware-assisted transcendental operations 24 | COST_SQRT = 20 25 | COST_LOG2 = 25 26 | COST_LOG = 30 27 | COST_LOG10 = 35 28 | COST_EXP = 40 29 | 30 | # General power operation (requires exp and log) 31 | COST_POW = 50 32 | 33 | # Trigonometric functions (Taylor series implementations) 34 | COST_TRIG_BASIC = 75 # sin, cos 35 | COST_TRIG_TAN = 80 36 | COST_TRIG_ATAN = 85 37 | COST_TRIG_ASINCOS = 90 38 | 39 | # Hyperbolic functions (complex Taylor series) 40 | COST_HYPERBOLIC = 180 41 | COST_TANH = 200 42 | 43 | 44 | class Term(egglog.Expr): 45 | """ 46 | Intermediate representation for the egraph. 47 | """ 48 | 49 | @classmethod 50 | @abstractmethod 51 | def var(self, k: StringLike) -> Term: ... 52 | 53 | @classmethod 54 | @abstractmethod 55 | def lit_f64(self, v: f64Like) -> Term: ... 56 | 57 | @classmethod 58 | @abstractmethod 59 | def lit_f32(self, v: f64Like | i64) -> Term: ... 60 | 61 | @classmethod 62 | @abstractmethod 63 | def lit_i64(self, v: i64Like) -> Term: ... 64 | 65 | @abstractmethod 66 | def __add__(self, other: Term) -> Term: ... 67 | 68 | @abstractmethod 69 | def __mul__(self, other: Term) -> Term: ... 70 | 71 | @abstractmethod 72 | def __neg__(self) -> Term: ... 73 | 74 | @abstractmethod 75 | def __sub__(self, other: Term) -> Term: ... 76 | 77 | @abstractmethod 78 | def __truediv__(self, other: Term) -> Term: ... 79 | 80 | @abstractmethod 81 | def __pow__(self, other: Term) -> Term: ... 82 | 83 | 84 | # Binary Operations 85 | @egglog.function(cost=COST_BASIC_ARITH) 86 | def Add(x: Term, y: Term) -> Term: ... 87 | 88 | 89 | @egglog.function(cost=COST_BASIC_ARITH) 90 | def Mul(x: Term, y: Term) -> Term: ... 91 | 92 | 93 | @egglog.function(cost=COST_DIV) 94 | def Div(x: Term, y: Term) -> Term: ... 95 | 96 | 97 | @egglog.function(cost=COST_POW) 98 | def Pow(x: Term, y: Term) -> Term: ... 99 | 100 | 101 | @egglog.function(cost=COST_POW_INTEGER) 102 | def PowConst(x: Term, i: i64Like) -> Term: ... 103 | 104 | 105 | # Unary Operations 106 | @egglog.function(cost=COST_TRIG_BASIC) 107 | def Sin(x: Term) -> Term: ... 108 | 109 | 110 | @egglog.function(cost=COST_TRIG_BASIC) 111 | def Cos(x: Term) -> Term: ... 112 | 113 | 114 | @egglog.function(cost=COST_TRIG_TAN) 115 | def Tan(x: Term) -> Term: ... 116 | 117 | 118 | @egglog.function(cost=COST_TRIG_ASINCOS) 119 | def ASin(x: Term) -> Term: ... 120 | 121 | 122 | @egglog.function(cost=COST_TRIG_ASINCOS) 123 | def ACos(x: Term) -> Term: ... 124 | 125 | 126 | @egglog.function(cost=COST_TRIG_ATAN) 127 | def ATan(x: Term) -> Term: ... 128 | 129 | 130 | @egglog.function(cost=COST_SQRT) 131 | def Sqrt(x: Term) -> Term: ... 132 | 133 | 134 | @egglog.function(cost=COST_TANH) 135 | def Tanh(x: Term) -> Term: ... 136 | 137 | 138 | @egglog.function(cost=COST_HYPERBOLIC) 139 | def Sinh(x: Term) -> Term: ... 140 | 141 | 142 | @egglog.function(cost=COST_HYPERBOLIC) 143 | def Cosh(x: Term) -> Term: ... 144 | 145 | 146 | @egglog.function(cost=COST_EXP) 147 | def Exp(x: Term) -> Term: ... 148 | 149 | 150 | @egglog.function(cost=COST_LOG) 151 | def Log(x: Term) -> Term: ... 152 | 153 | 154 | @egglog.function(cost=COST_LOG10) 155 | def Log10(x: Term) -> Term: ... 156 | 157 | 158 | @egglog.function(cost=COST_LOG2) 159 | def Log2(x: Term) -> Term: ... 160 | 161 | 162 | @egglog.function(cost=COST_CAST) 163 | def CastF32(x: Term) -> Term: ... 164 | 165 | 166 | @egglog.function(cost=COST_CAST) 167 | def CastI64(x: Term) -> Term: ... 168 | 169 | 170 | @egglog.function(cost=COST_BASIC_ARITH) 171 | def Maximum(x: Term, y: Term) -> Term: ... 172 | 173 | 174 | @egglog.function(cost=COST_BASIC_ARITH) 175 | def Neg(x: Term) -> Term: ... 176 | 177 | 178 | def as_egraph(expr: Expr) -> Term: 179 | """ 180 | Convert a syntax tree expression to an egraph term. 181 | """ 182 | from mlir_egglog import expr_model 183 | 184 | match expr: 185 | # Literals and Symbols 186 | case FloatLiteral(fval=val): 187 | return Term.lit_f32(val) 188 | case IntLiteral(ival=val): 189 | return Term.lit_i64(int(val)) 190 | case Symbol(name=name): 191 | return Term.var(name) 192 | 193 | # Binary Operations 194 | case expr_model.Add(lhs=lhs, rhs=rhs): 195 | return Add(as_egraph(lhs), as_egraph(rhs)) 196 | case expr_model.Mul(lhs=lhs, rhs=rhs): 197 | return Mul(as_egraph(lhs), as_egraph(rhs)) 198 | case expr_model.Div(lhs=lhs, rhs=rhs): 199 | return Div(as_egraph(lhs), as_egraph(rhs)) 200 | case expr_model.Pow(lhs=lhs, rhs=rhs): 201 | return Pow(as_egraph(lhs), as_egraph(rhs)) 202 | case expr_model.Maximum(lhs=lhs, rhs=rhs): 203 | return Maximum(as_egraph(lhs), as_egraph(rhs)) 204 | 205 | # Trigonometric Functions 206 | case expr_model.Sin(operand=op): 207 | return Sin(as_egraph(op)) 208 | case expr_model.Cos(operand=op): 209 | return Cos(as_egraph(op)) 210 | case expr_model.Tan(operand=op): 211 | return Tan(as_egraph(op)) 212 | case expr_model.ASin(operand=op): 213 | return ASin(as_egraph(op)) 214 | case expr_model.ACos(operand=op): 215 | return ACos(as_egraph(op)) 216 | case expr_model.ATan(operand=op): 217 | return ATan(as_egraph(op)) 218 | 219 | # Hyperbolic Functions 220 | case expr_model.Tanh(operand=op): 221 | return Tanh(as_egraph(op)) 222 | case expr_model.Sinh(operand=op): 223 | return Sinh(as_egraph(op)) 224 | case expr_model.Cosh(operand=op): 225 | return Cosh(as_egraph(op)) 226 | 227 | # Exponential and Logarithmic Functions 228 | case expr_model.Exp(operand=op): 229 | return Exp(as_egraph(op)) 230 | case expr_model.Log(operand=op): 231 | return Log(as_egraph(op)) 232 | case expr_model.Log10(operand=op): 233 | return Log10(as_egraph(op)) 234 | case expr_model.Log2(operand=op): 235 | return Log2(as_egraph(op)) 236 | 237 | # Type Casting and Other Operations 238 | case expr_model.CastF32(operand=op): 239 | return CastF32(as_egraph(op)) 240 | case expr_model.CastI64(operand=op): 241 | return CastI64(as_egraph(op)) 242 | case expr_model.Neg(operand=op): 243 | return Neg(as_egraph(op)) 244 | case expr_model.Sqrt(operand=op): 245 | return Sqrt(as_egraph(op)) 246 | 247 | case _: 248 | raise NotImplementedError(f"Unsupported expression type: {type(expr)}") 249 | 250 | 251 | def birewrite_subsume(a: Term, b: Term) -> Generator[RewriteOrRule, None, None]: 252 | yield rewrite(a, subsume=True).to(b) 253 | yield rewrite(b).to(a) 254 | -------------------------------------------------------------------------------- /src/mlir_egglog/trig_simplify.py: -------------------------------------------------------------------------------- 1 | from mlir_egglog.term_ir import Sin, Cos, Sinh, Cosh, Tanh, Term, Pow, Add 2 | from egglog import ruleset, i64, f64 3 | from egglog import rewrite 4 | 5 | 6 | @ruleset 7 | def trig_simplify(x: Term, y: Term, z: Term, i: i64, fval: f64): 8 | # Fundamental trig identities 9 | # sin²(x) + cos²(x) = 1 10 | two = Term.lit_i64(2) 11 | yield rewrite(Add(Pow(Sin(x), two), Pow(Cos(x), two))).to(Term.lit_f32(1.0)) 12 | 13 | # Double angle formulas 14 | yield rewrite(Sin(x + y)).to(Sin(x) * Cos(y) + Cos(x) * Sin(y)) 15 | yield rewrite(Sin(x - y)).to(Sin(x) * Cos(y) - Cos(x) * Sin(y)) 16 | yield rewrite(Cos(x + y)).to(Cos(x) * Cos(y) - Sin(x) * Sin(y)) 17 | yield rewrite(Cos(x - y)).to(Cos(x) * Cos(y) + Sin(x) * Sin(y)) 18 | 19 | # Hyperbolic identities 20 | yield rewrite(Sinh(x) * Cosh(y) + Cosh(y) * Sinh(x)).to(Sinh(x + y)) 21 | yield rewrite(Cosh(x) * Cosh(y) + Sinh(x) * Sinh(y)).to(Cosh(x + y)) 22 | yield rewrite((Tanh(x) + Tanh(y)) / (Term.lit_i64(1) + Tanh(x) * Tanh(y))).to( 23 | Tanh(x + y) 24 | ) 25 | -------------------------------------------------------------------------------- /tests/test_basic_expressions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import numpy as np 3 | from mlir_egglog.egglog_optimizer import compile 4 | from mlir_egglog.jit_engine import JITEngine 5 | from egglog import rewrite, ruleset, RewriteOrRule, i64, f64 6 | from mlir_egglog.term_ir import Term, Add 7 | from mlir_egglog.basic_simplify import basic_math 8 | from typing import Generator 9 | 10 | 11 | class TestBasicExpressions(unittest.TestCase): 12 | def test_arithmetic_expression(self): 13 | def arithmetic_fn(x): 14 | return x * 2.0 + 1.0 15 | 16 | # Test frontend compilation (MLIR generation) 17 | mlir_code = compile(arithmetic_fn, debug=True) 18 | self.assertIn("arith.mulf", mlir_code) 19 | self.assertIn("arith.addf", mlir_code) 20 | 21 | # Test full pipeline compilation 22 | jit = JITEngine() 23 | try: 24 | func_addr = jit.jit_compile(arithmetic_fn) 25 | self.assertIsNotNone(func_addr) 26 | self.assertGreater(func_addr, 0) 27 | except Exception as e: 28 | self.fail(f"Full pipeline compilation failed: {str(e)}") 29 | 30 | def test_trigonometric_expression(self): 31 | def trig_fn(x): 32 | return np.sin(x) * np.cos(x) 33 | 34 | # Test frontend compilation (MLIR generation) 35 | mlir_code = compile(trig_fn, debug=True) 36 | self.assertIn("math.sin", mlir_code) 37 | self.assertIn("math.cos", mlir_code) 38 | 39 | # Test full pipeline compilation 40 | jit = JITEngine() 41 | try: 42 | func_addr = jit.jit_compile(trig_fn) 43 | self.assertIsNotNone(func_addr) 44 | self.assertGreater(func_addr, 0) 45 | except Exception as e: 46 | self.fail(f"Full pipeline compilation failed: {str(e)}") 47 | 48 | def test_exponential_expression(self): 49 | def exp_fn(x): 50 | return np.exp(x) + np.log(x) 51 | 52 | # Test frontend compilation (MLIR generation) 53 | mlir_code = compile(exp_fn, debug=True) 54 | self.assertIn("math.exp", mlir_code) 55 | self.assertIn("math.log", mlir_code) 56 | 57 | # Test full pipeline compilation 58 | jit = JITEngine() 59 | try: 60 | func_addr = jit.jit_compile(exp_fn) 61 | self.assertIsNotNone(func_addr) 62 | self.assertGreater(func_addr, 0) 63 | except Exception as e: 64 | self.fail(f"Full pipeline compilation failed: {str(e)}") 65 | 66 | def test_type_casting(self): 67 | def cast_fn(x): 68 | # Cast to int64 and back to float32 69 | return np.float32(np.int64(x)) 70 | 71 | # Test frontend compilation (MLIR generation) 72 | mlir_code = compile(cast_fn, debug=True) 73 | self.assertIn("arith.fptosi", mlir_code) # float to int 74 | self.assertIn("arith.sitofp", mlir_code) # int to float 75 | 76 | # Test full pipeline compilation 77 | jit = JITEngine() 78 | try: 79 | func_addr = jit.jit_compile(cast_fn) 80 | self.assertIsNotNone(func_addr) 81 | self.assertGreater(func_addr, 0) 82 | except Exception as e: 83 | self.fail(f"Full pipeline compilation failed: {str(e)}") 84 | 85 | def test_constants_and_sqrt(self): 86 | def const_fn(x): 87 | return np.sqrt(x) + np.pi 88 | 89 | # Test frontend compilation (MLIR generation) 90 | mlir_code = compile(const_fn, debug=True) 91 | print(mlir_code) 92 | self.assertIn("math.sqrt", mlir_code) 93 | self.assertIn("arith.constant", mlir_code) 94 | 95 | # Test full pipeline compilation 96 | jit = JITEngine() 97 | try: 98 | func_addr = jit.jit_compile(const_fn) 99 | self.assertIsNotNone(func_addr) 100 | self.assertGreater(func_addr, 0) 101 | except Exception as e: 102 | self.fail(f"Full pipeline compilation failed: {str(e)}") 103 | 104 | def test_full_compilation_pipeline(self): 105 | def simple_fn(x): 106 | return x * 2.0 + 1.0 107 | 108 | # Create JIT engine instance 109 | jit = JITEngine() 110 | 111 | # Test frontend compilation (MLIR generation) 112 | mlir_code = jit.run_frontend(simple_fn) 113 | self.assertIn("arith.mulf", mlir_code) 114 | self.assertIn("arith.addf", mlir_code) 115 | 116 | # Test backend compilation (MLIR to machine code) 117 | try: 118 | func_addr = jit.run_backend(mlir_code) 119 | self.assertIsNotNone(func_addr) 120 | self.assertGreater(func_addr, 0) 121 | except Exception as e: 122 | self.fail(f"Backend compilation failed: {str(e)}") 123 | 124 | def test_relu_function(self): 125 | def relu_fn(x): 126 | # ReLU(x) = max(0, x) 127 | return np.maximum(x, 0.0) 128 | 129 | # Test frontend compilation (MLIR generation) 130 | mlir_code = compile(relu_fn, debug=True) 131 | print("Generated MLIR:") 132 | print(mlir_code) 133 | # Check for the comparison and select operations used to implement maximum 134 | self.assertIn("arith.cmpf", mlir_code) # check for comparison 135 | self.assertIn("arith.select", mlir_code) # check for select 136 | 137 | # Test full pipeline compilation 138 | jit = JITEngine() 139 | try: 140 | func_addr = jit.jit_compile(relu_fn) 141 | self.assertIsNotNone(func_addr) 142 | self.assertGreater(func_addr, 0) 143 | except Exception as e: 144 | self.fail(f"Full pipeline compilation failed: {str(e)}") 145 | 146 | def test_sigmoid_function(self): 147 | def sigmoid_fn(x): 148 | # Sigmoid(x) = 1/(1 + e^(-x)) 149 | return 1.0 / (1.0 + np.exp(-x)) 150 | 151 | # Test frontend compilation (MLIR generation) 152 | mlir_code = compile(sigmoid_fn, debug=True) 153 | print("Generated MLIR:") 154 | print(mlir_code) 155 | self.assertIn("math.exp", mlir_code) # check for exponential 156 | self.assertIn("arith.divf", mlir_code) # check for division 157 | self.assertIn("arith.negf", mlir_code) # check for negation 158 | 159 | # Test full pipeline compilation 160 | jit = JITEngine() 161 | try: 162 | func_addr = jit.jit_compile(sigmoid_fn) 163 | self.assertIsNotNone(func_addr) 164 | self.assertGreater(func_addr, 0) 165 | except Exception as e: 166 | self.fail(f"Full pipeline compilation failed: {str(e)}") 167 | 168 | def test_custom_rewrites_in_compile(self): 169 | @ruleset 170 | def float_rules( 171 | x: Term, y: Term, z: Term, i: i64, f: f64 172 | ) -> Generator[RewriteOrRule, None, None]: 173 | # x + 0.0 = x (float case) 174 | yield rewrite(Add(x, Term.lit_f32(0.0))).to(x) 175 | # 0.0 + x = x (float case) 176 | yield rewrite(Add(Term.lit_f32(0.0), x)).to(x) 177 | 178 | def custom_fn(x): 179 | return x + 0.0 180 | 181 | # Test frontend compilation (MLIR generation) with custom rewrites 182 | mlir_code = compile(custom_fn, rewrites=(basic_math, float_rules), debug=True) 183 | self.assertNotIn( 184 | "arith.addf", mlir_code 185 | ) # The addition should be optimized away 186 | 187 | # Test full pipeline compilation 188 | jit = JITEngine() 189 | try: 190 | func_addr = jit.jit_compile(custom_fn) 191 | self.assertIsNotNone(func_addr) 192 | self.assertGreater(func_addr, 0) 193 | except Exception as e: 194 | self.fail(f"Full pipeline compilation failed: {str(e)}") 195 | -------------------------------------------------------------------------------- /tests/test_dispatch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mlir_egglog import kernel 3 | from mlir_egglog.egglog_optimizer import compile 4 | from mlir_egglog.jit_engine import JITEngine 5 | from mlir_egglog.basic_simplify import basic_math 6 | from egglog import rewrite, ruleset, RewriteOrRule, i64, f64 7 | from mlir_egglog.term_ir import Term, Add 8 | from typing import Generator 9 | 10 | 11 | def test_sin2_plus_cos2(): 12 | """Test that sin²(x) + cos²(x) simplifies to 1 and executes correctly.""" 13 | 14 | # First define the raw function for compilation 15 | def raw_fn(a): 16 | return np.sin(a) ** 2 + np.cos(a) ** 2 17 | 18 | # Test that it compiles to MLIR and simplifies to 1 19 | mlir_code = compile(raw_fn, debug=True) 20 | # The simplified version should just be a constant 1 21 | assert "arith.constant 1.0" in mlir_code 22 | assert "math.sin" not in mlir_code # Should not contain sin after optimization 23 | assert "math.cos" not in mlir_code # Should not contain cos after optimization 24 | 25 | # Now create the kernelized version for runtime testing 26 | @kernel("float32(float32)") 27 | def fn(a): 28 | return np.sin(a) ** 2 + np.cos(a) ** 2 29 | 30 | # Test JIT compilation and runtime execution 31 | jit = JITEngine() 32 | 33 | # Compile the function 34 | func_addr = jit.jit_compile(fn.py_func) # Use the underlying Python function 35 | assert func_addr is not None 36 | assert func_addr > 0 37 | 38 | # Test with some input values 39 | test_input = np.array([0.0, np.pi / 4, np.pi / 2, np.pi], dtype=np.float32) 40 | result = fn(test_input) 41 | 42 | # All results should be very close to 1.0 43 | np.testing.assert_allclose(result, np.ones_like(test_input), rtol=1e-6) 44 | 45 | 46 | def test_sigmoid(): 47 | """Test that our kernelized sigmoid matches NumPy's implementation.""" 48 | 49 | def numpy_sigmoid(x): 50 | return 1.0 / (1.0 + np.exp(-x)) 51 | 52 | # Define our kernelized sigmoid 53 | @kernel("float32(float32)") 54 | def sigmoid(x): 55 | return 1.0 / (1.0 + np.exp(-x)) 56 | 57 | # Create test input 58 | x = np.linspace(-5, 5, 100, dtype=np.float32) 59 | 60 | # Get results from both implementations 61 | expected = numpy_sigmoid(x) 62 | result = sigmoid(x) 63 | 64 | # Compare results 65 | np.testing.assert_allclose(result, expected, rtol=1e-6) 66 | 67 | # Test with a 1D array 68 | x_1d = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0], dtype=np.float32) 69 | expected_1d = numpy_sigmoid(x_1d) 70 | result_1d = sigmoid(x_1d) 71 | 72 | # Compare 1D results 73 | np.testing.assert_allclose(result_1d, expected_1d, rtol=1e-6) 74 | 75 | # Test with a 2D array 76 | x_2d = np.array([[-2.0, -1.0, 0.0], [1.0, 2.0, 3.0]], dtype=np.float32) 77 | expected_2d = numpy_sigmoid(x_2d) 78 | result_2d = sigmoid(x_2d) 79 | 80 | # Compare 2D results 81 | np.testing.assert_allclose(result_2d, expected_2d, rtol=1e-6) 82 | 83 | # Test some known values 84 | special_cases = np.array([0.0], dtype=np.float32) # sigmoid(0) = 0.5 85 | assert np.abs(sigmoid(special_cases)[0] - 0.5) < 1e-6 86 | 87 | 88 | def test_multidimensional_arrays(): 89 | """Test that our kernelized functions work with multidimensional arrays.""" 90 | 91 | # Define a simple function that adds 1 to each element 92 | @kernel("float32(float32)") 93 | def add_one(x): 94 | return x + 1.0 95 | 96 | # Test with a 2D array 97 | x_2d = np.ones((3, 4), dtype=np.float32) 98 | expected_2d = x_2d + 1.0 99 | result_2d = add_one(x_2d) 100 | np.testing.assert_allclose(result_2d, expected_2d, rtol=1e-6) 101 | 102 | # Test with a 3D array 103 | x_3d = np.ones((2, 3, 2), dtype=np.float32) 104 | expected_3d = x_3d + 1.0 105 | result_3d = add_one(x_3d) 106 | np.testing.assert_allclose(result_3d, expected_3d, rtol=1e-6) 107 | 108 | # Test with a more complex function 109 | @kernel("float32(float32)") 110 | def complex_fn(x): 111 | return np.sin(x) * np.cos(x) + np.sqrt(np.abs(x)) 112 | 113 | # Test with a 2D array 114 | test_2d = np.array([[-1.0, 0.0, 1.0], [2.0, 3.0, 4.0]], dtype=np.float32) 115 | expected_complex = np.sin(test_2d) * np.cos(test_2d) + np.sqrt(np.abs(test_2d)) 116 | result_complex = complex_fn(test_2d) 117 | np.testing.assert_allclose(result_complex, expected_complex, rtol=1e-5) 118 | 119 | 120 | def test_custom_rewrites(): 121 | """Test that custom rewrite rules can be passed to the kernel decorator.""" 122 | 123 | # Define a custom rewrite rule 124 | @ruleset 125 | def float_rules( 126 | x: Term, y: Term, z: Term, i: i64, f: f64 127 | ) -> Generator[RewriteOrRule, None, None]: 128 | # x + 0.0 = x (float case) 129 | yield rewrite(Add(x, Term.lit_f32(0.0))).to(x) 130 | # 0.0 + x = x (float case) 131 | yield rewrite(Add(Term.lit_f32(0.0), x)).to(x) 132 | 133 | # Define a function that uses the custom rewrite rule 134 | @kernel("float32(float32)", rewrites=(basic_math, float_rules)) 135 | def custom_fn(x): 136 | return x + 0.0 137 | 138 | # Test that the custom rewrite rule is applied 139 | mlir_code = compile( 140 | custom_fn.py_func, rewrites=(basic_math, float_rules), debug=True 141 | ) 142 | 143 | # Test that the custom rewrite rule can be composed with other rules 144 | mlir_code = compile( 145 | custom_fn.py_func, rewrites=(basic_math | float_rules,), debug=True 146 | ) 147 | assert "arith.addf" not in mlir_code # The addition should be optimized away 148 | 149 | # Test JIT compilation and runtime execution 150 | jit = JITEngine() 151 | func_addr = jit.jit_compile(custom_fn.py_func) 152 | assert func_addr is not None 153 | assert func_addr > 0 154 | 155 | # Test with some input values 156 | test_input = np.array([1.0, 2.0, 3.0], dtype=np.float32) 157 | result = custom_fn(test_input) 158 | expected = test_input # The addition should be optimized away 159 | np.testing.assert_allclose(result, expected, rtol=1e-6) 160 | -------------------------------------------------------------------------------- /tests/test_simplify.py: -------------------------------------------------------------------------------- 1 | from mlir_egglog.term_ir import ( 2 | Term, 3 | COST_BASIC_ARITH, 4 | COST_POW, 5 | COST_TRIG_BASIC, 6 | COST_EXP, 7 | Add as TermAdd, 8 | Mul as TermMul, 9 | Pow as TermPow, 10 | Sin as TermSin, 11 | Cos as TermCos, 12 | Exp as TermExp, 13 | ) 14 | import egglog 15 | from egglog import EGraph, ruleset, rewrite 16 | from typing import Generator 17 | 18 | 19 | @ruleset 20 | def cost_based_rules(x: Term) -> Generator[egglog.RewriteOrRule, None, None]: 21 | # Rule 1: x^2 -> x * x (converts expensive power to cheaper multiplication) 22 | two = Term.lit_i64(2) 23 | yield rewrite(TermPow(x, two)).to(TermMul(x, x)) 24 | 25 | # Rule 2: x^1 -> x (eliminates unnecessary power) 26 | one = Term.lit_i64(1) 27 | yield rewrite(TermPow(x, one)).to(x) 28 | 29 | # Rule 3: cos(x)^2 + sin(x)^2 -> 1 (trigonometric identity) 30 | yield rewrite(TermAdd(TermPow(TermCos(x), two), TermPow(TermSin(x), two))).to( 31 | Term.lit_f64(1.0) 32 | ) 33 | 34 | 35 | @ruleset 36 | def exponential_rules(x: Term) -> Generator[egglog.RewriteOrRule, None, None]: 37 | # e^x * e^x -> e^(2*x) 38 | two = Term.lit_i64(2) 39 | yield rewrite(TermMul(TermExp(x), TermExp(x))).to(TermExp(TermMul(two, x))) 40 | 41 | # e^(2x) * e^x -> e^(3x) 42 | three = Term.lit_i64(3) 43 | yield rewrite(TermMul(TermExp(TermMul(two, x)), TermExp(x))).to( 44 | TermExp(TermMul(three, x)) 45 | ) 46 | 47 | 48 | def test_cost_based_optimization(): 49 | """Test that egglog can optimize expressions based on our cost model.""" 50 | 51 | # Create an egglog program 52 | egraph = EGraph() 53 | 54 | # Create test expressions 55 | x = Term.var("x") # Use Term.var directly 56 | 57 | # Test case 1: x^2 should be optimized to x * x 58 | expr1 = TermPow(x, Term.lit_i64(2)) # Use Term constructors directly 59 | cost_before1 = COST_POW 60 | cost_after1 = COST_BASIC_ARITH # multiplication 61 | 62 | # Test case 2: cos(x)^2 + sin(x)^2 should be optimized to 1 63 | expr2 = TermAdd( 64 | TermPow(TermCos(x), Term.lit_i64(2)), TermPow(TermSin(x), Term.lit_i64(2)) 65 | ) 66 | cost_before2 = ( 67 | 2 * COST_TRIG_BASIC # cos(x) and sin(x) 68 | + 2 * COST_POW # both terms squared 69 | + COST_BASIC_ARITH # final addition 70 | ) 71 | cost_after2 = 0 # constant 1.0 has no runtime cost 72 | 73 | # Add expressions to egraph 74 | egraph.let("expr1", expr1) 75 | egraph.let("expr2", expr2) 76 | 77 | # Add the rules manually 78 | egraph.register(rewrite(TermPow(x, Term.lit_i64(2))).to(TermMul(x, x))) 79 | egraph.register(rewrite(TermPow(x, Term.lit_i64(1))).to(x)) 80 | egraph.register( 81 | rewrite( 82 | TermAdd( 83 | TermPow(TermCos(x), Term.lit_i64(2)), 84 | TermPow(TermSin(x), Term.lit_i64(2)), 85 | ) 86 | ).to(Term.lit_f64(1.0)) 87 | ) 88 | 89 | # Run the optimizer 90 | egraph.run(10) # Run for a fixed number of iterations 91 | 92 | # Extract optimized expressions 93 | opt_expr1 = egraph.extract(expr1) 94 | opt_expr2 = egraph.extract(expr2) 95 | 96 | # Verify optimizations occurred 97 | assert str(opt_expr1) == str(TermMul(x, x)), "x^2 should be optimized to x * x" 98 | assert str(opt_expr2) == str( 99 | Term.lit_f64(1.0) 100 | ), "cos(x)^2 + sin(x)^2 should be optimized to 1" 101 | 102 | # Verify cost improvements 103 | assert cost_before1 > cost_after1, "Optimization should reduce cost of x^2" 104 | assert ( 105 | cost_before2 > cost_after2 106 | ), "Optimization should reduce cost of trig identity" 107 | 108 | 109 | def test_compound_expression_optimization(): 110 | """Test optimization of more complex expressions.""" 111 | 112 | egraph = EGraph() 113 | 114 | # Create test expression: e^x * e^x * e^x 115 | x = Term.var("x") # Use Term.var directly 116 | two = Term.lit_i64(2) 117 | three = Term.lit_i64(3) 118 | expr = TermMul( 119 | TermMul(TermExp(x), TermExp(x)), TermExp(x) 120 | ) # Use Term constructors directly 121 | 122 | # Calculate costs 123 | cost_before = ( 124 | 3 * COST_EXP # Three separate e^x calculations 125 | + 2 * COST_BASIC_ARITH # Two multiplications 126 | ) 127 | 128 | cost_after = ( 129 | COST_BASIC_ARITH + COST_EXP # Multiplication by 3 # Single e^(3x) calculation 130 | ) 131 | 132 | # Add expression to egraph 133 | egraph.let("expr", expr) 134 | 135 | # Add the rules manually 136 | egraph.register( 137 | rewrite(TermMul(TermExp(x), TermExp(x))).to(TermExp(TermMul(two, x))) 138 | ) 139 | egraph.register( 140 | rewrite(TermMul(TermExp(TermMul(two, x)), TermExp(x))).to( 141 | TermExp(TermMul(three, x)) 142 | ) 143 | ) 144 | 145 | # Run optimizer 146 | egraph.run(10) # Run for a fixed number of iterations 147 | 148 | # Extract optimized expression 149 | opt_expr = egraph.extract(expr) 150 | expected = TermExp(TermMul(three, x)) 151 | 152 | # Verify optimization occurred 153 | assert str(opt_expr) == str( 154 | expected 155 | ), "e^x * e^x * e^x should be optimized to e^(3x)" 156 | 157 | assert ( 158 | cost_before > cost_after 159 | ), "Optimization should reduce cost of compound exponential" 160 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "anywidget" 6 | version = "0.9.15" 7 | source = { registry = "https://pypi.org/simple" } 8 | dependencies = [ 9 | { name = "ipywidgets" }, 10 | { name = "psygnal" }, 11 | { name = "typing-extensions" }, 12 | ] 13 | sdist = { url = "https://files.pythonhosted.org/packages/78/85/9805db9a042b9090e37d78517290b8f8c3e89b50db324b365f54b8754eee/anywidget-0.9.15.tar.gz", hash = "sha256:1891c11897aaf7cff8809f996413f618f97d786b6097f7e46266423969a726a0", size = 9757760 } 14 | wheels = [ 15 | { url = "https://files.pythonhosted.org/packages/3a/cf/4f2797204895846d2a33a5f8e0cffcc3e23493c6daa10d500f9929da6644/anywidget-0.9.15-py3-none-any.whl", hash = "sha256:fd7876332e47f380e0428f552f26b7227f5694d4e0a257bbc23354d9b9e9a73c", size = 219054 }, 16 | ] 17 | 18 | [[package]] 19 | name = "asttokens" 20 | version = "3.0.0" 21 | source = { registry = "https://pypi.org/simple" } 22 | sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } 23 | wheels = [ 24 | { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, 25 | ] 26 | 27 | [[package]] 28 | name = "black" 29 | version = "25.1.0" 30 | source = { registry = "https://pypi.org/simple" } 31 | dependencies = [ 32 | { name = "click" }, 33 | { name = "mypy-extensions" }, 34 | { name = "packaging" }, 35 | { name = "pathspec" }, 36 | { name = "platformdirs" }, 37 | ] 38 | sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } 39 | wheels = [ 40 | { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, 41 | { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, 42 | { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, 43 | { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, 44 | { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, 45 | { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, 46 | { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, 47 | { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, 48 | { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, 49 | ] 50 | 51 | [[package]] 52 | name = "click" 53 | version = "8.1.8" 54 | source = { registry = "https://pypi.org/simple" } 55 | dependencies = [ 56 | { name = "colorama", marker = "platform_system == 'Windows'" }, 57 | ] 58 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } 59 | wheels = [ 60 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, 61 | ] 62 | 63 | [[package]] 64 | name = "colorama" 65 | version = "0.4.6" 66 | source = { registry = "https://pypi.org/simple" } 67 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 68 | wheels = [ 69 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 70 | ] 71 | 72 | [[package]] 73 | name = "comm" 74 | version = "0.2.2" 75 | source = { registry = "https://pypi.org/simple" } 76 | dependencies = [ 77 | { name = "traitlets" }, 78 | ] 79 | sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } 80 | wheels = [ 81 | { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, 82 | ] 83 | 84 | [[package]] 85 | name = "decorator" 86 | version = "5.2.1" 87 | source = { registry = "https://pypi.org/simple" } 88 | sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711 } 89 | wheels = [ 90 | { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190 }, 91 | ] 92 | 93 | [[package]] 94 | name = "egglog" 95 | version = "8.0.1" 96 | source = { registry = "https://pypi.org/simple" } 97 | dependencies = [ 98 | { name = "anywidget" }, 99 | { name = "black" }, 100 | { name = "graphviz" }, 101 | { name = "typing-extensions" }, 102 | ] 103 | sdist = { url = "https://files.pythonhosted.org/packages/c5/9e/29fdfd6d7436e55ccf6f2c70fb3530e33022dcdefc199cfc8995b838c004/egglog-8.0.1.tar.gz", hash = "sha256:b817b46de37e764efa3bf54cc1909e3e798ffe5f9488659120ef8419e6573d3b", size = 3492895 } 104 | wheels = [ 105 | { url = "https://files.pythonhosted.org/packages/ba/b2/0e1aafecd9b73be4f01f64e5fac2b5c9e02126715f3c70cc69a76867cd6f/egglog-8.0.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:dcf50e2d3b4ab46e3d8cfc5da2771457057348709f765d5eee1089697ae020e0", size = 4873492 }, 106 | { url = "https://files.pythonhosted.org/packages/79/9b/7d6679c9fa7e97b122aa2765205e50ab1827805a2732843285f17e124548/egglog-8.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f13e1227efabc167962c41b25a3af77de61943c94ac86f2ff05b8f40e0861a8f", size = 3260926 }, 107 | { url = "https://files.pythonhosted.org/packages/39/f9/9a4ae44a7bb3eea4ec2f0f77a83246d40e3c6bc17debfec52aa95e78507d/egglog-8.0.1-cp312-none-win_amd64.whl", hash = "sha256:5b4ed2d0398b1775b8670c239e831c9ac8e3625127857c3a739f8a434b7f5981", size = 2788710 }, 108 | ] 109 | 110 | [[package]] 111 | name = "executing" 112 | version = "2.2.0" 113 | source = { registry = "https://pypi.org/simple" } 114 | sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } 115 | wheels = [ 116 | { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, 117 | ] 118 | 119 | [[package]] 120 | name = "graphviz" 121 | version = "0.20.3" 122 | source = { registry = "https://pypi.org/simple" } 123 | sdist = { url = "https://files.pythonhosted.org/packages/fa/83/5a40d19b8347f017e417710907f824915fba411a9befd092e52746b63e9f/graphviz-0.20.3.zip", hash = "sha256:09d6bc81e6a9fa392e7ba52135a9d49f1ed62526f96499325930e87ca1b5925d", size = 256455 } 124 | wheels = [ 125 | { url = "https://files.pythonhosted.org/packages/00/be/d59db2d1d52697c6adc9eacaf50e8965b6345cc143f671e1ed068818d5cf/graphviz-0.20.3-py3-none-any.whl", hash = "sha256:81f848f2904515d8cd359cc611faba817598d2feaac4027b266aa3eda7b3dde5", size = 47126 }, 126 | ] 127 | 128 | [[package]] 129 | name = "iniconfig" 130 | version = "2.1.0" 131 | source = { registry = "https://pypi.org/simple" } 132 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } 133 | wheels = [ 134 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, 135 | ] 136 | 137 | [[package]] 138 | name = "ipython" 139 | version = "9.0.2" 140 | source = { registry = "https://pypi.org/simple" } 141 | dependencies = [ 142 | { name = "colorama", marker = "sys_platform == 'win32'" }, 143 | { name = "decorator" }, 144 | { name = "ipython-pygments-lexers" }, 145 | { name = "jedi" }, 146 | { name = "matplotlib-inline" }, 147 | { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, 148 | { name = "prompt-toolkit" }, 149 | { name = "pygments" }, 150 | { name = "stack-data" }, 151 | { name = "traitlets" }, 152 | ] 153 | sdist = { url = "https://files.pythonhosted.org/packages/7d/ce/012a0f40ca58a966f87a6e894d6828e2817657cbdf522b02a5d3a87d92ce/ipython-9.0.2.tar.gz", hash = "sha256:ec7b479e3e5656bf4f58c652c120494df1820f4f28f522fb7ca09e213c2aab52", size = 4366102 } 154 | wheels = [ 155 | { url = "https://files.pythonhosted.org/packages/20/3a/917cb9e72f4e1a4ea13c862533205ae1319bd664119189ee5cc9e4e95ebf/ipython-9.0.2-py3-none-any.whl", hash = "sha256:143ef3ea6fb1e1bffb4c74b114051de653ffb7737a3f7ab1670e657ca6ae8c44", size = 600524 }, 156 | ] 157 | 158 | [[package]] 159 | name = "ipython-pygments-lexers" 160 | version = "1.1.1" 161 | source = { registry = "https://pypi.org/simple" } 162 | dependencies = [ 163 | { name = "pygments" }, 164 | ] 165 | sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393 } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 }, 168 | ] 169 | 170 | [[package]] 171 | name = "ipywidgets" 172 | version = "8.1.5" 173 | source = { registry = "https://pypi.org/simple" } 174 | dependencies = [ 175 | { name = "comm" }, 176 | { name = "ipython" }, 177 | { name = "jupyterlab-widgets" }, 178 | { name = "traitlets" }, 179 | { name = "widgetsnbextension" }, 180 | ] 181 | sdist = { url = "https://files.pythonhosted.org/packages/c7/4c/dab2a281b07596a5fc220d49827fe6c794c66f1493d7a74f1df0640f2cc5/ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17", size = 116723 } 182 | wheels = [ 183 | { url = "https://files.pythonhosted.org/packages/22/2d/9c0b76f2f9cc0ebede1b9371b6f317243028ed60b90705863d493bae622e/ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245", size = 139767 }, 184 | ] 185 | 186 | [[package]] 187 | name = "jedi" 188 | version = "0.19.2" 189 | source = { registry = "https://pypi.org/simple" } 190 | dependencies = [ 191 | { name = "parso" }, 192 | ] 193 | sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287 } 194 | wheels = [ 195 | { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, 196 | ] 197 | 198 | [[package]] 199 | name = "jupyterlab-widgets" 200 | version = "3.0.13" 201 | source = { registry = "https://pypi.org/simple" } 202 | sdist = { url = "https://files.pythonhosted.org/packages/59/73/fa26bbb747a9ea4fca6b01453aa22990d52ab62dd61384f1ac0dc9d4e7ba/jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed", size = 203556 } 203 | wheels = [ 204 | { url = "https://files.pythonhosted.org/packages/a9/93/858e87edc634d628e5d752ba944c2833133a28fa87bb093e6832ced36a3e/jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54", size = 214392 }, 205 | ] 206 | 207 | [[package]] 208 | name = "llvmlite" 209 | version = "0.44.0" 210 | source = { registry = "https://pypi.org/simple" } 211 | sdist = { url = "https://files.pythonhosted.org/packages/89/6a/95a3d3610d5c75293d5dbbb2a76480d5d4eeba641557b69fe90af6c5b84e/llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4", size = 171880 } 212 | wheels = [ 213 | { url = "https://files.pythonhosted.org/packages/15/86/e3c3195b92e6e492458f16d233e58a1a812aa2bfbef9bdd0fbafcec85c60/llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad", size = 28132297 }, 214 | { url = "https://files.pythonhosted.org/packages/d6/53/373b6b8be67b9221d12b24125fd0ec56b1078b660eeae266ec388a6ac9a0/llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db", size = 26201105 }, 215 | { url = "https://files.pythonhosted.org/packages/cb/da/8341fd3056419441286c8e26bf436923021005ece0bff5f41906476ae514/llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9", size = 42361901 }, 216 | { url = "https://files.pythonhosted.org/packages/53/ad/d79349dc07b8a395a99153d7ce8b01d6fcdc9f8231355a5df55ded649b61/llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d", size = 41184247 }, 217 | { url = "https://files.pythonhosted.org/packages/e2/3b/a9a17366af80127bd09decbe2a54d8974b6d8b274b39bf47fbaedeec6307/llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1", size = 30332380 }, 218 | { url = "https://files.pythonhosted.org/packages/89/24/4c0ca705a717514c2092b18476e7a12c74d34d875e05e4d742618ebbf449/llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516", size = 28132306 }, 219 | { url = "https://files.pythonhosted.org/packages/01/cf/1dd5a60ba6aee7122ab9243fd614abcf22f36b0437cbbe1ccf1e3391461c/llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e", size = 26201090 }, 220 | { url = "https://files.pythonhosted.org/packages/d2/1b/656f5a357de7135a3777bd735cc7c9b8f23b4d37465505bd0eaf4be9befe/llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf", size = 42361904 }, 221 | { url = "https://files.pythonhosted.org/packages/d8/e1/12c5f20cb9168fb3464a34310411d5ad86e4163c8ff2d14a2b57e5cc6bac/llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc", size = 41184245 }, 222 | { url = "https://files.pythonhosted.org/packages/d0/81/e66fc86539293282fd9cb7c9417438e897f369e79ffb62e1ae5e5154d4dd/llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930", size = 30331193 }, 223 | ] 224 | 225 | [[package]] 226 | name = "matplotlib-inline" 227 | version = "0.1.7" 228 | source = { registry = "https://pypi.org/simple" } 229 | dependencies = [ 230 | { name = "traitlets" }, 231 | ] 232 | sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } 233 | wheels = [ 234 | { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, 235 | ] 236 | 237 | [[package]] 238 | name = "mlir-egglog" 239 | version = "0.1.0" 240 | source = { editable = "." } 241 | dependencies = [ 242 | { name = "egglog" }, 243 | { name = "llvmlite" }, 244 | { name = "numpy" }, 245 | { name = "pyyaml" }, 246 | ] 247 | 248 | [package.dev-dependencies] 249 | dev = [ 250 | { name = "black" }, 251 | { name = "mypy" }, 252 | { name = "pytest" }, 253 | { name = "ruff" }, 254 | ] 255 | 256 | [package.metadata] 257 | requires-dist = [ 258 | { name = "egglog", specifier = ">=8.0.1" }, 259 | { name = "llvmlite", specifier = ">=0.44.0" }, 260 | { name = "numpy", specifier = ">=2.2.5" }, 261 | { name = "pyyaml", specifier = ">=6.0.2" }, 262 | ] 263 | 264 | [package.metadata.requires-dev] 265 | dev = [ 266 | { name = "black", specifier = ">=25.1.0" }, 267 | { name = "mypy", specifier = ">=1.15.0" }, 268 | { name = "pytest", specifier = ">=8.3.5" }, 269 | { name = "ruff", specifier = ">=0.9.10" }, 270 | ] 271 | 272 | [[package]] 273 | name = "mypy" 274 | version = "1.15.0" 275 | source = { registry = "https://pypi.org/simple" } 276 | dependencies = [ 277 | { name = "mypy-extensions" }, 278 | { name = "typing-extensions" }, 279 | ] 280 | sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } 281 | wheels = [ 282 | { url = "https://files.pythonhosted.org/packages/98/3a/03c74331c5eb8bd025734e04c9840532226775c47a2c39b56a0c8d4f128d/mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd", size = 10793981 }, 283 | { url = "https://files.pythonhosted.org/packages/f0/1a/41759b18f2cfd568848a37c89030aeb03534411eef981df621d8fad08a1d/mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f", size = 9749175 }, 284 | { url = "https://files.pythonhosted.org/packages/12/7e/873481abf1ef112c582db832740f4c11b2bfa510e829d6da29b0ab8c3f9c/mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464", size = 11455675 }, 285 | { url = "https://files.pythonhosted.org/packages/b3/d0/92ae4cde706923a2d3f2d6c39629134063ff64b9dedca9c1388363da072d/mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee", size = 12410020 }, 286 | { url = "https://files.pythonhosted.org/packages/46/8b/df49974b337cce35f828ba6fda228152d6db45fed4c86ba56ffe442434fd/mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e", size = 12498582 }, 287 | { url = "https://files.pythonhosted.org/packages/13/50/da5203fcf6c53044a0b699939f31075c45ae8a4cadf538a9069b165c1050/mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22", size = 9366614 }, 288 | { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, 289 | { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, 290 | { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, 291 | { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, 292 | { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, 293 | { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, 294 | { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, 295 | ] 296 | 297 | [[package]] 298 | name = "mypy-extensions" 299 | version = "1.0.0" 300 | source = { registry = "https://pypi.org/simple" } 301 | sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 } 302 | wheels = [ 303 | { url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 }, 304 | ] 305 | 306 | [[package]] 307 | name = "numpy" 308 | version = "2.2.5" 309 | source = { registry = "https://pypi.org/simple" } 310 | sdist = { url = "https://files.pythonhosted.org/packages/dc/b2/ce4b867d8cd9c0ee84938ae1e6a6f7926ebf928c9090d036fc3c6a04f946/numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291", size = 20273920 } 311 | wheels = [ 312 | { url = "https://files.pythonhosted.org/packages/e2/f7/1fd4ff108cd9d7ef929b8882692e23665dc9c23feecafbb9c6b80f4ec583/numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051", size = 20948633 }, 313 | { url = "https://files.pythonhosted.org/packages/12/03/d443c278348371b20d830af155ff2079acad6a9e60279fac2b41dbbb73d8/numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc", size = 14176123 }, 314 | { url = "https://files.pythonhosted.org/packages/2b/0b/5ca264641d0e7b14393313304da48b225d15d471250376f3fbdb1a2be603/numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e", size = 5163817 }, 315 | { url = "https://files.pythonhosted.org/packages/04/b3/d522672b9e3d28e26e1613de7675b441bbd1eaca75db95680635dd158c67/numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa", size = 6698066 }, 316 | { url = "https://files.pythonhosted.org/packages/a0/93/0f7a75c1ff02d4b76df35079676b3b2719fcdfb39abdf44c8b33f43ef37d/numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571", size = 14087277 }, 317 | { url = "https://files.pythonhosted.org/packages/b0/d9/7c338b923c53d431bc837b5b787052fef9ae68a56fe91e325aac0d48226e/numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073", size = 16135742 }, 318 | { url = "https://files.pythonhosted.org/packages/2d/10/4dec9184a5d74ba9867c6f7d1e9f2e0fb5fe96ff2bf50bb6f342d64f2003/numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8", size = 15581825 }, 319 | { url = "https://files.pythonhosted.org/packages/80/1f/2b6fcd636e848053f5b57712a7d1880b1565eec35a637fdfd0a30d5e738d/numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae", size = 17899600 }, 320 | { url = "https://files.pythonhosted.org/packages/ec/87/36801f4dc2623d76a0a3835975524a84bd2b18fe0f8835d45c8eae2f9ff2/numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb", size = 6312626 }, 321 | { url = "https://files.pythonhosted.org/packages/8b/09/4ffb4d6cfe7ca6707336187951992bd8a8b9142cf345d87ab858d2d7636a/numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282", size = 12645715 }, 322 | { url = "https://files.pythonhosted.org/packages/e2/a0/0aa7f0f4509a2e07bd7a509042967c2fab635690d4f48c6c7b3afd4f448c/numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4", size = 20935102 }, 323 | { url = "https://files.pythonhosted.org/packages/7e/e4/a6a9f4537542912ec513185396fce52cdd45bdcf3e9d921ab02a93ca5aa9/numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f", size = 14191709 }, 324 | { url = "https://files.pythonhosted.org/packages/be/65/72f3186b6050bbfe9c43cb81f9df59ae63603491d36179cf7a7c8d216758/numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9", size = 5149173 }, 325 | { url = "https://files.pythonhosted.org/packages/e5/e9/83e7a9432378dde5802651307ae5e9ea07bb72b416728202218cd4da2801/numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191", size = 6684502 }, 326 | { url = "https://files.pythonhosted.org/packages/ea/27/b80da6c762394c8ee516b74c1f686fcd16c8f23b14de57ba0cad7349d1d2/numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372", size = 14084417 }, 327 | { url = "https://files.pythonhosted.org/packages/aa/fc/ebfd32c3e124e6a1043e19c0ab0769818aa69050ce5589b63d05ff185526/numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d", size = 16133807 }, 328 | { url = "https://files.pythonhosted.org/packages/bf/9b/4cc171a0acbe4666f7775cfd21d4eb6bb1d36d3a0431f48a73e9212d2278/numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7", size = 15575611 }, 329 | { url = "https://files.pythonhosted.org/packages/a3/45/40f4135341850df48f8edcf949cf47b523c404b712774f8855a64c96ef29/numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73", size = 17895747 }, 330 | { url = "https://files.pythonhosted.org/packages/f8/4c/b32a17a46f0ffbde8cc82df6d3daeaf4f552e346df143e1b188a701a8f09/numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b", size = 6309594 }, 331 | { url = "https://files.pythonhosted.org/packages/13/ae/72e6276feb9ef06787365b05915bfdb057d01fceb4a43cb80978e518d79b/numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471", size = 12638356 }, 332 | { url = "https://files.pythonhosted.org/packages/79/56/be8b85a9f2adb688e7ded6324e20149a03541d2b3297c3ffc1a73f46dedb/numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6", size = 20963778 }, 333 | { url = "https://files.pythonhosted.org/packages/ff/77/19c5e62d55bff507a18c3cdff82e94fe174957bad25860a991cac719d3ab/numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba", size = 14207279 }, 334 | { url = "https://files.pythonhosted.org/packages/75/22/aa11f22dc11ff4ffe4e849d9b63bbe8d4ac6d5fae85ddaa67dfe43be3e76/numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133", size = 5199247 }, 335 | { url = "https://files.pythonhosted.org/packages/4f/6c/12d5e760fc62c08eded0394f62039f5a9857f758312bf01632a81d841459/numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376", size = 6711087 }, 336 | { url = "https://files.pythonhosted.org/packages/ef/94/ece8280cf4218b2bee5cec9567629e61e51b4be501e5c6840ceb593db945/numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19", size = 14059964 }, 337 | { url = "https://files.pythonhosted.org/packages/39/41/c5377dac0514aaeec69115830a39d905b1882819c8e65d97fc60e177e19e/numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0", size = 16121214 }, 338 | { url = "https://files.pythonhosted.org/packages/db/54/3b9f89a943257bc8e187145c6bc0eb8e3d615655f7b14e9b490b053e8149/numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a", size = 15575788 }, 339 | { url = "https://files.pythonhosted.org/packages/b1/c4/2e407e85df35b29f79945751b8f8e671057a13a376497d7fb2151ba0d290/numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066", size = 17893672 }, 340 | { url = "https://files.pythonhosted.org/packages/29/7e/d0b44e129d038dba453f00d0e29ebd6eaf2f06055d72b95b9947998aca14/numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e", size = 6377102 }, 341 | { url = "https://files.pythonhosted.org/packages/63/be/b85e4aa4bf42c6502851b971f1c326d583fcc68227385f92089cf50a7b45/numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8", size = 12750096 }, 342 | ] 343 | 344 | [[package]] 345 | name = "packaging" 346 | version = "24.2" 347 | source = { registry = "https://pypi.org/simple" } 348 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 349 | wheels = [ 350 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 351 | ] 352 | 353 | [[package]] 354 | name = "parso" 355 | version = "0.8.4" 356 | source = { registry = "https://pypi.org/simple" } 357 | sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } 358 | wheels = [ 359 | { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, 360 | ] 361 | 362 | [[package]] 363 | name = "pathspec" 364 | version = "0.12.1" 365 | source = { registry = "https://pypi.org/simple" } 366 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 367 | wheels = [ 368 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, 369 | ] 370 | 371 | [[package]] 372 | name = "pexpect" 373 | version = "4.9.0" 374 | source = { registry = "https://pypi.org/simple" } 375 | dependencies = [ 376 | { name = "ptyprocess" }, 377 | ] 378 | sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } 379 | wheels = [ 380 | { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, 381 | ] 382 | 383 | [[package]] 384 | name = "platformdirs" 385 | version = "4.3.6" 386 | source = { registry = "https://pypi.org/simple" } 387 | sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } 388 | wheels = [ 389 | { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, 390 | ] 391 | 392 | [[package]] 393 | name = "pluggy" 394 | version = "1.5.0" 395 | source = { registry = "https://pypi.org/simple" } 396 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 397 | wheels = [ 398 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 399 | ] 400 | 401 | [[package]] 402 | name = "prompt-toolkit" 403 | version = "3.0.50" 404 | source = { registry = "https://pypi.org/simple" } 405 | dependencies = [ 406 | { name = "wcwidth" }, 407 | ] 408 | sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } 409 | wheels = [ 410 | { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, 411 | ] 412 | 413 | [[package]] 414 | name = "psygnal" 415 | version = "0.12.0" 416 | source = { registry = "https://pypi.org/simple" } 417 | sdist = { url = "https://files.pythonhosted.org/packages/34/7f/ef01fa880529b0cbdf33a02e690cbca7868ee0ee291bcb2ebce53f3b3043/psygnal-0.12.0.tar.gz", hash = "sha256:8d2a99803f3152c469d3642d36c04d680213a20e114245558e026695adf9a9c2", size = 104400 } 418 | wheels = [ 419 | { url = "https://files.pythonhosted.org/packages/9b/2e/6cff528f8f5dc7f60221fddca85ed52131a90b606a72c5a4085606d5217d/psygnal-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dac134c8890e3d0e413ab701fcb56a882f9b151a6a9d625080736c36833b26ed", size = 469384 }, 420 | { url = "https://files.pythonhosted.org/packages/55/9d/774d547ed1fcb079de7fc41b1e3103b261eebae7f34da5cf05909a040470/psygnal-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bc2324cef7ba3f4d30d32895f8cb7d5cf9ad7bcfdb7955aa92a0fbfe7537ec3f", size = 426617 }, 421 | { url = "https://files.pythonhosted.org/packages/b4/89/300991108d86c00e6aa84ea85e9c998e27eed59fdcb1abd3e69b9f4d62f5/psygnal-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae2bd6edcf911fbff34ed75150e8f8dfb246ebf203514c7c1e4397cabbb1368a", size = 787401 }, 422 | { url = "https://files.pythonhosted.org/packages/9e/ae/8cc64c0f1eebbc4be74a953e76e431431c770a30f28b71b48edc7f6b8288/psygnal-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d6fbeee192beab90ca23d9d3ee3bf1eb7ef5f00e815fa53e23e402feee617242", size = 781293 }, 423 | { url = "https://files.pythonhosted.org/packages/84/09/f00841834b7ae543bd232c22e557914d63d0d0430d32980883421d5981bb/psygnal-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:25a9f2db710a6cd2566b3e0e03cf6e04d56276f36ac86b42fa22d81f9a4ac0f2", size = 381816 }, 424 | { url = "https://files.pythonhosted.org/packages/cb/b4/64a06b1d9b7628c84c9ea68a6cdc9d54378bae04695e7173addb9cf46607/psygnal-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2f4c1fed9337f57778109c397b6b9591961123ce4bbeb068115c0468964fc2b4", size = 468346 }, 425 | { url = "https://files.pythonhosted.org/packages/78/be/b3df7dac845f5f6b9897e60d19c3eaed27b56b024099588db92c3b76bb21/psygnal-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d5a953a50fc8263bb23bc558b926cf691f70c9c781c68c64c983fb8cbead910", size = 426482 }, 426 | { url = "https://files.pythonhosted.org/packages/df/36/0017e838d3c63081a64e6d2252c8dda368a6d0898c7ecf689ba678fe4127/psygnal-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a67ec8e0c8a6553dd56ed653f87c46ef652b0c512bb8c8f8c5adcff3907751f", size = 785239 }, 427 | { url = "https://files.pythonhosted.org/packages/67/d0/7057151debcd5c7d8ce7789d276e18681d5c141c9222c9cf99ce3a418680/psygnal-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:742abb2d0e230521b208161eeab06abb682a19239e734e543a269214c84a54d2", size = 779690 }, 428 | { url = "https://files.pythonhosted.org/packages/5e/ae/a3d6815db583b6d05878b3647ea0e2aa21ce6941d03c9d2c6caad1afbcf6/psygnal-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:d779f20c6977ec9d5b9fece23b4b28bbcf0a7773539a4a176b5527aea5da27c7", size = 382622 }, 429 | { url = "https://files.pythonhosted.org/packages/eb/fa/84fc30ad391081cb119099aae5491ba4a9ebd34ce5139bf05b31813abb84/psygnal-0.12.0-py3-none-any.whl", hash = "sha256:15f39abd8bee2926e79da76bec31a258d03dbe3e61d22d6251f65caefbae5d54", size = 78492 }, 430 | ] 431 | 432 | [[package]] 433 | name = "ptyprocess" 434 | version = "0.7.0" 435 | source = { registry = "https://pypi.org/simple" } 436 | sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } 437 | wheels = [ 438 | { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, 439 | ] 440 | 441 | [[package]] 442 | name = "pure-eval" 443 | version = "0.2.3" 444 | source = { registry = "https://pypi.org/simple" } 445 | sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } 446 | wheels = [ 447 | { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, 448 | ] 449 | 450 | [[package]] 451 | name = "pygments" 452 | version = "2.19.1" 453 | source = { registry = "https://pypi.org/simple" } 454 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } 455 | wheels = [ 456 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, 457 | ] 458 | 459 | [[package]] 460 | name = "pytest" 461 | version = "8.3.5" 462 | source = { registry = "https://pypi.org/simple" } 463 | dependencies = [ 464 | { name = "colorama", marker = "sys_platform == 'win32'" }, 465 | { name = "iniconfig" }, 466 | { name = "packaging" }, 467 | { name = "pluggy" }, 468 | ] 469 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } 470 | wheels = [ 471 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, 472 | ] 473 | 474 | [[package]] 475 | name = "pyyaml" 476 | version = "6.0.2" 477 | source = { registry = "https://pypi.org/simple" } 478 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 479 | wheels = [ 480 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 481 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 482 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 483 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 484 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 485 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 486 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 487 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 488 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 489 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 490 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 491 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 492 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 493 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 494 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 495 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 496 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 497 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 498 | ] 499 | 500 | [[package]] 501 | name = "ruff" 502 | version = "0.11.0" 503 | source = { registry = "https://pypi.org/simple" } 504 | sdist = { url = "https://files.pythonhosted.org/packages/77/2b/7ca27e854d92df5e681e6527dc0f9254c9dc06c8408317893cf96c851cdd/ruff-0.11.0.tar.gz", hash = "sha256:e55c620690a4a7ee6f1cccb256ec2157dc597d109400ae75bbf944fc9d6462e2", size = 3799407 } 505 | wheels = [ 506 | { url = "https://files.pythonhosted.org/packages/48/40/3d0340a9e5edc77d37852c0cd98c5985a5a8081fc3befaeb2ae90aaafd2b/ruff-0.11.0-py3-none-linux_armv6l.whl", hash = "sha256:dc67e32bc3b29557513eb7eeabb23efdb25753684b913bebb8a0c62495095acb", size = 10098158 }, 507 | { url = "https://files.pythonhosted.org/packages/ec/a9/d8f5abb3b87b973b007649ac7bf63665a05b2ae2b2af39217b09f52abbbf/ruff-0.11.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:38c23fd9bdec4eb437b4c1e3595905a0a8edfccd63a790f818b28c78fe345639", size = 10879071 }, 508 | { url = "https://files.pythonhosted.org/packages/ab/62/aaa198614c6211677913ec480415c5e6509586d7b796356cec73a2f8a3e6/ruff-0.11.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7c8661b0be91a38bd56db593e9331beaf9064a79028adee2d5f392674bbc5e88", size = 10247944 }, 509 | { url = "https://files.pythonhosted.org/packages/9f/52/59e0a9f2cf1ce5e6cbe336b6dd0144725c8ea3b97cac60688f4e7880bf13/ruff-0.11.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6c0e8d3d2db7e9f6efd884f44b8dc542d5b6b590fc4bb334fdbc624d93a29a2", size = 10421725 }, 510 | { url = "https://files.pythonhosted.org/packages/a6/c3/dcd71acc6dff72ce66d13f4be5bca1dbed4db678dff2f0f6f307b04e5c02/ruff-0.11.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c3156d3f4b42e57247275a0a7e15a851c165a4fc89c5e8fa30ea6da4f7407b8", size = 9954435 }, 511 | { url = "https://files.pythonhosted.org/packages/a6/9a/342d336c7c52dbd136dee97d4c7797e66c3f92df804f8f3b30da59b92e9c/ruff-0.11.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:490b1e147c1260545f6d041c4092483e3f6d8eba81dc2875eaebcf9140b53905", size = 11492664 }, 512 | { url = "https://files.pythonhosted.org/packages/84/35/6e7defd2d7ca95cc385ac1bd9f7f2e4a61b9cc35d60a263aebc8e590c462/ruff-0.11.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1bc09a7419e09662983b1312f6fa5dab829d6ab5d11f18c3760be7ca521c9329", size = 12207856 }, 513 | { url = "https://files.pythonhosted.org/packages/22/78/da669c8731bacf40001c880ada6d31bcfb81f89cc996230c3b80d319993e/ruff-0.11.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcfa478daf61ac8002214eb2ca5f3e9365048506a9d52b11bea3ecea822bb844", size = 11645156 }, 514 | { url = "https://files.pythonhosted.org/packages/ee/47/e27d17d83530a208f4a9ab2e94f758574a04c51e492aa58f91a3ed7cbbcb/ruff-0.11.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fbb2aed66fe742a6a3a0075ed467a459b7cedc5ae01008340075909d819df1e", size = 13884167 }, 515 | { url = "https://files.pythonhosted.org/packages/9f/5e/42ffbb0a5d4b07bbc642b7d58357b4e19a0f4774275ca6ca7d1f7b5452cd/ruff-0.11.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c0c1ff014351c0b0cdfdb1e35fa83b780f1e065667167bb9502d47ca41e6db", size = 11348311 }, 516 | { url = "https://files.pythonhosted.org/packages/c8/51/dc3ce0c5ce1a586727a3444a32f98b83ba99599bb1ebca29d9302886e87f/ruff-0.11.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e4fd5ff5de5f83e0458a138e8a869c7c5e907541aec32b707f57cf9a5e124445", size = 10305039 }, 517 | { url = "https://files.pythonhosted.org/packages/60/e0/475f0c2f26280f46f2d6d1df1ba96b3399e0234cf368cc4c88e6ad10dcd9/ruff-0.11.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:96bc89a5c5fd21a04939773f9e0e276308be0935de06845110f43fd5c2e4ead7", size = 9937939 }, 518 | { url = "https://files.pythonhosted.org/packages/e2/d3/3e61b7fd3e9cdd1e5b8c7ac188bec12975c824e51c5cd3d64caf81b0331e/ruff-0.11.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a9352b9d767889ec5df1483f94870564e8102d4d7e99da52ebf564b882cdc2c7", size = 10923259 }, 519 | { url = "https://files.pythonhosted.org/packages/30/32/cd74149ebb40b62ddd14bd2d1842149aeb7f74191fb0f49bd45c76909ff2/ruff-0.11.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:049a191969a10897fe052ef9cc7491b3ef6de79acd7790af7d7897b7a9bfbcb6", size = 11406212 }, 520 | { url = "https://files.pythonhosted.org/packages/00/ef/033022a6b104be32e899b00de704d7c6d1723a54d4c9e09d147368f14b62/ruff-0.11.0-py3-none-win32.whl", hash = "sha256:3191e9116b6b5bbe187447656f0c8526f0d36b6fd89ad78ccaad6bdc2fad7df2", size = 10310905 }, 521 | { url = "https://files.pythonhosted.org/packages/ed/8a/163f2e78c37757d035bd56cd60c8d96312904ca4a6deeab8442d7b3cbf89/ruff-0.11.0-py3-none-win_amd64.whl", hash = "sha256:c58bfa00e740ca0a6c43d41fb004cd22d165302f360aaa56f7126d544db31a21", size = 11411730 }, 522 | { url = "https://files.pythonhosted.org/packages/4e/f7/096f6efabe69b49d7ca61052fc70289c05d8d35735c137ef5ba5ef423662/ruff-0.11.0-py3-none-win_arm64.whl", hash = "sha256:868364fc23f5aa122b00c6f794211e85f7e78f5dffdf7c590ab90b8c4e69b657", size = 10538956 }, 523 | ] 524 | 525 | [[package]] 526 | name = "stack-data" 527 | version = "0.6.3" 528 | source = { registry = "https://pypi.org/simple" } 529 | dependencies = [ 530 | { name = "asttokens" }, 531 | { name = "executing" }, 532 | { name = "pure-eval" }, 533 | ] 534 | sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } 535 | wheels = [ 536 | { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, 537 | ] 538 | 539 | [[package]] 540 | name = "traitlets" 541 | version = "5.14.3" 542 | source = { registry = "https://pypi.org/simple" } 543 | sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } 544 | wheels = [ 545 | { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, 546 | ] 547 | 548 | [[package]] 549 | name = "typing-extensions" 550 | version = "4.12.2" 551 | source = { registry = "https://pypi.org/simple" } 552 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 553 | wheels = [ 554 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 555 | ] 556 | 557 | [[package]] 558 | name = "wcwidth" 559 | version = "0.2.13" 560 | source = { registry = "https://pypi.org/simple" } 561 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } 562 | wheels = [ 563 | { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, 564 | ] 565 | 566 | [[package]] 567 | name = "widgetsnbextension" 568 | version = "4.0.13" 569 | source = { registry = "https://pypi.org/simple" } 570 | sdist = { url = "https://files.pythonhosted.org/packages/56/fc/238c424fd7f4ebb25f8b1da9a934a3ad7c848286732ae04263661eb0fc03/widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6", size = 1164730 } 571 | wheels = [ 572 | { url = "https://files.pythonhosted.org/packages/21/02/88b65cc394961a60c43c70517066b6b679738caf78506a5da7b88ffcb643/widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71", size = 2335872 }, 573 | ] 574 | --------------------------------------------------------------------------------