diff --git a/Cargo.lock b/Cargo.lock index 915ebf9..5deb985 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,13 +8,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "athena" -version = "0.1.0" -dependencies = [ - "rand", -] - [[package]] name = "bitflags" version = "2.11.1" @@ -23,9 +16,9 @@ checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" @@ -47,6 +40,31 @@ dependencies = [ "libc", ] +[[package]] +name = "delta" +version = "0.1.0" +dependencies = [ + "delta-macros", + "rand", +] + +[[package]] +name = "delta-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "delta-py" +version = "0.1.0" +dependencies = [ + "delta", + "pyo3", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -142,6 +160,18 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "prettyplease" version = "0.2.37" @@ -161,6 +191,64 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +dependencies = [ + "libc", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", +] + +[[package]] +name = "pyo3-build-config" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.45" @@ -252,6 +340,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/Cargo.toml b/Cargo.toml index f810966..e84e50a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,5 @@ -[package] -name = "athena" -# versions -version = "0.1.0" -edition = "2024" -rust-version = "1.95.0" -# info -authors = ["commanderxa"] -readme = "README.md" -repository = "https://github.com/CommanderXA/athena" -license-file = "LICENSE.txt" +[workspace] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +members = ["delta", "delta-macros", "delta-py"] -[dependencies] -rand = "0.10.1" +resolver = "2" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b5abac3 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +py_develop: + cd ./delta-py && maturin develop --uv + +py_release: + cd ./delta-py && maturin build --release diff --git a/README.md b/README.md index e19e41c..8dd3807 100644 --- a/README.md +++ b/README.md @@ -5,23 +5,23 @@
-

The ATHENA Project

+

The DELTA Project

- + Rust 1.95.0+ - - Build + + Build - - Stars + + Stars - - Last Commit + + Last Commit - - License + + License
diff --git a/SECURITY.md b/SECURITY.md index e789524..770c428 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,6 +2,6 @@ If you have found a security vulnerability in this project, then please share your finding. -Please, report security issues here: https://github.com/CommanderXA/athena/security/advisories/new +Please, report security issues here: https://github.com/CommanderXA/delta/security/advisories/new Describe the issue in detail as much as possible. \ No newline at end of file diff --git a/delta-macros/Cargo.toml b/delta-macros/Cargo.toml new file mode 100644 index 0000000..f6185b0 --- /dev/null +++ b/delta-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "delta-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/delta-macros/src/lib.rs b/delta-macros/src/lib.rs new file mode 100644 index 0000000..974b986 --- /dev/null +++ b/delta-macros/src/lib.rs @@ -0,0 +1,154 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Type}; + +/// Derive macro for the `Module` trait. +/// +/// Automatically implements: +/// - `module_name()` → returns the struct's name as a `String` +/// - `parameters()` → collects all fields of type `nn::Parameter` +/// - `submodules()` → collects all fields that implement `Module` +/// (detected via the `#[module]` field attribute) +/// +/// The user must still implement `forward()` manually. +/// +/// # Field Attributes +/// +/// - `#[module]` — marks a field as a sub-module (must be `Box` or +/// a concrete type implementing `Module`). The macro will include it in the +/// `submodules()` return value. +/// +/// Fields of type `nn::Parameter` are picked up **automatically** without any +/// attribute, because the type itself is unambiguous. +/// +/// # Example +/// +/// ```ignore +/// use your_crate::nn; +/// use module_derive::Module; +/// +/// #[derive(Module)] +/// pub struct Linear { +/// pub weight: nn::Parameter, +/// pub bias: nn::Parameter, +/// #[module] +/// pub activation: Box, +/// } +/// +/// impl Module for Linear { +/// fn forward(&self, args: Vec, kwargs: HashMap) -> IValue { +/// todo!() +/// } +/// } +/// ``` +#[proc_macro_derive(Module, attributes(module))] +pub fn derive_module(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let struct_name = &input.ident; + let struct_name_str = struct_name.to_string(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(f) => &f.named, + Fields::Unnamed(_) => { + return syn::Error::new_spanned( + struct_name, + "Module derive does not support tuple structs", + ) + .to_compile_error() + .into() + } + Fields::Unit => { + // Unit struct — no parameters or submodules + return expand(struct_name, &struct_name_str, vec![], vec![]); + } + }, + _ => { + return syn::Error::new_spanned(struct_name, "Module can only be derived for structs") + .to_compile_error() + .into() + } + }; + + let mut param_fields = Vec::new(); + let mut submodule_fields = Vec::new(); + + for field in fields { + let field_name = field.ident.as_ref().expect("named field"); + + // Check for #[module] attribute → submodule + let is_submodule = field + .attrs + .iter() + .any(|a| a.path().is_ident("module")); + + if is_submodule { + submodule_fields.push(field_name.clone()); + } else if is_nn_parameter(&field.ty) { + // Automatically detect nn::Parameter fields + param_fields.push(field_name.clone()); + } + } + + expand(struct_name, &struct_name_str, param_fields, submodule_fields) +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Returns `true` when the type path ends in `Parameter` (covers both +/// `nn::Parameter` and a bare `Parameter` import). +fn is_nn_parameter(ty: &Type) -> bool { + if let Type::Path(type_path) = ty { + if let Some(last) = type_path.path.segments.last() { + return last.ident == "Parameter"; + } + } + false +} + +/// Emit the `module_name`, `parameters`, and `submodules` impl block. +fn expand( + struct_name: &syn::Ident, + struct_name_str: &str, + param_fields: Vec, + submodule_fields: Vec, +) -> TokenStream { + let parameters_body = if param_fields.is_empty() { + quote! { vec![] } + } else { + quote! { + vec![ + #( self.#param_fields.clone() ),* + ] + } + }; + + let submodules_body = if submodule_fields.is_empty() { + quote! { vec![] } + } else { + quote! { + vec![ + #( &self.#submodule_fields as &dyn Module ),* + ] + } + }; + + quote! { + impl Module for #struct_name { + fn module_name(&self) -> String { + #struct_name_str.to_string() + } + + fn parameters(&self) -> Vec { + #parameters_body + } + + fn submodules(&self) -> Vec<&dyn Module> { + #submodules_body + } + } + } + .into() +} \ No newline at end of file diff --git a/delta-py/.github/workflows/CI.yml b/delta-py/.github/workflows/CI.yml new file mode 100644 index 0000000..546037c --- /dev/null +++ b/delta-py/.github/workflows/CI.yml @@ -0,0 +1,186 @@ +# This file is autogenerated by maturin v1.13.1 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - '*' + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + - runner: ubuntu-22.04 + target: s390x + - runner: ubuntu-22.04 + target: ppc64le + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-22.04 + target: x86_64 + - runner: ubuntu-22.04 + target: x86 + - runner: ubuntu-22.04 + target: aarch64 + - runner: ubuntu-22.04 + target: armv7 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + manylinux: musllinux_1_2 + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + python_arch: x64 + - runner: windows-latest + target: x86 + python_arch: x86 + - runner: windows-11-arm + target: aarch64 + python_arch: arm64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.13 + architecture: ${{ matrix.platform.python_arch }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-15-intel + target: x86_64 + - runner: macos-latest + target: aarch64 + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: ${{ !startsWith(github.ref, 'refs/tags/') }} + - name: Upload wheels + uses: actions/upload-artifact@v6 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v6 + with: + name: wheels-sdist + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' }} + needs: [linux, musllinux, windows, macos, sdist] + permissions: + # Use to sign the release artifacts + id-token: write + # Used to upload release artifacts + contents: write + # Used to generate artifact attestation + attestations: write + steps: + - uses: actions/download-artifact@v7 + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v3 + with: + subject-path: 'wheels-*/*' + - name: Install uv + if: ${{ startsWith(github.ref, 'refs/tags/') }} + uses: astral-sh/setup-uv@v7 + - name: Publish to PyPI + if: ${{ startsWith(github.ref, 'refs/tags/') }} + run: uv publish 'wheels-*/*' + env: + UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }} diff --git a/delta-py/.gitignore b/delta-py/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/delta-py/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/delta-py/Cargo.toml b/delta-py/Cargo.toml new file mode 100644 index 0000000..fa72c0c --- /dev/null +++ b/delta-py/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "delta-py" +version = "0.1.0" +edition = "2024" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "delta_py" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.28.2" + +delta = { path = "../delta" } diff --git a/delta-py/pyproject.toml b/delta-py/pyproject.toml new file mode 100644 index 0000000..f703197 --- /dev/null +++ b/delta-py/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["maturin>=1.13,<2.0"] +build-backend = "maturin" + +[project] +name = "delta" +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +dependencies = ["ipykernel >= 7.2.0"] + +[tool.maturin] +python-source = "python" +module-name = "delta._delta" diff --git a/delta-py/python/delta/__init__.py b/delta-py/python/delta/__init__.py new file mode 100644 index 0000000..9cefd16 --- /dev/null +++ b/delta-py/python/delta/__init__.py @@ -0,0 +1 @@ +from ._delta import * diff --git a/delta-py/python/delta/__init__.pyi b/delta-py/python/delta/__init__.pyi new file mode 100644 index 0000000..0628689 --- /dev/null +++ b/delta-py/python/delta/__init__.pyi @@ -0,0 +1,78 @@ +# delta/_delta.pyi +from __future__ import annotations +from typing import Optional + +class Tensor: + shape: list[int] + stride: list[int] + + # ── Constructors ──────────────────────────────────────────────────────── + @staticmethod + def tensor(data: list[float], shape: list[int]) -> Tensor: ... + @staticmethod + def randn(shape: list[int]) -> Tensor: ... + @staticmethod + def zeros(shape: list[int]) -> Tensor: ... + @staticmethod + def zeros_like(tensor: Tensor) -> Tensor: ... + @staticmethod + def ones(shape: list[int]) -> Tensor: ... + @staticmethod + def ones_like(tensor: Tensor) -> Tensor: ... + @staticmethod + def arange(start: float, end: float, step: float) -> Tensor: ... + @staticmethod + def cat(tensors: list[Tensor], dim: int) -> Tensor: ... + + # ── Metadata ──────────────────────────────────────────────────────────── + def length(self) -> int: ... + def storage(self) -> list[float]: ... + def item(self) -> list[float]: ... + def grad(self) -> list[float] | None: ... + def requires_grad(self, value: bool) -> Tensor: ... + + # ── Shape manipulation ─────────────────────────────────────────────────── + def reshape(self, shape: list[int]) -> Tensor: ... + def view(self, shape: list[int]) -> Tensor: ... + def transpose(self, dim0: int, dim1: int) -> Tensor: ... + def t(self) -> Tensor: ... + def unsqueeze(self, dim: int) -> Tensor: ... + def squeeze(self, dim: list[int]) -> Tensor: ... + def expand(self, new_shape: list[int]) -> Tensor: ... + + # ── Reduction ─────────────────────────────────────────────────────────── + def sum(self, dim: Optional[int] = None, keepdim: bool = False) -> Tensor: ... + def mean(self, dim: Optional[int] = None, keepdim: bool = False) -> Tensor: ... + + # ── Element-wise ops ──────────────────────────────────────────────────── + def exp(self) -> Tensor: ... + def pow(self, n: int) -> Tensor: ... + + # ── Autograd ──────────────────────────────────────────────────────────── + def backward(self) -> None: ... + + # ── Arithmetic operators ───────────────────────────────────────────────── + def __add__(self, other: float | int | Tensor) -> Tensor: ... + def __radd__(self, other: float | int | Tensor) -> Tensor: ... + def __sub__(self, other: float | int | Tensor) -> Tensor: ... + def __rsub__(self, other: float | int | Tensor) -> Tensor: ... + def __mul__(self, other: float | int | Tensor) -> Tensor: ... + def __rmul__(self, other: float | int | Tensor) -> Tensor: ... + def __truediv__(self, other: float | int | Tensor) -> Tensor: ... + def __rtruediv__(self, other: float | int | Tensor) -> Tensor: ... + def __neg__(self) -> Tensor: ... + + # ── Display ────────────────────────────────────────────────────────────── + def __repr__(self) -> str: ... + def __str__(self) -> str: ... + +# ── Module-level functions ─────────────────────────────────────────────────── +def tensor(data: list[float], shape: list[int]) -> Tensor: ... +def randn(*shape: int) -> Tensor: ... +def zeros(*shape: int) -> Tensor: ... +def ones(*shape: int) -> Tensor: ... +def matmul(a: Tensor, b: Tensor) -> Tensor: ... +def cross(a: Tensor, b: Tensor) -> Tensor: ... +def sum(input: Tensor, dim: Optional[int] = None, keepdim: bool = False) -> Tensor: ... +def mean(input: Tensor, dim: Optional[int] = None, keepdim: bool = False) -> Tensor: ... +def cat(tensors: list[Tensor], dim: int) -> Tensor: ... diff --git a/delta-py/python/delta/nn/__init__.py b/delta-py/python/delta/nn/__init__.py new file mode 100644 index 0000000..c9c00b1 --- /dev/null +++ b/delta-py/python/delta/nn/__init__.py @@ -0,0 +1,17 @@ +import sys + +from .. import _delta as _C + +_mod = _C.nn + +from .module import Module + +for name in dir(_mod): + if not name.startswith("_") and name not in globals(): + globals()[name] = getattr(_mod, name) + +sys.modules["delta.nn.functional"] = _mod.functional + +from .linear import Linear + +__all__ = [name for name in dir(_mod) if not name.startswith("_")] diff --git a/delta-py/python/delta/nn/__init__.pyi b/delta-py/python/delta/nn/__init__.pyi new file mode 100644 index 0000000..f833345 --- /dev/null +++ b/delta-py/python/delta/nn/__init__.pyi @@ -0,0 +1,25 @@ +from __future__ import annotations +from enum import Enum +from delta import Tensor + +class Parameter: + @property + def shape(self) -> list[int]: ... + @property + def data(self) -> Tensor: ... + def __new__(cls, tensor: Tensor) -> Parameter: ... + def grad(self) -> list[float] | None: ... + def item(self) -> list[float]: ... + +def relu(x: Tensor) -> Tensor: ... +def sigmoid(x: Tensor) -> Tensor: ... +def softmax(x: Tensor, dim: int = -1) -> Tensor: ... + +class Reduction(Enum): + SUM: Reduction + MEAN: Reduction + +class MSELoss: + def __new__(cls, reduction: Reduction | None = Reduction.MEAN) -> MSELoss: ... + def measure(self, a: Tensor, b: Tensor) -> Tensor: ... + def __call__(self, a: Tensor, b: Tensor) -> Tensor: ... diff --git a/delta-py/python/delta/nn/linear.py b/delta-py/python/delta/nn/linear.py new file mode 100644 index 0000000..e597fe5 --- /dev/null +++ b/delta-py/python/delta/nn/linear.py @@ -0,0 +1,20 @@ +from delta import Tensor +from delta.nn import Module, Parameter +import delta + + +class Linear(Module): + + def __init__(self, in_features: int, out_features: int, bias: bool = True) -> None: + super().__init__() + + self.use_bias = bias + if self.use_bias: + in_features += 1 + self.weights = Parameter(delta.randn([in_features, out_features])) + + def forward(self, x: Tensor) -> Tensor: + if self.use_bias: + ones_shape = list(x.shape[:-1]) + [1] + x = delta.cat([x, delta.ones(ones_shape)], dim=-1) + return x @ self.weights.data diff --git a/delta-py/python/delta/nn/module.py b/delta-py/python/delta/nn/module.py new file mode 100644 index 0000000..89064e0 --- /dev/null +++ b/delta-py/python/delta/nn/module.py @@ -0,0 +1,29 @@ +from abc import ABC, abstractmethod + +from delta import Tensor, nn + + +class Module(ABC): + + def __init__(self): + self._modules: dict[str, "Module"] = {} + self._parameters: dict[str, "nn.Parameter"] = {} + + @abstractmethod + def forward(self, *args, **kwargs) -> ...: ... + + def __call__(self, *args, **kwargs): + return self.forward(*args, **kwargs) + + def __setattr__(self, name, value): + if isinstance(value, Module): + self._modules[name] = value + elif isinstance(value, nn.Parameter): + self._parameters[name] = value + super().__setattr__(name, value) + + def parameters(self) -> list[Tensor]: + params = list(self._parameters.values()) + for mod in self._modules.values(): + params.extend(mod.parameters()) + return params diff --git a/delta-py/python/delta/optim/__init__.py b/delta-py/python/delta/optim/__init__.py new file mode 100644 index 0000000..8150864 --- /dev/null +++ b/delta-py/python/delta/optim/__init__.py @@ -0,0 +1,9 @@ +from .. import _delta as _C + +_mod = _C.optim + +for name in dir(_mod): + if not name.startswith("_"): + globals()[name] = getattr(_mod, name) + +__all__ = [name for name in dir(_mod) if not name.startswith("_")] \ No newline at end of file diff --git a/delta-py/python/delta/optim/__init__.pyi b/delta-py/python/delta/optim/__init__.pyi new file mode 100644 index 0000000..1e92cfc --- /dev/null +++ b/delta-py/python/delta/optim/__init__.pyi @@ -0,0 +1,16 @@ +from typing import Sequence +from delta import Tensor + +class SGD: + @property + def lr(self) -> float: ... + + @property + def is_maximizing(self) -> bool: ... + + def __init__(self, parameters: Sequence[Tensor], lr: float) -> None: ... + def step(self) -> None: ... + def zero_grad(self) -> None: ... + def change_lr(self, gamma: float) -> None: ... + def maximize(self) -> None: ... + def minimize(self) -> None: ... \ No newline at end of file diff --git a/delta-py/python/delta/py.typed b/delta-py/python/delta/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/delta-py/src/lib.rs b/delta-py/src/lib.rs new file mode 100644 index 0000000..c11c7bf --- /dev/null +++ b/delta-py/src/lib.rs @@ -0,0 +1,18 @@ +mod linalg; +mod nn; +mod operations; +mod optim; +mod tensor; + +use pyo3::prelude::*; + +#[pymodule] +#[pyo3(name = "_delta")] +fn _delta(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { + linalg::register_submodule(_py, m)?; + nn::register_submodule(_py, m)?; + operations::register_submodule(_py, m)?; + optim::register_submodule(_py, m)?; + tensor::register_submodule(_py, m)?; + Ok(()) +} diff --git a/delta-py/src/linalg.rs b/delta-py/src/linalg.rs new file mode 100644 index 0000000..e6bf632 --- /dev/null +++ b/delta-py/src/linalg.rs @@ -0,0 +1,23 @@ +use pyo3::prelude::*; + +use crate::tensor::PyTensor; + +pub fn register_submodule(_: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + parent.add_function(wrap_pyfunction!(matmul, parent)?)?; + parent.add_function(wrap_pyfunction!(cross, parent)?)?; + Ok(()) +} + +#[pyfunction] +pub fn matmul(a: PyRef<'_, PyTensor>, b: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: delta::linalg::matmul(a.inner.clone(), b.inner.clone()), + }) +} + +#[pyfunction] +pub fn cross(a: PyRef<'_, PyTensor>, b: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: delta::linalg::cross(a.inner.clone(), b.inner.clone()), + }) +} diff --git a/delta-py/src/nn.rs b/delta-py/src/nn.rs new file mode 100644 index 0000000..5a33036 --- /dev/null +++ b/delta-py/src/nn.rs @@ -0,0 +1,16 @@ +pub mod criterions; +pub mod functional; +pub mod parameter; + +use pyo3::prelude::*; + +use crate::nn::parameter::PyParameter; + +pub fn register_submodule(py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + let nn = PyModule::new(py, "_delta.nn")?; + nn.add_class::()?; + functional::register_submodule(py, &nn)?; + criterions::register_submodule(py, &nn)?; + parent.add_submodule(&nn)?; + Ok(()) +} diff --git a/delta-py/src/nn/criterions.rs b/delta-py/src/nn/criterions.rs new file mode 100644 index 0000000..959ca89 --- /dev/null +++ b/delta-py/src/nn/criterions.rs @@ -0,0 +1,65 @@ +use pyo3::prelude::*; + +use crate::tensor::PyTensor; +use delta::nn::{MSELoss, criterions::Reduction}; + +pub fn register_submodule(_py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + parent.add_class::()?; + parent.add_class::()?; + Ok(()) +} + +#[pyclass(module = "delta.nn", name = "Reduction", eq, eq_int, from_py_object)] +#[derive(Clone, Copy, PartialEq)] +pub enum PyReduction { + SUM = 0, + MEAN = 1, +} + +impl From for Reduction { + fn from(value: PyReduction) -> Self { + match value { + PyReduction::SUM => Reduction::SUM, + PyReduction::MEAN => Reduction::MEAN, + } + } +} + +#[pyclass( + name = "MSELoss", + module = "delta.nn", + unsendable, + skip_from_py_object +)] +#[derive(Clone)] +pub struct PyMSELoss { + pub(crate) inner: MSELoss, +} + +#[pymethods] +impl PyMSELoss { + #[new] + #[pyo3(signature = (reduction=PyReduction::MEAN))] + fn new(reduction: Option) -> Self { + Self { + inner: MSELoss::new(reduction.map(Into::into)), + } + } + + #[pyo3(name = "measure")] + fn measure(&self, a: PyRef<'_, PyTensor>, b: PyRef<'_, PyTensor>) -> PyTensor { + PyTensor { + inner: self.inner.measure(a.inner.clone(), b.inner.clone()), + } + } + + fn __call__(&self, a: PyRef<'_, PyTensor>, b: PyRef<'_, PyTensor>) -> PyTensor { + PyTensor { + inner: self.inner.measure(a.inner.clone(), b.inner.clone()), + } + } + + fn __repr__(&self) -> String { + "MSELoss()".to_string() + } +} diff --git a/delta-py/src/nn/functional.rs b/delta-py/src/nn/functional.rs new file mode 100644 index 0000000..b37cb4d --- /dev/null +++ b/delta-py/src/nn/functional.rs @@ -0,0 +1,36 @@ +use pyo3::prelude::*; + +use delta::nn::functional as F; + +use crate::tensor::PyTensor; + +pub fn register_submodule(py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + let functional = PyModule::new(py, "delta.nn.functional")?; + functional.add_function(wrap_pyfunction!(relu, &functional)?)?; + functional.add_function(wrap_pyfunction!(sigmoid, &functional)?)?; + functional.add_function(wrap_pyfunction!(softmax, &functional)?)?; + parent.add_submodule(&functional)?; + Ok(()) +} + +#[pyfunction] +pub fn relu(x: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: F::relu(x.inner.clone()), + }) +} + +#[pyfunction] +pub fn sigmoid(x: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: F::sigmoid(x.inner.clone()), + }) +} + +#[pyfunction] +#[pyo3(signature = (x, dim=-1))] +pub fn softmax(x: PyRef<'_, PyTensor>, dim: isize) -> PyResult { + Ok(PyTensor { + inner: F::softmax(x.inner.clone(), dim), + }) +} diff --git a/delta-py/src/nn/parameter.rs b/delta-py/src/nn/parameter.rs new file mode 100644 index 0000000..2132997 --- /dev/null +++ b/delta-py/src/nn/parameter.rs @@ -0,0 +1,46 @@ +use delta::nn::Parameter; +use pyo3::prelude::*; + +use crate::tensor::PyTensor; + +#[pyclass( + name = "Parameter", + module = "delta.nn", + unsendable, + skip_from_py_object +)] +#[derive(Clone)] +pub struct PyParameter { + pub(crate) inner: Parameter, +} + +#[pymethods] +impl PyParameter { + #[new] + fn new(tensor: PyRef<'_, PyTensor>) -> Self { + Self { + inner: Parameter(tensor.inner.clone()), + } + } + + // Delegate tensor methods so it behaves like a Tensor in Python + fn grad(&self) -> Option> { + self.inner.0.grad() + } + + fn item(&self) -> Vec { + self.inner.0.item() + } + + #[getter] + fn shape(&self) -> Vec { + self.inner.0.shape.clone() + } + + #[getter] + fn data(&self) -> PyTensor { + PyTensor { + inner: self.inner.0.clone(), + } + } +} diff --git a/delta-py/src/operations.rs b/delta-py/src/operations.rs new file mode 100644 index 0000000..bbdc550 --- /dev/null +++ b/delta-py/src/operations.rs @@ -0,0 +1,36 @@ +use delta::Tensor; +use pyo3::prelude::*; + +use crate::tensor::PyTensor; + +pub fn register_submodule(_: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + parent.add_function(wrap_pyfunction!(sum, parent)?)?; + parent.add_function(wrap_pyfunction!(mean, parent)?)?; + parent.add_function(wrap_pyfunction!(cat, parent)?)?; + Ok(()) +} + +#[pyfunction] +#[pyo3(signature = (input, dim=None, keepdim=false))] +pub fn sum(input: PyRef<'_, PyTensor>, dim: Option, keepdim: bool) -> PyTensor { + PyTensor { + inner: input.inner.sum(dim, keepdim), + } +} + +#[pyfunction] +#[pyo3(signature = (input, dim=None, keepdim=false))] +pub fn mean(input: PyRef<'_, PyTensor>, dim: Option, keepdim: bool) -> PyTensor { + PyTensor { + inner: input.inner.mean(dim, keepdim), + } +} + +#[pyfunction] +#[pyo3(signature = (tensors, dim=0))] +pub fn cat(tensors: Vec>, dim: isize) -> PyTensor { + let rust_tensors: Vec = tensors.iter().map(|t| t.inner.clone()).collect(); + PyTensor { + inner: Tensor::cat(&rust_tensors, dim), + } +} diff --git a/delta-py/src/optim.rs b/delta-py/src/optim.rs new file mode 100644 index 0000000..1bd8fac --- /dev/null +++ b/delta-py/src/optim.rs @@ -0,0 +1,12 @@ +pub mod sgd; + +use pyo3::prelude::*; + +use crate::optim::sgd::PySGD; + +pub fn register_submodule(py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + let optim = PyModule::new(py, "_delta.optim")?; + optim.add_class::()?; + parent.add_submodule(&optim)?; + Ok(()) +} diff --git a/delta-py/src/optim/sgd.rs b/delta-py/src/optim/sgd.rs new file mode 100644 index 0000000..6855489 --- /dev/null +++ b/delta-py/src/optim/sgd.rs @@ -0,0 +1,53 @@ +use pyo3::prelude::*; + +use delta::optim::Optim; +use delta::optim::sgd::SGD; + +use crate::nn::parameter::PyParameter; + +#[pyclass(name = "SGD", module = "delta.optim", unsendable)] +pub struct PySGD { + pub(crate) inner: SGD, +} + +#[pymethods] +impl PySGD { + #[new] + #[pyo3(signature = (parameters, lr))] + fn new(parameters: Vec>, lr: f64) -> Self { + let params = parameters.into_iter().map(|t| t.inner.clone()).collect(); + + Self { + inner: SGD::new(params, lr), + } + } + #[getter] + fn lr(&self) -> f64 { + self.inner.lr() + } + + #[getter] + fn is_maximize(&self) -> bool { + self.inner.is_maximize() + } + + fn step(&self) { + self.inner.step(); + } + + fn zero_grad(&self) { + self.inner.zero_grad(); + } + + fn change_lr(&mut self, gamma: f64) { + self.inner.change_lr(gamma); + } + + fn maximize(&mut self) { + self.inner.maximize(); + } + + fn minimize(&mut self) { + self.inner.minimize(); + } +} diff --git a/delta-py/src/tensor.rs b/delta-py/src/tensor.rs new file mode 100644 index 0000000..cc8cc22 --- /dev/null +++ b/delta-py/src/tensor.rs @@ -0,0 +1,402 @@ +use pyo3::exceptions::{PyTypeError, PyValueError}; +use pyo3::prelude::*; +use pyo3::types::{PyList, PyTuple}; + +use delta::Tensor; + +pub fn register_submodule(_: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { + parent.add_class::()?; + parent.add_function(wrap_pyfunction!(tensor, parent)?)?; + parent.add_function(wrap_pyfunction!(randn, parent)?)?; + parent.add_function(wrap_pyfunction!(zeros, parent)?)?; + parent.add_function(wrap_pyfunction!(zeros_like, parent)?)?; + parent.add_function(wrap_pyfunction!(ones, parent)?)?; + parent.add_function(wrap_pyfunction!(ones_like, parent)?)?; + Ok(()) +} + +#[pyclass(name = "Tensor", module = "delta", unsendable, skip_from_py_object)] +#[derive(Clone)] +pub struct PyTensor { + pub(crate) inner: Tensor, +} + +#[pymethods] +impl PyTensor { + #[staticmethod] + fn zeros(shape: Vec) -> Self { + Self { + inner: Tensor::zeros(&shape), + } + } + + #[allow(non_snake_case)] + #[getter] + fn T(&self) -> PyResult { + Ok(Self { + inner: self.inner.t(), + }) + } + + #[staticmethod] + fn zeros_like(a: PyRef<'_, PyTensor>) -> Self { + let shape = a.shape(); + Self { + inner: Tensor::zeros(&shape), + } + } + + #[staticmethod] + fn ones(shape: Vec) -> Self { + Self { + inner: Tensor::ones(&shape), + } + } + + #[staticmethod] + fn ones_like(a: PyRef<'_, PyTensor>) -> Self { + let shape = a.shape(); + Self { + inner: Tensor::ones(&shape), + } + } + + #[staticmethod] + fn randn(shape: Vec) -> Self { + Self { + inner: Tensor::randn(&shape), + } + } + + #[getter] + fn shape(&self) -> Vec { + self.inner.shape.clone() + } + + #[getter] + fn ndim(&self) -> usize { + self.inner.shape.len() + } + + #[getter] + fn length(&self) -> usize { + self.inner.length() + } + + fn storage(&self) -> Vec { + self.inner.storage() + } + + fn item(&self) -> Vec { + self.inner.item() + } + + #[getter] + fn grad(&self) -> PyResult>> { + Ok(self.inner.grad()) + } + + #[pyo3(signature = (*shape))] + fn reshape(&self, shape: &Bound<'_, PyTuple>) -> PyResult { + let obj = if shape.len() == 1 { + shape.get_item(0)? + } else { + shape.as_any().clone() + }; + + let shape = parse_shape(&obj, self.inner.length())?; + + Ok(Self { + inner: self.inner.reshape(&shape), + }) + } + + #[pyo3(signature = (dim=None))] + fn squeeze(&self, dim: Option>) -> PyResult { + let dim = dim.unwrap_or_default(); + Ok(Self { + inner: self.inner.squeeze(&dim), + }) + } + + fn __repr__(&self) -> String { + format!("{}", self.inner) + } + + fn backward(&self) -> () { + self.inner.backward(); + } + + #[staticmethod] + #[pyo3(signature = (tensors, dim=0))] + fn cat(tensors: Vec>, dim: Option) -> PyResult { + let rust_tensors: Vec = tensors.iter().map(|t| t.inner.clone()).collect(); + let dim = dim.unwrap_or_default(); + Ok(Self { + inner: Tensor::cat(&rust_tensors, dim), + }) + } + + #[pyo3(signature = (dim=None, keepdim=false))] + fn sum(&self, dim: Option, keepdim: bool) -> Self { + Self { + inner: self.inner.sum(dim, keepdim), + } + } + + #[pyo3(signature = (dim=None, keepdim=false))] + fn mean(&self, dim: Option, keepdim: bool) -> Self { + Self { + inner: self.inner.mean(dim, keepdim), + } + } + + fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if let Ok(value) = other.extract::() { + Ok(Self { + inner: self.inner.clone() + value, + }) + } else if let Ok(other_tensor) = other.extract::>() { + Ok(Self { + inner: self.inner.clone() + other_tensor.inner.clone(), + }) + } else { + Err(PyTypeError::new_err( + "unsupported operand type(s) for +: 'delta.Tensor' and given type", + )) + } + } + + fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult { + self.__add__(other) + } + + fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if let Ok(value) = other.extract::() { + Ok(Self { + inner: self.inner.clone() + value, + }) + } else if let Ok(other_tensor) = other.extract::>() { + Ok(Self { + inner: self.inner.clone() - other_tensor.inner.clone(), + }) + } else { + Err(PyTypeError::new_err( + "unsupported operand type(s) for +: 'delta.Tensor' and given type", + )) + } + } + + fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult { + self.__sub__(other) + } + + fn __mul__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if let Ok(value) = other.extract::() { + Ok(Self { + inner: self.inner.clone() + value, + }) + } else if let Ok(other_tensor) = other.extract::>() { + Ok(Self { + inner: self.inner.clone() * other_tensor.inner.clone(), + }) + } else { + Err(PyTypeError::new_err( + "unsupported operand type(s) for +: 'delta.Tensor' and given type", + )) + } + } + + fn __rmul__(&self, other: &Bound<'_, PyAny>) -> PyResult { + self.__mul__(other) + } + + fn __truediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { + if let Ok(value) = other.extract::() { + Ok(Self { + inner: self.inner.clone() + value, + }) + } else if let Ok(other_tensor) = other.extract::>() { + Ok(Self { + inner: self.inner.clone() / other_tensor.inner.clone(), + }) + } else { + Err(PyTypeError::new_err( + "unsupported operand type(s) for +: 'delta.Tensor' and given type", + )) + } + } + + fn __rtruediv__(&self, other: &Bound<'_, PyAny>) -> PyResult { + self.__truediv__(other) + } + + fn __neg__(&self) -> Self { + Self { + inner: -self.inner.clone(), + } + } + + fn __matmul__(&self, other: &PyTensor) -> PyTensor { + PyTensor { + inner: delta::linalg::matmul(self.inner.clone(), other.inner.clone()), + } + } +} + +#[pyfunction] +pub fn tensor(obj: &Bound<'_, PyAny>) -> PyResult { + let (data, shape) = extract_nested(obj)?; + Ok(PyTensor { + inner: Tensor::tensor(&data, &shape), + }) +} + +#[pyfunction] +pub fn randn(shape: Vec) -> PyResult { + Ok(PyTensor { + inner: Tensor::randn(&shape), + }) +} + +#[pyfunction] +pub fn zeros(shape: Vec) -> PyResult { + Ok(PyTensor { + inner: Tensor::zeros(&shape), + }) +} + +#[pyfunction] +pub fn zeros_like(tensor: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: Tensor::zeros_like(&tensor.inner), + }) +} + +#[pyfunction] +pub fn ones(shape: Vec) -> PyResult { + Ok(PyTensor { + inner: Tensor::ones(&shape), + }) +} + +#[pyfunction] +pub fn ones_like(tensor: PyRef<'_, PyTensor>) -> PyResult { + Ok(PyTensor { + inner: Tensor::ones_like(&tensor.inner), + }) +} + +fn extract_nested(obj: &Bound<'_, PyAny>) -> PyResult<(Vec, Vec)> { + if let Ok(value) = obj.extract::() { + return Ok((vec![value], vec![])); + } + + if let Ok(list) = obj.cast::() { + return extract_sequence(list.iter().collect()); + } + + if let Ok(tuple) = obj.cast::() { + return extract_sequence(tuple.iter().collect()); + } + + Err(PyTypeError::new_err( + "tensor() expects a number or a nested list/tuple of numbers", + )) +} + +fn extract_sequence(items: Vec>) -> PyResult<(Vec, Vec)> { + let len = items.len(); + + if len == 0 { + return Ok((Vec::new(), vec![0])); + } + + let mut flat = Vec::new(); + let mut inner_shape: Option> = None; + + for item in items { + let (child_flat, child_shape) = extract_nested(&item)?; + + match &inner_shape { + None => inner_shape = Some(child_shape.clone()), + Some(expected) if *expected == child_shape => {} + Some(expected) => { + return Err(PyValueError::new_err(format!( + "ragged nested sequence: expected inner shape {:?}, got {:?}", + expected, child_shape + ))); + } + } + + flat.extend(child_flat); + } + + let mut shape = vec![len]; + if let Some(child_shape) = inner_shape { + shape.extend(child_shape); + } + + Ok((flat, shape)) +} + +fn parse_shape(obj: &Bound<'_, PyAny>, total_len: usize) -> PyResult> { + let dims: Vec = if let Ok(v) = obj.extract::() { + vec![v] + } else if let Ok(v) = obj.extract::>() { + v + } else if let Ok(tuple) = obj.cast::() { + tuple.extract::>()? + } else if let Ok(list) = obj.cast::() { + list.extract::>()? + } else { + return Err(PyValueError::new_err( + "reshape expects an int, tuple, or list of ints", + )); + }; + + if dims.is_empty() { + return Err(PyValueError::new_err("reshape shape cannot be empty")); + } + + let mut out = Vec::with_capacity(dims.len()); + let mut infer_idx: Option = None; + let mut known_product = 1usize; + + for (i, &d) in dims.iter().enumerate() { + if d == -1 { + if infer_idx.is_some() { + return Err(PyValueError::new_err("only one dimension can be inferred")); + } + infer_idx = Some(i); + out.push(0); + } else if d < 0 { + return Err(PyValueError::new_err( + "reshape dimensions must be >= 0, except -1", + )); + } else { + let u = d as usize; + known_product = known_product + .checked_mul(u) + .ok_or_else(|| PyValueError::new_err("shape product overflow"))?; + out.push(u); + } + } + + if let Some(i) = infer_idx { + if known_product == 0 || !total_len.is_multiple_of(known_product) { + return Err(PyValueError::new_err(format!( + "cannot infer shape {:?} for tensor of length {}", + dims, total_len + ))); + } + out[i] = total_len / known_product; + } else if known_product != total_len { + return Err(PyValueError::new_err(format!( + "cannot reshape tensor of length {} into shape {:?}", + total_len, dims + ))); + } + + Ok(out) +} diff --git a/delta-py/uv.lock b/delta-py/uv.lock new file mode 100644 index 0000000..3c0f3c4 --- /dev/null +++ b/delta-py/uv.lock @@ -0,0 +1,611 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/be/8bd693a0b9d53d48c8978fa5d889e06f3b5b03e45fd1ea1e78267b4887cb/debugpy-1.8.20-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:157e96ffb7f80b3ad36d808646198c90acb46fdcfd8bb1999838f0b6f2b59c64", size = 2099192, upload-time = "2026-01-29T23:03:29.707Z" }, + { url = "https://files.pythonhosted.org/packages/77/1b/85326d07432086a06361d493d2743edd0c4fc2ef62162be7f8618441ac37/debugpy-1.8.20-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:c1178ae571aff42e61801a38b007af504ec8e05fde1c5c12e5a7efef21009642", size = 3088568, upload-time = "2026-01-29T23:03:31.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/60/3e08462ee3eccd10998853eb35947c416e446bfe2bc37dbb886b9044586c/debugpy-1.8.20-cp310-cp310-win32.whl", hash = "sha256:c29dd9d656c0fbd77906a6e6a82ae4881514aa3294b94c903ff99303e789b4a2", size = 5284399, upload-time = "2026-01-29T23:03:33.678Z" }, + { url = "https://files.pythonhosted.org/packages/72/43/09d49106e770fe558ced5e80df2e3c2ebee10e576eda155dcc5670473663/debugpy-1.8.20-cp310-cp310-win_amd64.whl", hash = "sha256:3ca85463f63b5dd0aa7aaa933d97cbc47c174896dcae8431695872969f981893", size = 5316388, upload-time = "2026-01-29T23:03:35.095Z" }, + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "delta" +source = { editable = "." } +dependencies = [ + { name = "ipykernel" }, +] + +[package.metadata] +requires-dist = [{ name = "ipykernel", specifier = ">=7.2.0" }] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.13.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version >= '3.11'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/c4/87cda5842cf5c31837c06ddb588e11c3c35d8ece89b7a0108c06b8c9b00a/ipython-9.13.0.tar.gz", hash = "sha256:7e834b6afc99f020e3f05966ced34792f40267d64cb1ea9043886dab0dde5967", size = 4430549, upload-time = "2026-04-24T12:24:55.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/86/3060e8029b7cc505cce9a0137431dda81d0a3fde93a8f0f50ee0bf37a795/ipython-9.13.0-py3-none-any.whl", hash = "sha256:57f9d4639e20818d328d287c7b549af3d05f12486ea8f2e7f73e52a36ec4d201", size = 627274, upload-time = "2026-04-24T12:24:53.038Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "parso" +version = "0.8.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.9.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/b9/52aa9ec2867528b54f1e60846728d8b4d84726630874fee3a91e66c7df81/pyzmq-27.1.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:508e23ec9bc44c0005c4946ea013d9317ae00ac67778bd47519fdf5a0e930ff4", size = 1329850, upload-time = "2025-09-08T23:07:26.274Z" }, + { url = "https://files.pythonhosted.org/packages/99/64/5653e7b7425b169f994835a2b2abf9486264401fdef18df91ddae47ce2cc/pyzmq-27.1.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:507b6f430bdcf0ee48c0d30e734ea89ce5567fd7b8a0f0044a369c176aa44556", size = 906380, upload-time = "2025-09-08T23:07:29.78Z" }, + { url = "https://files.pythonhosted.org/packages/73/78/7d713284dbe022f6440e391bd1f3c48d9185673878034cfb3939cdf333b2/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf7b38f9fd7b81cb6d9391b2946382c8237fd814075c6aa9c3b746d53076023b", size = 666421, upload-time = "2025-09-08T23:07:31.263Z" }, + { url = "https://files.pythonhosted.org/packages/30/76/8f099f9d6482450428b17c4d6b241281af7ce6a9de8149ca8c1c649f6792/pyzmq-27.1.0-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03ff0b279b40d687691a6217c12242ee71f0fba28bf8626ff50e3ef0f4410e1e", size = 854149, upload-time = "2025-09-08T23:07:33.17Z" }, + { url = "https://files.pythonhosted.org/packages/59/f0/37fbfff06c68016019043897e4c969ceab18bde46cd2aca89821fcf4fb2e/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:677e744fee605753eac48198b15a2124016c009a11056f93807000ab11ce6526", size = 1655070, upload-time = "2025-09-08T23:07:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/7254be73f7a8edc3587609554fcaa7bfd30649bf89cd260e4487ca70fdaa/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd2fec2b13137416a1c5648b7009499bcc8fea78154cd888855fa32514f3dad1", size = 2033441, upload-time = "2025-09-08T23:07:37.432Z" }, + { url = "https://files.pythonhosted.org/packages/22/dc/49f2be26c6f86f347e796a4d99b19167fc94503f0af3fd010ad262158822/pyzmq-27.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:08e90bb4b57603b84eab1d0ca05b3bbb10f60c1839dc471fc1c9e1507bef3386", size = 1891529, upload-time = "2025-09-08T23:07:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3e/154fb963ae25be70c0064ce97776c937ecc7d8b0259f22858154a9999769/pyzmq-27.1.0-cp310-cp310-win32.whl", hash = "sha256:a5b42d7a0658b515319148875fcb782bbf118dd41c671b62dae33666c2213bda", size = 567276, upload-time = "2025-09-08T23:07:40.695Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/f4ab56c8c595abcb26b2be5fd9fa9e6899c1e5ad54964e93ae8bb35482be/pyzmq-27.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0bb87227430ee3aefcc0ade2088100e528d5d3298a0a715a64f3d04c60ba02f", size = 632208, upload-time = "2025-09-08T23:07:42.298Z" }, + { url = "https://files.pythonhosted.org/packages/3b/e3/be2cc7ab8332bdac0522fdb64c17b1b6241a795bee02e0196636ec5beb79/pyzmq-27.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:9a916f76c2ab8d045b19f2286851a38e9ac94ea91faf65bd64735924522a8b32", size = 559766, upload-time = "2025-09-08T23:07:43.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/f3/81/a65e71c1552f74dec9dff91d95bafb6e0d33338a8dfefbc88aa562a20c92/pyzmq-27.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c17e03cbc9312bee223864f1a2b13a99522e0dc9f7c5df0177cd45210ac286e6", size = 836266, upload-time = "2025-09-08T23:09:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/0202ca350f4f2b69faa95c6d931e3c05c3a397c184cacb84cb4f8f42f287/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f328d01128373cb6763823b2b4e7f73bdf767834268c565151eacb3b7a392f90", size = 800206, upload-time = "2025-09-08T23:09:41.902Z" }, + { url = "https://files.pythonhosted.org/packages/47/42/1ff831fa87fe8f0a840ddb399054ca0009605d820e2b44ea43114f5459f4/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1790386614232e1b3a40a958454bdd42c6d1811837b15ddbb052a032a43f62", size = 567747, upload-time = "2025-09-08T23:09:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/5c4d6807434751e3f21231bee98109aa57b9b9b55e058e450d0aef59b70f/pyzmq-27.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:448f9cb54eb0cee4732b46584f2710c8bc178b0e5371d9e4fc8125201e413a74", size = 747371, upload-time = "2025-09-08T23:09:45.575Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/78ce193dbf03567eb8c0dc30e3df2b9e56f12a670bf7eb20f9fb532c7e8a/pyzmq-27.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:05b12f2d32112bf8c95ef2e74ec4f1d4beb01f8b5e703b38537f8849f92cb9ba", size = 544862, upload-time = "2025-09-08T23:09:47.448Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, + { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, + { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, +] diff --git a/delta/Cargo.lock b/delta/Cargo.lock new file mode 100644 index 0000000..915ebf9 --- /dev/null +++ b/delta/Cargo.lock @@ -0,0 +1,417 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "athena" +version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures", + "rand_core", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/delta/Cargo.toml b/delta/Cargo.toml new file mode 100644 index 0000000..4210a8d --- /dev/null +++ b/delta/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "delta" +# versions +version = "0.1.0" +edition = "2024" +rust-version = "1.95.0" +# info +authors = ["commanderxa"] +readme = "README.md" +repository = "https://github.com/CommanderXA/delta" +license-file = "LICENSE.txt" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.10.1" +delta-macros = { path = "../delta-macros" } + +[lib] +name = "delta" +path = "src/lib.rs" +crate-type = ["rlib"] diff --git a/delta/examples/linear.rs b/delta/examples/linear.rs new file mode 100644 index 0000000..a0b669c --- /dev/null +++ b/delta/examples/linear.rs @@ -0,0 +1,16 @@ +use delta::{ + Tensor, ivalue, + nn::{Linear, Module, functional as F}, +}; + +fn main() { + let linear = Linear::new(20, 10, true); + let x = Tensor::randn(&[2, 20]); + println!("Weights: {}", linear.weights.0); + println!("IN:\n{x}"); + let (args, kwargs) = ivalue![[x]]; + let out = linear.forward(args, kwargs).unwrap_tensor(); + println!("OUT:\n{out}"); + let out = F::sigmoid(out); + println!("OUT:\n{out}"); +} diff --git a/examples/matmul.rs b/delta/examples/matmul.rs similarity index 87% rename from examples/matmul.rs rename to delta/examples/matmul.rs index 524eea8..e469070 100644 --- a/examples/matmul.rs +++ b/delta/examples/matmul.rs @@ -1,4 +1,4 @@ -use athena::{linalg, Tensor}; +use delta::{linalg, Tensor}; fn main() { let a = Tensor::randn(&[2, 3, 2]); diff --git a/examples/mlp.rs b/delta/examples/mlp.rs similarity index 79% rename from examples/mlp.rs rename to delta/examples/mlp.rs index cb92e8d..65893cf 100644 --- a/examples/mlp.rs +++ b/delta/examples/mlp.rs @@ -1,7 +1,9 @@ -use athena::{ - Tensor, - module::{Forward, Module}, - nn::{Linear, MSELoss, functional as F}, +use std::collections::HashMap; + +use delta::{ + Tensor, ivalue, + ivalue::IValue, + nn::{self, Linear, MSELoss, Module, functional as F}, optim::{Optim, SGD}, }; @@ -51,7 +53,8 @@ fn main() { let x = x.clone().unsqueeze(0); let y = y.clone(); - let out = mlp.forward(x).squeeze(&[0]); + let (args, kwargs) = ivalue![[x]]; + let out = mlp.forward(args, kwargs).unwrap_tensor().squeeze(&[0]); let loss = criterion.measure(out, y); loss.backward(); @@ -64,7 +67,8 @@ fn main() { for (x, y) in test_data.iter().zip(test_targets.clone()) { let x = x.clone().unsqueeze(0); - let out = mlp.forward(x.clone()).squeeze(&[0]); + let (args, kwargs) = ivalue![[x.clone()]]; + let out = mlp.forward(args, kwargs).unwrap_tensor().squeeze(&[0]); let loss = criterion.measure(out.clone(), y.clone()); println!( @@ -95,15 +99,13 @@ impl Module for MLP { "MLP".to_owned() } - fn parameters(&self) -> Vec { + fn parameters(&self) -> Vec { let parameters = self.linear.parameters(); parameters } -} -impl Forward for MLP { - fn forward(&self, x: Tensor) -> Tensor { - let x = self.linear.forward(x); - F::sigmoid(x) + fn forward(&self, args: Vec, kwargs: HashMap) -> IValue { + let x = self.linear.forward(args, kwargs).unwrap_tensor(); + IValue::Tensor(F::sigmoid(x)) } } diff --git a/examples/mlp2.rs b/delta/examples/mlp2.rs similarity index 81% rename from examples/mlp2.rs rename to delta/examples/mlp2.rs index 66e5c42..426042e 100644 --- a/examples/mlp2.rs +++ b/delta/examples/mlp2.rs @@ -1,8 +1,10 @@ -use athena::{ - module::{Forward, Module}, - nn::{Linear, MSELoss}, +use std::collections::HashMap; + +use delta::{ + Tensor, ivalue, + ivalue::IValue, + nn::{self, Linear, MSELoss, Module}, optim::{Optim, SGD}, - Tensor, }; fn main() { @@ -52,7 +54,8 @@ fn main() { let x = x.clone(); let y = y.clone(); - let out = mlp.forward(x.clone()); + let (args, kwargs) = ivalue![[x.clone()]]; + let out = mlp.forward(args, kwargs).unwrap_tensor(); let loss = criterion.measure(out, y); loss.backward(); @@ -70,14 +73,16 @@ fn main() { let mut test_res = vec![]; for (x, y) in data.iter().zip(targets.clone()) { - let out = mlp.forward(x.clone()); + let (args, kwargs) = ivalue![[x.clone()]]; + let out = mlp.forward(args, kwargs).unwrap_tensor(); let loss = criterion.measure(out.clone(), y.clone()); loss.backward(); training_res.push((out.item()[0], y.item()[0])) } for (x, y) in test_data.iter().zip(test_targets.clone()) { - let out = mlp.forward(x.clone()); + let (args, kwargs) = ivalue![[x.clone()]]; + let out = mlp.forward(args, kwargs).unwrap_tensor(); let loss = criterion.measure(out.clone(), y.clone()); loss.backward(); test_res.push((out.item()[0], y.item()[0])) @@ -105,15 +110,12 @@ impl Module for MLP { "MLP".to_owned() } - fn parameters(&self) -> Vec { + fn parameters(&self) -> Vec { let parameters = self.linear1.parameters(); parameters } -} -impl Forward for MLP { - fn forward(&self, x: Tensor) -> Tensor { - let x = self.linear1.forward(x); - x + fn forward(&self, args: Vec, kwargs: HashMap) -> IValue { + self.linear1.forward(args, kwargs) } } diff --git a/examples/softmax.rs b/delta/examples/softmax.rs similarity index 80% rename from examples/softmax.rs rename to delta/examples/softmax.rs index 5ba956d..5cf34fc 100644 --- a/examples/softmax.rs +++ b/delta/examples/softmax.rs @@ -1,4 +1,4 @@ -use athena::{nn::functional as F, Tensor}; +use delta::{nn::functional as F, Tensor}; fn main() { let x = Tensor::tensor(&[0.24, 0.1, 0.5, 0.8, 1.2, 2.2], &[1, 2, 3]); diff --git a/examples/tensor.rs b/delta/examples/tensor.rs similarity index 92% rename from examples/tensor.rs rename to delta/examples/tensor.rs index 55fc5c4..159cd63 100644 --- a/examples/tensor.rs +++ b/delta/examples/tensor.rs @@ -1,4 +1,4 @@ -use athena::{Tensor, tensor}; +use delta::{Tensor, tensor}; fn main() { let a = tensor!([1., 2., 3.]); diff --git a/src/backward.rs b/delta/src/backward.rs similarity index 69% rename from src/backward.rs rename to delta/src/backward.rs index 172976a..5e4695c 100644 --- a/src/backward.rs +++ b/delta/src/backward.rs @@ -1,4 +1,4 @@ -use crate::{linalg, op::Op, tensor_data::TensorData, Tensor}; +use crate::{Tensor, linalg, op::Op, tensor_data::TensorData}; /// Backward trait for backpropagation operation. pub trait Backward { @@ -47,6 +47,69 @@ impl Backward for Op { t._prev[1].add_to_grad(l_item.iter().zip(grad).map(|(a, b)| a * b).collect()); } + Op::Sum { dim, keepdim } => { + let t = tensor.inner.borrow(); + let grad = t.grad.clone().unwrap(); + let prev = &t._prev[0]; + let prev_shape = prev.shape.clone(); + + let back = match dim { + None => { + vec![grad[0]; prev.length()] + } + Some(dim) => { + let mut grad_tensor = + Tensor::tensor(&grad, &tensor.shape).requires_grad(false); + + if !*keepdim { + let mut shape = prev_shape.clone(); + shape[*dim] = 1; + grad_tensor = grad_tensor.reshape(&shape); + } + + grad_tensor.expand(&prev_shape).item() + } + }; + + prev.add_to_grad(back); + } + + Op::Mean { + dim, + keepdim, + count, + } => { + let t = tensor.inner.borrow(); + let grad = t.grad.clone().unwrap(); + let prev = &t._prev[0]; + let prev_shape = prev.shape.clone(); + + let mut back = match dim { + None => { + vec![grad[0]; prev.length()] + } + Some(dim) => { + let mut grad_tensor = + Tensor::tensor(&grad, &tensor.shape).requires_grad(false); + + if !*keepdim { + let mut shape = prev_shape.clone(); + shape[*dim] = 1; + grad_tensor = grad_tensor.reshape(&shape); + } + + grad_tensor.expand(&prev_shape).item() + } + }; + + let scale = 1.0 / *count as f64; + for g in back.iter_mut() { + *g *= scale; + } + + prev.add_to_grad(back); + } + // Power backward // d(x^n)/dx * grad = n * x^(n-1) * grad Op::Pow(n) => { @@ -126,9 +189,9 @@ impl Backward for Op { for i in 0..n { for j in 0..n { if i == j { - jacobian[i * j] = s[i] * (1.0 - s[i]); + jacobian[i * n + j] = s[i] * (1.0 - s[i]); } else { - jacobian[i * j] = -s[i] * s[j]; + jacobian[i * n + j] = -s[i] * s[j]; } } } diff --git a/src/data.rs b/delta/src/data.rs similarity index 100% rename from src/data.rs rename to delta/src/data.rs diff --git a/src/data/dataloader.rs b/delta/src/data/dataloader.rs similarity index 100% rename from src/data/dataloader.rs rename to delta/src/data/dataloader.rs diff --git a/src/data/dataset.rs b/delta/src/data/dataset.rs similarity index 100% rename from src/data/dataset.rs rename to delta/src/data/dataset.rs diff --git a/src/data/sample.rs b/delta/src/data/sample.rs similarity index 100% rename from src/data/sample.rs rename to delta/src/data/sample.rs diff --git a/delta/src/ivalue.rs b/delta/src/ivalue.rs new file mode 100644 index 0000000..66671f5 --- /dev/null +++ b/delta/src/ivalue.rs @@ -0,0 +1,223 @@ +use crate::Tensor; + +#[derive(Debug)] +pub enum IValue { + Tensor(Tensor), + TensorList(Vec), + Tuple(Vec), + Int(i64), + Float(f64), + Bool(bool), + Str(String), + None, +} + +impl IValue { + /// Unwraps the IValue as a Tensor, panics if it is not a Tensor variant + pub fn unwrap_tensor(self) -> Tensor { + match self { + IValue::Tensor(t) => t, + other => panic!("expected Tensor, got {:?}", other), + } + } + + /// Unwraps the IValue as a tuple (Vec), panics otherwise + pub fn unwrap_tuple(self) -> Vec { + match self { + IValue::Tuple(v) => v, + other => panic!("expected Tuple, got {:?}", other), + } + } + + /// Unwraps the Tensor as a tensor list (Vec), panics otherwise + pub fn unwrap_list(self) -> Vec { + match self { + IValue::TensorList(v) => v, + other => panic!("expected Tuple, got {:?}", other), + } + } + + /// Unwraps the IValue as i64, panics otherwise + pub fn unwrap_int(self) -> i64 { + match self { + IValue::Int(i) => i, + other => panic!("expected Int, got {:?}", other), + } + } + + /// Unwraps the IValue as f64, panics otherwise + pub fn unwrap_float(self) -> f64 { + match self { + IValue::Float(f) => f, + other => panic!("expected Float, got {:?}", other), + } + } + + /// Unwraps the IValue as bool, panics otherwise + pub fn unwrap_bool(self) -> bool { + match self { + IValue::Bool(b) => b, + other => panic!("expected Bool, got {:?}", other), + } + } + + /// Unwraps the IValue as String, panics otherwise + pub fn unwrap_str(self) -> String { + match self { + IValue::Str(s) => s, + other => panic!("expected Str, got {:?}", other), + } + } +} + +/// Creates a tuple of `(Vec, HashMap)` from positional args and keyword args. +/// +/// # Syntax +/// ```ignore +/// ivalue![[arg1, arg2, ...], { key1: val1, key2: val2, ... }] +/// ``` +/// +/// - The first bracket `[...]` contains positional arguments. +/// - The second brace `{...}` contains keyword arguments as `key: value` pairs. +/// - All values are automatically converted via `IValue::from(...)`, +/// so any type implementing `From for IValue` works directly. +/// +/// # Shorthand Forms +/// - `ivalue![[x, y]]` — args only, empty kwargs +/// - `ivalue![{ key: val }]` — kwargs only, empty args +/// - `ivalue![[], {}]` — fully empty +/// +/// # Examples +/// ```ignore +/// # use delta::ivalue; +/// # use delta::Tensor; +/// # use delta::nn; +/// # use delta::nn::Module; +/// # let x = Tensor::zeros(&[1, 1]); +/// # let q = Tensor::zeros(&[1, 1]); +/// # let k = Tensor::zeros(&[1, 1]); +/// # let v = Tensor::zeros(&[1, 1]); +/// +/// // args only shorthand +/// let (args, kwargs) = ivalue![[x]]; +/// +/// // kwargs only shorthand +/// let (args, kwargs) = ivalue![{ mask: true, scale: 1.0f64 }]; +/// +/// // multiple positional args +/// let (args, kwargs) = ivalue![[q, k, v]]; +/// +/// // both args and kwargs +/// let (args, kwargs) = ivalue![[x], { dropout: false }]; +/// +/// // inline directly into forward call +/// let model = nn::Linear::new(1, 1, true); +/// let (args, kwargs) = ivalue![[x], { scale: 0.5f64 }]; +/// model.forward(args, kwargs); +/// +/// // fully empty +/// let (args, kwargs) = ivalue![[], {}]; +/// ``` +#[macro_export] +macro_rules! ivalue { + // fully empty: ivalue![[], {}] + ([], {}) => { + (vec![], std::collections::HashMap::new()) + }; + + // args only shorthand: ivalue![[x, y]] + // sugar for ivalue![[x, y], {}] + ([$($arg:expr),* $(,)?]) => { + $crate::ivalue!([$($arg),*], {}) + }; + + // kwargs only shorthand: ivalue![{ key: val }] + // sugar for ivalue![[], { key: val }] + ({ $($key:ident : $val:expr),* $(,)? }) => { + $crate::ivalue!([], { $($key : $val),* }) + }; + + // args only, explicit empty kwargs: ivalue![[x, y], {}] + ([$($arg:expr),* $(,)?], {}) => { + ( + // wrap each positional arg into IValue via From trait + vec![$($crate::ivalue::IValue::from($arg)),*], + std::collections::HashMap::new(), + ) + }; + + // kwargs only, explicit empty args: ivalue![[], { key: val }] + ([], { $($key:ident : $val:expr),* $(,)? }) => { + ( + vec![], + { + let mut map = std::collections::HashMap::new(); + // stringify the identifier key and wrap value into IValue via From trait + $(map.insert( + stringify!($key).to_string(), + $crate::ivalue::IValue::from($val) + );)* + map + } + ) + }; + + // both args and kwargs: ivalue![[x], { mask: true }] + ([$($arg:expr),* $(,)?], { $($key:ident : $val:expr),* $(,)? }) => { + ( + // positional args → Vec + vec![$($crate::ivalue::IValue::from($arg)),*], + { + // keyword args → HashMap + let mut map = std::collections::HashMap::new(); + $(map.insert( + stringify!($key).to_string(), + $crate::ivalue::IValue::from($val) + );)* + map + } + ) + }; +} + +impl From for IValue { + fn from(t: Tensor) -> Self { + IValue::Tensor(t) + } +} + +impl From> for IValue { + fn from(v: Vec) -> Self { + IValue::TensorList(v) + } +} + +impl From for IValue { + fn from(i: i64) -> Self { + IValue::Int(i) + } +} + +impl From for IValue { + fn from(f: f64) -> Self { + IValue::Float(f) + } +} + +impl From for IValue { + fn from(b: bool) -> Self { + IValue::Bool(b) + } +} + +impl From for IValue { + fn from(s: String) -> Self { + IValue::Str(s) + } +} + +impl From<&str> for IValue { + fn from(s: &str) -> Self { + IValue::Str(s.to_owned()) + } +} diff --git a/src/lib.rs b/delta/src/lib.rs similarity index 68% rename from src/lib.rs rename to delta/src/lib.rs index e499076..dda1f13 100644 --- a/src/lib.rs +++ b/delta/src/lib.rs @@ -1,12 +1,14 @@ pub mod backward; pub mod data; +pub mod ivalue; pub mod linalg; -pub mod module; pub mod nn; mod op; +pub mod operations; pub mod optim; pub mod tensor; mod tensor_data; // define short paths +pub use operations::{cat, mean, sum}; pub use tensor::Tensor; diff --git a/src/linalg.rs b/delta/src/linalg.rs similarity index 100% rename from src/linalg.rs rename to delta/src/linalg.rs diff --git a/src/linalg/product.rs b/delta/src/linalg/product.rs similarity index 94% rename from src/linalg/product.rs rename to delta/src/linalg/product.rs index a9d27de..c19ad73 100644 --- a/src/linalg/product.rs +++ b/delta/src/linalg/product.rs @@ -1,4 +1,4 @@ -use crate::{op::Op, tensor_data::TensorData, Tensor}; +use crate::{Tensor, op::Op, tensor_data::TensorData}; /// Matrix multiplication /// @@ -23,14 +23,14 @@ pub fn matmul(a: Tensor, b: Tensor) -> Tensor { assert_eq!( a_shape.last().unwrap(), b_shape.first().unwrap(), - "The shapes of the tensors must have the same inner dimension -> (M x N) @ (N x M), but you have tensors A: {:?} and B: {:?}", - format!("({a_shape:?})").replace('[', "").replace(']', ""), - format!("({b_shape:?})").replace('[', "").replace(']', ""), + "The shapes of the tensors must have the same inner dimension -> (M x N) @ (N x M), but you have tensors A: {:?} and B: {:?}", + format!("({a_shape:?})").replace('[', "").replace(']', ""), + format!("({b_shape:?})").replace('[', "").replace(']', ""), ); // get batch dimensions if they exist let mut batches: Vec = vec![]; - for i in 2..a_shape.len() { - batches.push(a_shape[i - 2]); + for i in 0..(a_shape.len() - 2) { + batches.push(a_shape[i]); } // remove batch dimensions from the A tensor shape a_shape.drain(0..batches.len()); @@ -58,9 +58,7 @@ pub fn matmul(a: Tensor, b: Tensor) -> Tensor { .iter() .zip(b_data) .map(|(&a, &b)| a * b) - .collect::>() - .iter() - .sum(); + .sum::(); } } } diff --git a/src/nn.rs b/delta/src/nn.rs similarity index 58% rename from src/nn.rs rename to delta/src/nn.rs index b4091e0..d9075cb 100644 --- a/src/nn.rs +++ b/delta/src/nn.rs @@ -1,9 +1,15 @@ pub mod criterions; pub mod functional; pub mod linear; +pub mod module; +pub mod parameter; // define short paths // layers pub use linear::Linear; // criterions pub use criterions::MSELoss; +// module +pub use module::Module; +// parameter +pub use parameter::Parameter; diff --git a/src/nn/criterions.rs b/delta/src/nn/criterions.rs similarity index 100% rename from src/nn/criterions.rs rename to delta/src/nn/criterions.rs diff --git a/src/nn/functional.rs b/delta/src/nn/functional.rs similarity index 87% rename from src/nn/functional.rs rename to delta/src/nn/functional.rs index 4a2dcc1..8bcc36d 100644 --- a/src/nn/functional.rs +++ b/delta/src/nn/functional.rs @@ -1,4 +1,4 @@ -use crate::{op::Op, tensor_data::TensorData, Tensor}; +use crate::{Tensor, op::Op, tensor_data::TensorData}; pub fn relu(x: Tensor) -> Tensor { let mut data = x.item(); @@ -16,8 +16,17 @@ pub fn sigmoid(x: Tensor) -> Tensor { Tensor::new(inner, &data.shape) } -pub fn softmax(x: Tensor, dim: usize) -> Tensor { +pub fn softmax(x: Tensor, dim: isize) -> Tensor { + assert!(dim >= -1, "cat: `dim` cannot be negative integer"); + let shape = x.shape(); + + let dim: usize = if dim == -1 { + shape.len() - 1 + } else { + dim as usize + }; + let mut shape2 = shape.clone(); assert_eq!( dim, diff --git a/delta/src/nn/linear.rs b/delta/src/nn/linear.rs new file mode 100644 index 0000000..5a049c6 --- /dev/null +++ b/delta/src/nn/linear.rs @@ -0,0 +1,60 @@ +use std::collections::HashMap; + +use crate::{ + Tensor, + ivalue::IValue, + linalg, + nn::{Module, Parameter}, +}; + +/// # `Linear` Layer +/// +/// Contains of: +/// - weights +/// - bias +/// +/// Linear layer performs: `x @ W + b`, +/// where: +/// - `x` is input +/// - `W` is weights +/// - `b` is bias +// #[derive(Module, Clone)] +pub struct Linear { + // #[param] + pub weights: Parameter, +} + +impl Linear { + pub fn new(mut in_features: usize, out_features: usize, bias: bool) -> Self { + if bias { + in_features += 1; + } + let _weights = Parameter(Tensor::randn(&[in_features, out_features])); + Self { weights: _weights } + } +} + +impl Module for Linear { + fn module_name(&self) -> String { + "Linear".to_owned() + } + + fn parameters(&self) -> Vec { + let parameters = vec![self.weights.clone()]; + parameters + } + + fn forward(&self, args: Vec, _kwargs: HashMap) -> IValue { + let x = match &args[0] { + IValue::Tensor(t) => t.clone(), + _ => panic!("Linear expects a Tensor as first argument"), + }; + let weights = self.weights.clone(); + let mut ones_shape = x.shape(); + let _ = ones_shape.pop(); + ones_shape.push(1); + let x = Tensor::cat(&[x, Tensor::ones(&ones_shape)], 1); + let x = linalg::matmul(x, weights.0); + IValue::Tensor(x) + } +} diff --git a/src/module.rs b/delta/src/nn/module.rs similarity index 51% rename from src/module.rs rename to delta/src/nn/module.rs index 590f3a4..9e8a755 100644 --- a/src/module.rs +++ b/delta/src/nn/module.rs @@ -1,4 +1,6 @@ -use crate::Tensor; +use std::collections::HashMap; + +use crate::{ivalue::IValue, nn}; /// # `Module` Trait /// @@ -12,17 +14,16 @@ pub trait Module { /// Returns the name of module fn module_name(&self) -> String; - /// Returns the parameters of module - fn parameters(&self) -> Vec; -} + /// `forward` - performs inference in the module (forward propagation) + fn forward(&self, args: Vec, kwargs: HashMap) -> IValue; -/// # `Forward` Trait -/// -/// Trait that defines specific behavior for modules that work with tensors. -/// This trait has to be implemented for struct that implement `Module`. -/// -/// `forward` - performs inference in the module (forward propagation) -pub trait Forward: Module { - /// Forward (inference) function for module - fn forward(&self, x: Tensor) -> Tensor; + /// Returns the submodules of module + fn submodules(&self) -> Vec<&dyn Module> { + vec![] + } + + /// Returns the parameters of module + fn parameters(&self) -> Vec { + vec![] + } } diff --git a/delta/src/nn/parameter.rs b/delta/src/nn/parameter.rs new file mode 100644 index 0000000..0b9544b --- /dev/null +++ b/delta/src/nn/parameter.rs @@ -0,0 +1,12 @@ +use crate::Tensor; + +#[derive(Clone, Debug)] +pub struct Parameter(pub Tensor); + +impl std::ops::Deref for Parameter { + type Target = Tensor; + + fn deref(&self) -> &Tensor { + &self.0 + } +} diff --git a/src/op.rs b/delta/src/op.rs similarity index 73% rename from src/op.rs rename to delta/src/op.rs index 29ef74c..c7f43fc 100644 --- a/src/op.rs +++ b/delta/src/op.rs @@ -7,6 +7,15 @@ pub enum Op { Add, Sub, Mul, + Sum { + dim: Option, + keepdim: bool, + }, + Mean { + dim: Option, + keepdim: bool, + count: usize, + }, Pow(i32), Exp(Tensor), MatMul, @@ -23,6 +32,12 @@ impl std::fmt::Display for Op { Op::Add => write!(f, "Add"), Op::Sub => write!(f, "Sub"), Op::Mul => write!(f, "Mul"), + Op::Sum { dim: _, keepdim: _ } => write!(f, "Sum"), + Op::Mean { + dim: _, + keepdim: _, + count: _, + } => write!(f, "Mean"), Op::Pow(n) => write!(f, "Pow({n})"), Op::Exp(_) => write!(f, "Exp"), Op::MatMul => write!(f, "MatMul"), diff --git a/delta/src/operations.rs b/delta/src/operations.rs new file mode 100644 index 0000000..f9be400 --- /dev/null +++ b/delta/src/operations.rs @@ -0,0 +1,13 @@ +use crate::Tensor; + +pub fn sum(input: &Tensor, dim: Option, keepdim: bool) -> Tensor { + input.sum(dim, keepdim) +} + +pub fn mean(input: &Tensor, dim: Option, keepdim: bool) -> Tensor { + input.mean(dim, keepdim) +} + +pub fn cat(tensors: &[Tensor], dim: isize) -> Tensor { + Tensor::cat(tensors, dim) +} diff --git a/src/optim.rs b/delta/src/optim.rs similarity index 100% rename from src/optim.rs rename to delta/src/optim.rs diff --git a/src/optim/lr_scheduler.rs b/delta/src/optim/lr_scheduler.rs similarity index 100% rename from src/optim/lr_scheduler.rs rename to delta/src/optim/lr_scheduler.rs diff --git a/src/optim/lr_scheduler/multistep_lr.rs b/delta/src/optim/lr_scheduler/multistep_lr.rs similarity index 100% rename from src/optim/lr_scheduler/multistep_lr.rs rename to delta/src/optim/lr_scheduler/multistep_lr.rs diff --git a/src/optim/sgd.rs b/delta/src/optim/sgd.rs similarity index 79% rename from src/optim/sgd.rs rename to delta/src/optim/sgd.rs index 049a84c..e3c7a61 100644 --- a/src/optim/sgd.rs +++ b/delta/src/optim/sgd.rs @@ -1,4 +1,4 @@ -use crate::Tensor; +use crate::nn::Parameter; use super::Optim; @@ -11,13 +11,25 @@ use super::Optim; /// - learning rate #[derive(Clone)] pub struct SGD { - parameters: Vec, + parameters: Vec, lr: f64, maximize: bool, } impl SGD { - pub fn new(parameters: Vec, lr: f64) -> Self { + pub fn lr(&self) -> f64 { + self.lr + } + + pub fn is_maximize(&self) -> bool { + self.maximize + } + + pub fn parameters(&self) -> &[Parameter] { + &self.parameters + } + + pub fn new(parameters: Vec, lr: f64) -> Self { Self { parameters, lr, diff --git a/src/tensor.rs b/delta/src/tensor.rs similarity index 84% rename from src/tensor.rs rename to delta/src/tensor.rs index c32b76e..519fcb8 100644 --- a/src/tensor.rs +++ b/delta/src/tensor.rs @@ -192,6 +192,133 @@ impl Tensor { self } + pub fn sum(&self, dim: Option, keepdim: bool) -> Tensor { + match dim { + None => { + let value: f64 = self.item().iter().sum(); + let shape = if keepdim { + vec![1; self.shape.len().max(1)] + } else { + vec![1] + }; + let inner = TensorData::from_op( + vec![value], + vec![self.clone()], + Op::Sum { dim: None, keepdim }, + ); + Tensor::new(inner, &shape) + } + Some(dim) => { + assert!(dim < self.shape.len(), "sum: dim out of range"); + + let input = self.item(); + let outer: usize = self.shape[..dim].iter().product(); + let reduce: usize = self.shape[dim]; + let inner: usize = self.shape[dim + 1..].iter().product(); + + let mut out = vec![0.0; outer * inner]; + + for o in 0..outer { + for i in 0..inner { + let mut acc = 0.0; + for r in 0..reduce { + let idx = o * reduce * inner + r * inner + i; + acc += input[idx]; + } + out[o * inner + i] = acc; + } + } + + let mut out_shape = self.shape.clone(); + if keepdim { + out_shape[dim] = 1; + } else { + out_shape.remove(dim); + if out_shape.is_empty() { + out_shape.push(1); + } + } + + let inner_data = TensorData::from_op( + out, + vec![self.clone()], + Op::Sum { + dim: Some(dim), + keepdim, + }, + ); + Tensor::new(inner_data, &out_shape) + } + } + } + + pub fn mean(&self, dim: Option, keepdim: bool) -> Tensor { + match dim { + None => { + let n = self.length(); + assert!(n > 0, "mean of empty tensor is undefined"); + let value: f64 = self.item().iter().sum::() / n as f64; + let shape = if keepdim { + vec![1; self.shape.len().max(1)] + } else { + vec![1] + }; + let inner = TensorData::from_op( + vec![value], + vec![self.clone()], + Op::Mean { + dim: None, + keepdim, + count: n, + }, + ); + Tensor::new(inner, &shape) + } + Some(dim) => { + assert!(dim < self.shape.len(), "mean: dim out of range"); + + let input = self.item(); + let outer: usize = self.shape[..dim].iter().product(); + let reduce: usize = self.shape[dim]; + let inner: usize = self.shape[dim + 1..].iter().product(); + + let mut out = vec![0.0; outer * inner]; + + for o in 0..outer { + for i in 0..inner { + let mut acc = 0.0; + for r in 0..reduce { + let idx = o * reduce * inner + r * inner + i; + acc += input[idx]; + } + out[o * inner + i] = acc / reduce as f64; + } + } + + let mut out_shape = self.shape.clone(); + if keepdim { + out_shape[dim] = 1; + } else { + out_shape.remove(dim); + if out_shape.is_empty() { + out_shape.push(1); + } + } + + let inner_data = TensorData::from_op( + out, + vec![self.clone()], + Op::Mean { + dim: Some(dim), + keepdim, + count: reduce, + }, + ); + Tensor::new(inner_data, &out_shape) + } + } + } + /// Transpose /// /// This method transposes the tensor, it changes the shape. @@ -436,12 +563,12 @@ impl Tensor { /// Computes the gradients of all the tensors that have been interacting and /// have `requires_grad` set to `true`. pub fn backward(&self) { - let end_grad: Vec = self.inner.borrow()._prev[0] - .item() - .iter() - .map(|a| a.to_owned()) - .collect::>(); - self.add_to_grad(end_grad); + assert!( + self.length() == 1, + "grad can be implicitly created only for scalar outputs" + ); + + self.add_to_grad(vec![1.0]); self._backward() } @@ -626,10 +753,18 @@ impl Tensor { /// Concatenates a slice of tensors along the given dimension. /// All tensors must have the same shape except in the `dim` axis. - pub fn cat(tensors: &[Tensor], dim: usize) -> Self { + pub fn cat(tensors: &[Tensor], dim: isize) -> Self { assert!(!tensors.is_empty(), "cat: need at least one tensor"); + assert!(dim >= -1, "cat: `dim` cannot be negative integer"); let ndim = tensors[0].shape.len(); + + let dim: usize = if dim == -1 { + tensors[0].shape().len() - 1 + } else { + dim as usize + }; + assert!( dim < ndim, "cat: dim {} out of range for {}-D tensor", diff --git a/src/tensor_data.rs b/delta/src/tensor_data.rs similarity index 100% rename from src/tensor_data.rs rename to delta/src/tensor_data.rs diff --git a/tests/activations_test.rs b/delta/tests/activations_test.rs similarity index 92% rename from tests/activations_test.rs rename to delta/tests/activations_test.rs index de5bd37..2ffad02 100644 --- a/tests/activations_test.rs +++ b/delta/tests/activations_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use athena::{nn::functional as F, Tensor}; + use delta::{nn::functional as F, Tensor}; #[test] fn sigmoid() { diff --git a/tests/data_test.rs b/delta/tests/data_test.rs similarity index 98% rename from tests/data_test.rs rename to delta/tests/data_test.rs index e308221..6391601 100644 --- a/tests/data_test.rs +++ b/delta/tests/data_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use athena::{ + use delta::{ data::{dataloader::Dataloader, dataset::Dataset, sample::Sample}, Tensor, }; diff --git a/tests/linalg_test.rs b/delta/tests/linalg_test.rs similarity index 99% rename from tests/linalg_test.rs rename to delta/tests/linalg_test.rs index 52bfc48..3e6dc7e 100644 --- a/tests/linalg_test.rs +++ b/delta/tests/linalg_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use athena::{linalg, Tensor}; + use delta::{linalg, Tensor}; #[test] /// Matrix multiplication @@ -68,7 +68,7 @@ mod tests { ], &[3, 1, 3, 1, 2, 3], ); - let c = athena::linalg::cross(a.clone(), b.clone()); + let c = delta::linalg::cross(a.clone(), b.clone()); let correct = vec![ -0.5866, 1.0262, -0.4396, -1.9130, -0.8710, 2.7839, -0.5866, 1.0262, -0.4396, -1.9130, -0.8710, 2.7839, 0.8525, 0.7091, -1.5616, -1.0111, 3.0523, -2.0412, 0.8525, 0.7091, diff --git a/tests/optim_test.rs b/delta/tests/optim_test.rs similarity index 70% rename from tests/optim_test.rs rename to delta/tests/optim_test.rs index 2b3f99d..35889a6 100644 --- a/tests/optim_test.rs +++ b/delta/tests/optim_test.rs @@ -1,19 +1,21 @@ #[cfg(test)] mod tests { - use athena::{ - Tensor, - module::{Forward, Module}, - nn::{self, Linear, functional as F}, + use std::collections::HashMap; + + use delta::{ + Tensor, ivalue, + ivalue::IValue, + nn::{self, Linear, Module, functional as F}, optim::{Optim, SGD}, tensor, }; #[test] fn zero_grad() { - let a = Tensor::tensor(&[1., 2., 3., 4., 5., 6.], &[2, 3]); - let b = Tensor::ones(&[2, 3]); + let a = nn::Parameter(Tensor::tensor(&[1., 2., 3., 4., 5., 6.], &[2, 3])); + let b = nn::Parameter(Tensor::ones(&[2, 3])); let optim = SGD::new(vec![a.clone(), b.clone()], 1e-3); - let c = a.clone() * b.clone(); + let c = delta::sum(&(a.0.clone() * b.0.clone()), None, false); c.backward(); assert_ne!(a.grad().unwrap().iter().sum::(), 0.0); assert_ne!(b.grad().unwrap().iter().sum::(), 0.0); @@ -32,7 +34,8 @@ mod tests { let x = Tensor::randn(&[10, 4]); let criterion = nn::MSELoss::default(); - let mut out = mlp.forward(x.clone()); + let (args, kwargs) = ivalue![[x.clone()]]; + let mut out = mlp.forward(args, kwargs).unwrap_tensor(); out = out.squeeze(&[]); let loss = criterion.measure( out.clone(), @@ -81,16 +84,13 @@ mod tests { "MLP".to_owned() } - fn parameters(&self) -> Vec { - let parameters = self.linear1.parameters(); - parameters + fn parameters(&self) -> Vec { + self.linear1.parameters() } - } - impl Forward for MLP { - fn forward(&self, x: Tensor) -> Tensor { - let x = self.linear1.forward(x); - F::sigmoid(x) + fn forward(&self, args: Vec, kwargs: HashMap) -> IValue { + let x = self.linear1.forward(args, kwargs).unwrap_tensor(); + IValue::Tensor(F::sigmoid(x)) } } } diff --git a/tests/tensor_test.rs b/delta/tests/tensor_test.rs similarity index 99% rename from tests/tensor_test.rs rename to delta/tests/tensor_test.rs index 5d7b9be..6050e9a 100644 --- a/tests/tensor_test.rs +++ b/delta/tests/tensor_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use athena::{randn, Tensor}; + use delta::{randn, Tensor}; #[test] /// Valid shape of the tensor diff --git a/examples/linear.rs b/examples/linear.rs deleted file mode 100644 index 7d84e87..0000000 --- a/examples/linear.rs +++ /dev/null @@ -1,16 +0,0 @@ -use athena::{ - module::Forward, - nn::{functional as F, Linear}, - Tensor, -}; - -fn main() { - let linear = Linear::new(20, 10, true); - let x = Tensor::randn(&[2, 20]); - println!("Weights: {}", linear.weight); - println!("IN:\n{x}"); - let out = linear.forward(x); - println!("OUT:\n{out}"); - let out = F::sigmoid(out); - println!("OUT:\n{out}"); -} diff --git a/media/header.png b/media/header.png index 6035a21..437466c 100644 Binary files a/media/header.png and b/media/header.png differ diff --git a/media/header_original.png b/media/header_original.png index 0942abe..302d7c9 100644 Binary files a/media/header_original.png and b/media/header_original.png differ diff --git a/src/nn/linear.rs b/src/nn/linear.rs deleted file mode 100644 index 9c321d4..0000000 --- a/src/nn/linear.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{ - Tensor, linalg, - module::{Forward, Module}, -}; - -/// # `Linear` Layer -/// -/// Contains of: -/// - weights -/// - bias -/// -/// Linear layer performs: `x @ W + b`, -/// where: -/// - `x` is input -/// - `W` is weights -/// - `b` is bias -pub struct Linear { - pub weight: Tensor, -} - -impl Linear { - pub fn new(mut in_features: usize, out_features: usize, bias: bool) -> Self { - if bias { - in_features += 1; - } - let _weight = Tensor::randn(&[in_features, out_features]); - Self { weight: _weight } - } -} - -impl Module for Linear { - fn module_name(&self) -> String { - "Linear".to_owned() - } - - fn parameters(&self) -> Vec { - let parameters = vec![self.weight.clone()]; - parameters - } -} - -impl Forward for Linear { - fn forward(&self, x: Tensor) -> Tensor { - let weight = self.weight.clone(); - let mut ones_shape = x.shape(); - let _ = ones_shape.pop(); - ones_shape.push(1); - let x = Tensor::cat(&[x, Tensor::ones(&ones_shape)], 1); - let x = linalg::matmul(x, weight); - x - } -}