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
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