├── .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 |
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 |
--------------------------------------------------------------------------------