diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..21b65e7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,37 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ - main
+ pull_request:
+
+jobs:
+ package-smoke:
+ runs-on: ubuntu-latest
+ env:
+ RECODE_HOME: ${{ runner.temp }}/recode-home
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Sync dependencies
+ run: uv sync --extra dev
+
+ - name: Smoke test CLI
+ run: |
+ uv run python -m recode --version
+ uv run python -m recode --paths
+ uv run python -m recode --doctor
+
+ - name: Build distributions
+ run: uv build
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
new file mode 100644
index 0000000..57e9c25
--- /dev/null
+++ b/.github/workflows/publish-pypi.yml
@@ -0,0 +1,32 @@
+name: Publish to PyPI
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ environment: pypi
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Build package
+ run: uv build
+
+ - name: Publish to PyPI (trusted publishing)
+ uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..b0cf68d
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,34 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write
+
+jobs:
+ build-and-release:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12"
+
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Build package
+ run: uv build
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ dist/*
+ generate_release_notes: true
diff --git a/.gitignore b/.gitignore
index 5a8e969..2b4c855 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,8 @@ __pycache__/
*.pyc
.DS_Store
.python-version
-uv.lock
.tmp_*
+.pytest_cache/
+build/
+dist/
+*.egg-info/
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cbbc443
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Ever
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..71232bc
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include LICENSE
+include README.md
+recursive-include recode/problems *
+global-exclude __pycache__ *.py[cod] .DS_Store
diff --git a/README.md b/README.md
index 4b1e4dd..85f07de 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ A terminal-based spaced repetition tool for practicing any code from memory. Wri
- **OpenCode chat modal** — conversational help in-context while you study a problem
- **Agent-like chat tools** — presets (`/nudge`, `/test-me`), TODO capture, diff split view, and `/health`
- **158 themes** — full terminal.sexy palette, switchable live from the command palette
-- **Extensible** — add any problem by dropping a `.py` file into `problems/`
+- **Extensible** — add any problem by dropping a `.py`, `.jl`, or `.R` file into your writable `problems_dir`
---
@@ -36,36 +36,64 @@ A terminal-based spaced repetition tool for practicing any code from memory. Wri
## Requirements
- Python 3.10+
-- [`uv`](https://github.com/astral-sh/uv) (recommended) or `pip`
+- [`uv`](https://github.com/astral-sh/uv) (recommended), `pipx`, or `pip`
- A Gemini or OpenRouter API key
- [OpenCode CLI](https://opencode.ai/) on your PATH for in-app chat (auto-started by Recode)
---
-## Setup
+## Install
-**1. Clone the repo**
+### Homebrew
```bash
-git clone https://github.com/yourusername/recode.git
+brew tap ever-oli/homebrew-tap
+brew install ever-oli/homebrew-tap/recode
+```
+
+### PyPI
+
+```bash
+uv tool install recode-cli
+# or
+pipx install recode-cli
+```
+
+### Local dev
+
+```bash
+git clone https://github.com/ever-oli/recode.git
cd recode
+uv sync
+uv run python -m recode
```
-**2. Create a `.env` file** in the project root with your API key:
+---
+
+## Configuration
+
+Recode reads environment variables from your shell, a local `.env`, or `~/.config/recode/.env`.
+Use `recode --paths` to print the exact runtime directories for your machine.
+
+On first run, Recode seeds its bundled problem set into your writable `problems_dir` so generated and imported problems live beside the defaults instead of inside the installed package.
+
+Example `.env`:
```env
-# Option A — Google Gemini (default)
+# Option A — Google Gemini
GEMINI_API_KEY=your_gemini_api_key_here
+AI_PROVIDER=gemini
# Option B — OpenRouter
OPENROUTER_API_KEY=your_openrouter_api_key_here
AI_PROVIDER=openrouter
-# Optional: override the default editor (default: hx / Helix)
+# Optional: override the editor (default: hx)
EDITOR=nvim
-# Optional: override the problems directory
+# Optional: override runtime locations
# PROBLEMS_DIR=/path/to/your/problems
+# DB_PATH=/path/to/recode.db
# Optional: OpenCode server URL for in-app chat modal
# OPENCODE_SERVER_URL=http://127.0.0.1:4096
@@ -74,22 +102,25 @@ EDITOR=nvim
# OPENCODE_AUTOSTART=0
```
-**3. Install dependencies and run**
+---
+
+## Run
-With `uv` (recommended):
+Once installed:
```bash
-uv run app.py
+recode
```
-With `pip`:
+Useful non-interactive commands:
```bash
-pip install -r requirements.txt
-python app.py
+recode --version
+recode --paths
+recode --doctor
```
-**4. Chat modal behavior (OpenCode)**
+### Chat modal behavior (OpenCode)
By default, pressing `c` in a problem will auto-start a local OpenCode server if it is not already running.
The chat modal shows a live status line (`connected`, `auto-started OpenCode`, or `offline`).
@@ -109,16 +140,30 @@ opencode serve --port 4096
| Key | Action |
|-----|--------|
-| `Enter` | Open the selected problem in your editor, then review |
-| `c` | Open in-problem chat modal (OpenCode-backed) |
+| `Enter` | Open the selected problem |
+| `/` | Focus search |
+| `c` | Change collection on the main menu |
+| `g` | Generate problems from an arXiv paper |
+| `i` | Import from Exercism or LeetCode |
| `r` | Refresh the problem list |
-| `Ctrl+P` | Open the command palette (theme switcher, etc.) |
| `q` / `Ctrl+C` | Quit |
+Inside a problem:
+
+| Key | Action |
+|-----|--------|
+| `e` | Open the editor |
+| `s` | Submit and review |
+| `h` | Ask for a hint |
+| `f` | Ask for a suggested fix |
+| `c` | Open the in-problem chat modal |
+| `x` | Explain the solution or gap |
+| `q` | Return to the menu |
+
### Workflow
1. Select a problem from the list and press `Enter`
-2. Your editor opens — write the implementation from memory
+2. Press `e` to open your editor and write the implementation from memory
3. Save and close the editor
4. Recode shows a side-by-side diff of your attempt vs. the reference
5. Use **Hint** or **Suggest Fix** if you need AI assistance
@@ -129,8 +174,8 @@ opencode serve --port 4096
## Adding Problems
-Problems are plain `.py` files. Drop any `.py` file into the `problems/` folder and it will appear in the list on the next refresh (`r`).
-`TensorPoly` is now vendored as a normal folder inside `problems/` (not a submodule), and can be selected with collection switch (`c`) in the menu.
+Problems are plain `.py`, `.jl`, or `.R` files. Drop them into the writable `problems_dir` from `recode --paths` and they will appear in the list on the next refresh (`r`).
+`TensorPoly` ships as a bundled collection and is copied into your writable problems directory on first run. The built-in importer currently supports Exercism Python and free LeetCode problems.
A problem file contains two things:
@@ -151,12 +196,6 @@ DESCRIPTION = "Implement the sigmoid function using NumPy."
---
-## Themes
-
-Recode ships with 158 themes from [terminal.sexy](https://terminal.sexy). Switch themes live via `Ctrl+P` -> search "theme".
-
----
-
## License
MIT
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..c91c5e3
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,54 @@
+# Releasing Recode
+
+## PyPI
+
+1. Create the `recode` project on PyPI if it does not exist yet.
+ Use the package name `recode-cli`.
+2. Add a Trusted Publisher for:
+ - owner/repo: `ever-oli/recode`
+ - workflow: `.github/workflows/publish-pypi.yml`
+ - environment: `pypi`
+3. Bump the version in:
+ - `pyproject.toml`
+ - `recode/__init__.py`
+4. Push a tag like `v0.1.0`.
+
+The tag will trigger:
+
+- `.github/workflows/release.yml` to attach `dist/*` to a GitHub release
+- `.github/workflows/publish-pypi.yml` to publish to PyPI
+
+## Homebrew
+
+Update `ever-oli/homebrew-tap/Formula/recode.rb` after the tag is pushed so the formula points at the immutable GitHub source tarball for that release.
+
+Formula template:
+
+```ruby
+class Recode < Formula
+ include Language::Python::Virtualenv
+
+ desc "Terminal spaced repetition for coding problems and reference solutions"
+ homepage "https://github.com/ever-oli/recode"
+ url "https://github.com/ever-oli/recode/archive/refs/tags/v0.1.0.tar.gz"
+ sha256 ""
+ license "MIT"
+
+ depends_on "python@3.12"
+
+ def install
+ virtualenv_install_with_resources
+ end
+
+ test do
+ assert_match "problems_dir=", shell_output("#{bin}/recode --paths")
+ end
+end
+```
+
+To compute the SHA locally:
+
+```bash
+curl -L -o /tmp/recode-v0.1.0.tar.gz https://github.com/ever-oli/recode/archive/refs/tags/v0.1.0.tar.gz
+shasum -a 256 /tmp/recode-v0.1.0.tar.gz
+```
diff --git a/app.py b/app.py
index 25f2de7..4c83faf 100644
--- a/app.py
+++ b/app.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""
Recode — Spaced repetition for ML code.
-Drop .py scripts into PROBLEMS_DIR (default: ./problems).
-Run: uv run app.py
+Drop .py scripts into the writable problems directory shown by `recode --paths`.
+Run: uv run python -m recode
"""
from __future__ import annotations
@@ -12,7 +12,6 @@
import tempfile
from pathlib import Path
-from dotenv import load_dotenv
from rich.markup import escape
from rich.syntax import Syntax
from textual.app import App, ComposeResult
@@ -23,29 +22,43 @@
from ai import get_explain, get_hint, get_suggest_fix, opencode_chat, opencode_chat_health
from db import get_db, get_row, get_streak, log_mistake, recent_mistakes, reset_progress, sm2_update
-from modals import AIModal, ChatModal, ConfirmModal, RatingModal, CollectionSelectModal
+from modals import AIModal, ChatModal, ConfirmModal, RatingModal, CollectionSelectModal, PaperGenerateModal, ImportProblemModal
from problems_utils import (
build_side_by_side,
get_problem_id,
+ has_test_cases,
+ is_marimo_problem,
load_problem_meta,
max_rating_for,
+ problem_badges,
scan_problems,
scan_collections,
status_label,
)
+from test_runner import run_tests, format_test_results
+from paper_generator import generate_problems, parse_arxiv_url, fetch_paper, extract_sections
from themes import TERMINAL_SEXY_THEMES
-
-load_dotenv()
+from recode.runtime import RuntimePaths, get_runtime, prepare_runtime
# ── Config ────────────────────────────────────────────────────────────────────
-PROBLEMS_DIR = Path(os.environ.get("PROBLEMS_DIR", "./problems"))
-DB_PATH = Path(os.environ.get("DB_PATH", "study_data.db"))
-EDITOR = os.environ.get("EDITOR", "hx")
+RUNTIME = get_runtime()
+PROBLEMS_DIR = RUNTIME.problems_dir
+DB_PATH = RUNTIME.db_path
+EDITOR = RUNTIME.editor
_TMP = Path(tempfile.gettempdir())
RATING_LABELS = {1: "Again", 2: "Hard", 3: "Good", 4: "Easy"}
+def configure_runtime(runtime: RuntimePaths | None = None) -> RuntimePaths:
+ global RUNTIME, PROBLEMS_DIR, DB_PATH, EDITOR
+ RUNTIME = runtime or prepare_runtime()
+ PROBLEMS_DIR = RUNTIME.problems_dir
+ DB_PATH = RUNTIME.db_path
+ EDITOR = RUNTIME.editor
+ return RUNTIME
+
+
# ── Study Screen ──────────────────────────────────────────────────────────────
class StudyScreen(Screen):
BINDINGS = [
@@ -81,8 +94,10 @@ def compose(self) -> ComposeResult:
def on_mount(self) -> None:
desc = self.meta["description"]
desc_part = f" [dim italic]{escape(desc)}[/]" if desc else ""
+ badges = problem_badges(self.problem)
+ badge_str = " " + " ".join(f"[{b[1]}]" for b in badges) if badges else ""
self.query_one("#problem-bar", Static).update(
- f"[bold white]{escape(self.problem.name)}[/]{desc_part}"
+ f"[bold white]{escape(self.problem.name)}[/]{badge_str}{desc_part}"
)
log = self.query_one("#diff-pane", RichLog)
row = get_row(self.conn, self.pid)
@@ -208,6 +223,22 @@ def _show_diff(self) -> None:
if small_summary:
log_mistake(self.conn, self.pid, small_summary)
+ # Run tests if available
+ if has_test_cases(self.problem) and user_code.strip():
+ log.write("\n[dim]── running tests ──[/]")
+ try:
+ test_results = run_tests(self.problem, user_code)
+ if test_results:
+ log.write(format_test_results(test_results))
+ # Log test failures as mistakes
+ for tr in test_results:
+ if not tr.passed:
+ log_mistake(self.conn, self.pid, f"test failed: {tr.name} — {tr.detail}")
+ else:
+ log.write("[dim]no tests ran[/]")
+ except Exception as e:
+ log.write(f"[red]test runner error: {e}[/]")
+
max_r = max_rating_for(self.attempts)
if self.attempts >= 4:
log.write(f"\n[bold red]attempt {self.attempts} — press s to record (forced: Again)[/]")
@@ -280,6 +311,8 @@ class MenuScreen(Screen):
Binding("r", "refresh", "Refresh"),
Binding("/", "focus_search", "Search"),
Binding("c", "change_collection", "Collection"),
+ Binding("g", "generate_from_paper", "Generate"),
+ Binding("i", "import_problems", "Import"),
Binding("escape", "clear_search", "Clear", show=False),
Binding("d", "reset_row", "Reset", show=False),
Binding("q", "quit_app", "Quit"),
@@ -302,7 +335,7 @@ def compose(self) -> ComposeResult:
def on_mount(self) -> None:
t = self.query_one(DataTable)
- t.add_columns("Status", "Problem", "Reps", "Interval", "Next review")
+ t.add_columns("Status", " ", "Problem", "Reps", "Interval", "Next review")
self._refresh()
def _refresh(self) -> None:
@@ -357,8 +390,10 @@ def _render_table(self) -> None:
if q and q not in p.name.lower():
continue
self._visible_paths.append(p)
+ badges = problem_badges(p)
+ badge_str = "".join(b[0] for b in badges) if badges else ""
t.add_row(
- f"[{color}]{label}[/]", p.name, reps, interval, nxt,
+ f"[{color}]{label}[/]", badge_str, p.name, reps, interval, nxt,
key=str(p),
)
@@ -377,6 +412,60 @@ def _on_collection_selected(self, collection: Path | None) -> None:
self.current_collection = collection
self._refresh()
+ def action_generate_from_paper(self) -> None:
+ self.app.push_screen(PaperGenerateModal(), self._on_paper_config)
+
+ def _on_paper_config(self, config: dict | None) -> None:
+ if not config:
+ return
+
+ # Show generating status
+ stats = self.query_one("#stats-bar", Static)
+ original_text = str(stats.renderable)
+ stats.update("[bold yellow] Generating problems from paper...[/]")
+
+ # Run generation in a thread to not block UI
+ import threading
+ threading.Thread(
+ target=self._run_generation,
+ args=(config,),
+ daemon=True,
+ ).start()
+
+ def _run_generation(self, config: dict) -> None:
+ from paper_generator import generate_problems
+
+ output_dir = PROBLEMS_DIR / "generated"
+ paper, files = generate_problems(
+ arxiv_url=config["url"],
+ output_dir=output_dir,
+ num_problems=config.get("num_problems", 3),
+ language=config.get("language", "python"),
+ use_marimo=True,
+ )
+
+ def _done():
+ if paper and files:
+ self.query_one("#stats-bar", Static).update(
+ f"[bold green] Generated {len(files)} problems from: {paper.title}[/]"
+ )
+ # Switch to generated collection
+ self.current_collection = output_dir
+ self._refresh()
+ else:
+ self.query_one("#stats-bar", Static).update(
+ "[bold red] Failed to generate problems. Check the paper URL.[/]"
+ )
+
+ self.app.call_from_thread(_done)
+
+ def action_import_problems(self) -> None:
+ self.app.push_screen(ImportProblemModal(), self._on_import_result)
+
+ def _on_import_result(self, result: dict | None) -> None:
+ if result:
+ self._refresh()
+
def action_clear_search(self) -> None:
inp = self.query_one("#search-input", Input)
inp.value = ""
@@ -536,5 +625,17 @@ def on_mount(self) -> None:
self.push_screen(MenuScreen())
-if __name__ == "__main__":
+def main(
+ *,
+ problems_dir: str | Path | None = None,
+ db_path: str | Path | None = None,
+ editor: str | None = None,
+) -> None:
+ configure_runtime(
+ prepare_runtime(problems_dir=problems_dir, db_path=db_path, editor=editor)
+ )
MLStudyApp().run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/exercism.py b/exercism.py
new file mode 100644
index 0000000..95ff4e0
--- /dev/null
+++ b/exercism.py
@@ -0,0 +1,384 @@
+"""
+exercism.py — Fetch exercises from Exercism and convert to Recode problems.
+
+Exercism's old v1 API is no longer usable for anonymous requests. This module
+uses the public v2 listing endpoints on exercism.org and the public GitHub
+track repositories to fetch starter/example/test files.
+"""
+from __future__ import annotations
+
+import json
+import re
+import urllib.error
+import urllib.request
+from dataclasses import dataclass
+from html import unescape
+from pathlib import Path
+
+EXERCISM_API = "https://exercism.org/api/v2"
+GITHUB_API = "https://api.github.com"
+
+
+@dataclass
+class Track:
+ slug: str
+ name: str
+ num_concept_exercises: int
+ num_practice_exercises: int
+ tags: list[str]
+
+
+@dataclass
+class Exercise:
+ slug: str
+ name: str
+ difficulty: str # easy, medium, hard
+ type: str # tutorial, concept, practice
+ description: str
+ topics: list[str]
+ files: dict[str, str] # filename -> content
+
+
+def _api_get(url: str) -> dict | list | None:
+ """Make a GET request and parse JSON."""
+ try:
+ req = urllib.request.Request(
+ url,
+ headers={
+ "Accept": "application/json",
+ "User-Agent": "Recode/0.1",
+ },
+ )
+ with urllib.request.urlopen(req, timeout=20) as resp:
+ return json.loads(resp.read().decode())
+ except Exception:
+ return None
+
+
+def _text_get(url: str) -> str | None:
+ """Fetch a UTF-8 text resource."""
+ try:
+ req = urllib.request.Request(url, headers={"User-Agent": "Recode/0.1"})
+ with urllib.request.urlopen(req, timeout=20) as resp:
+ return resp.read().decode()
+ except Exception:
+ return None
+
+
+def list_tracks() -> list[Track]:
+ """List all available Exercism tracks (languages)."""
+ data = _api_get(f"{EXERCISM_API}/tracks")
+ if not data or "tracks" not in data:
+ return []
+
+ tracks = []
+ for t in data["tracks"]:
+ num_concepts = int(t.get("num_concepts", 0) or 0)
+ num_total = int(t.get("num_exercises", 0) or 0)
+ tracks.append(
+ Track(
+ slug=t.get("slug", ""),
+ name=t.get("title", t.get("slug", "")),
+ num_concept_exercises=num_concepts,
+ num_practice_exercises=max(0, num_total - num_concepts),
+ tags=t.get("tags", []),
+ )
+ )
+ return tracks
+
+
+def _difficulty_label(level: int | str | None) -> str:
+ """Normalize Exercism difficulty to easy/medium/hard."""
+ if isinstance(level, str):
+ lowered = level.strip().lower()
+ if lowered in {"easy", "medium", "hard"}:
+ return lowered
+ if isinstance(level, (int, float)):
+ if level <= 3:
+ return "easy"
+ if level <= 6:
+ return "medium"
+ return "hard"
+
+
+def _exercise_kind(repo_type: str) -> str:
+ """Map Exercism exercise types to repo directory names."""
+ return "concept" if repo_type == "concept" else "practice"
+
+
+def _matches_type(found_type: str, expected_type: str) -> bool:
+ """Filter v2 list results locally because the endpoint ignores the query param."""
+ if not expected_type:
+ return True
+ if expected_type == "practice":
+ return found_type in {"practice", "tutorial"}
+ return found_type == expected_type
+
+
+def list_exercises(track: str, exercise_type: str = "practice") -> list[dict]:
+ """
+ List exercises for a track.
+
+ Args:
+ track: Track slug (e.g., "python", "rust", "julia")
+ exercise_type: "practice", "concept", or "" for all
+ """
+ data = _api_get(f"{EXERCISM_API}/tracks/{track}/exercises")
+ if not data or "exercises" not in data:
+ return []
+
+ exercises = []
+ for ex in data["exercises"]:
+ ex_type = str(ex.get("type", "")).lower()
+ if not _matches_type(ex_type, exercise_type):
+ continue
+ exercises.append(
+ {
+ "slug": ex.get("slug", ""),
+ "name": ex.get("title", ex.get("slug", "")),
+ "difficulty": _difficulty_label(ex.get("difficulty")),
+ "type": ex_type or exercise_type or "practice",
+ "topics": [],
+ "description": ex.get("blurb", ""),
+ }
+ )
+ return exercises
+
+
+def _github_contents(repo: str, path: str) -> list[dict]:
+ """Return GitHub contents listing for a repo path."""
+ data = _api_get(f"{GITHUB_API}/repos/{repo}/contents/{path}")
+ return data if isinstance(data, list) else []
+
+
+def _fetch_repo_files(track: str, exercise_type: str, slug: str) -> dict[str, str]:
+ """Fetch top-level and .meta files from the public Exercism track repo."""
+ repo = f"exercism/{track}"
+ root = f"exercises/{_exercise_kind(exercise_type)}/{slug}"
+ files: dict[str, str] = {}
+
+ for item in _github_contents(repo, root):
+ if item.get("type") == "file" and item.get("download_url"):
+ text = _text_get(item["download_url"])
+ if text:
+ files[item["path"]] = text
+
+ for item in _github_contents(repo, f"{root}/.meta"):
+ if item.get("type") == "file" and item.get("download_url"):
+ text = _text_get(item["download_url"])
+ if text:
+ files[item["path"]] = text
+
+ return files
+
+
+def _strip_html(html: str) -> str:
+ """Convert a small HTML fragment to readable plain text."""
+ text = unescape(html)
+ text = re.sub(r"]*>(.*?)
", r"\n```\n\1\n```\n", text, flags=re.DOTALL)
+ text = re.sub(r"]*>(.*?)", r"`\1`", text, flags=re.DOTALL)
+ text = re.sub(r"]*>(.*?)", r"**\1**", text, flags=re.DOTALL)
+ text = re.sub(r"]*>(.*?)", r"*\1*", text, flags=re.DOTALL)
+ text = re.sub(r"]*>", "- ", text)
+ text = re.sub(r"", "\n", text)
+ text = re.sub(r"
", "\n", text)
+ text = re.sub(r"
||", "\n", text)
+ text = re.sub(r"<[^>]+>", "", text)
+ text = re.sub(r"\n{3,}", "\n\n", text)
+ return text.strip()
+
+
+def _fetch_description(track: str, slug: str, fallback: str) -> str:
+ """Fetch the public exercise page and extract its instructions section."""
+ html = _text_get(f"https://exercism.org/tracks/{track}/exercises/{slug}")
+ if not html:
+ return fallback
+
+ match = re.search(
+ r"(.*?)",
+ html,
+ re.DOTALL,
+ )
+ if match:
+ text = _strip_html(match.group(1))
+ if text:
+ return text
+ return fallback
+
+
+def fetch_exercise(track: str, slug: str) -> Exercise | None:
+ """Fetch a specific exercise with public metadata and repo files."""
+ exercise_data = None
+ for ex in list_exercises(track, ""):
+ if ex["slug"] == slug:
+ exercise_data = ex
+ break
+ if not exercise_data:
+ return None
+
+ files = _fetch_repo_files(track, exercise_data["type"], slug)
+ description = _fetch_description(track, slug, exercise_data.get("description") or f"Exercism {track} exercise: {slug}")
+
+ return Exercise(
+ slug=slug,
+ name=exercise_data["name"],
+ difficulty=exercise_data["difficulty"],
+ type=exercise_data["type"],
+ description=description,
+ topics=exercise_data.get("topics", []),
+ files=files,
+ )
+
+
+def _extract_test_cases(test_code: str, track: str) -> str:
+ """
+ Convert Exercism test file to Recode TEST_CASES format.
+
+ This is track-specific. Currently supports Python.
+ For other tracks, returns the raw test file.
+ """
+ if track == "python":
+ return _extract_python_tests(test_code)
+ return test_code
+
+
+def _extract_python_tests(test_code: str) -> str:
+ """
+ Extract test functions from an Exercism Python test file
+ and convert to Recode TEST_CASES format.
+
+ Exercism's Python test files mix unittest helpers, decorators, and
+ hand-crafted import errors. Converting them faithfully is brittle, so we
+ keep the original source for reference and generate a minimal smoke test
+ from the imported symbols the test file expects.
+ """
+ imports = re.findall(r"from\s+\w+\s+import\s*\((.*?)\)", test_code, re.DOTALL)
+ expected_names: list[str] = []
+ for block in imports:
+ for line in block.splitlines():
+ name = line.strip().rstrip(",")
+ if name:
+ expected_names.append(name)
+
+ expected_names = list(dict.fromkeys(expected_names))
+ raw_literal = repr(test_code)
+
+ lines = [
+ "# ── Test cases (converted from Exercism) ──",
+ "",
+ "# Original Exercism test source, preserved for reference.",
+ f"RAW_TESTS = {raw_literal}",
+ "",
+ "def _test_expected_symbols(ns):",
+ ' """Validate the exported names Exercism expects."""',
+ ]
+
+ if expected_names:
+ for name in expected_names:
+ lines.append(f" assert {name!r} in ns, {name!r} + ' not found in solution namespace'")
+ else:
+ lines.append(" assert ns, 'solution namespace is empty'")
+
+ lines.extend(
+ [
+ "",
+ "TEST_CASES = [_test_expected_symbols]",
+ ]
+ )
+ return "\n".join(lines)
+
+
+def convert_to_recode_problem(exercise: Exercise, track: str) -> dict:
+ """
+ Convert an Exercism exercise to Recode problem format.
+
+ Returns dict with keys: filename, solution, description, tests
+ """
+ solution = ""
+ test_code = ""
+
+ items = list(exercise.files.items())
+
+ for path, content in items:
+ lowered = path.lower()
+ if lowered.endswith(("_test.py", "test.py")) or lowered.endswith(("_test.jl", "_test.r", "_test.rs", "_test.go")):
+ test_code = content
+ break
+
+ for path, content in items:
+ lowered = path.lower()
+ if "example" in lowered or "solution" in lowered or "exemplar" in lowered:
+ solution = content
+ break
+
+ if not solution:
+ for path, content in items:
+ lowered = path.lower()
+ if lowered.endswith((".py", ".jl", ".r", ".rs", ".go")) and "/.meta/" not in lowered and "test" not in lowered:
+ solution = content
+ break
+
+ if not solution:
+ solution = "# TODO: Implement the solution\n"
+ if test_code:
+ solution += "# See the bundled Exercism tests for the expected behavior.\n"
+
+ tests = _extract_test_cases(test_code, track) if test_code else ""
+
+ slug = exercise.slug.replace("-", "_")
+ ext = {"python": ".py", "julia": ".jl", "r": ".R", "rust": ".rs", "go": ".go"}.get(track, ".py")
+ filename = f"exercism-{slug}{ext}"
+
+ return {
+ "filename": filename,
+ "solution": solution.strip(),
+ "description": f"[Exercism/{track}] {exercise.description}",
+ "difficulty": exercise.difficulty,
+ "tags": exercise.topics + [track, "exercism", exercise.type],
+ "tests": tests,
+ "source": f"exercism/{track}/{exercise.slug}",
+ }
+
+
+def fetch_and_convert(track: str, slug: str) -> dict | None:
+ """Fetch an Exercism exercise and convert to Recode format."""
+ exercise = fetch_exercise(track, slug)
+ if not exercise:
+ return None
+ return convert_to_recode_problem(exercise, track)
+
+
+def write_exercism_problem(problem: dict, output_dir: Path) -> Path:
+ """Write an Exercism-sourced problem to a file."""
+ output_dir.mkdir(parents=True, exist_ok=True)
+ filepath = output_dir / problem["filename"]
+
+ tags_str = ", ".join(problem.get("tags", []))
+ description_literal = json.dumps(problem["description"])
+ solution_literal = repr(problem["solution"])
+ lines = [
+ "# ---",
+ f"# description: {description_literal}",
+ f'# difficulty: {problem.get("difficulty", "medium")}',
+ f"# tags: [{tags_str}]",
+ f'# source: {problem.get("source", "")}',
+ "# ---",
+ "",
+ f"SOLUTION = {solution_literal}",
+ "",
+ f"DESCRIPTION = {description_literal}",
+ ]
+
+ if problem.get("tests"):
+ lines.append("")
+ lines.append(problem["tests"])
+
+ filepath.write_text("\n".join(lines))
+ return filepath
+
+
+def popular_exercises(track: str = "python", limit: int = 10) -> list[dict]:
+ """Get popular beginner-friendly exercises for a track."""
+ exercises = list_exercises(track, "practice")
+ sorted_ex = sorted(exercises, key=lambda e: (e["difficulty"] != "easy", e["name"]))
+ return sorted_ex[:limit]
diff --git a/leetcode.py b/leetcode.py
new file mode 100644
index 0000000..cd08f45
--- /dev/null
+++ b/leetcode.py
@@ -0,0 +1,416 @@
+"""
+leetcode.py — Fetch problems from LeetCode and convert to Recode format.
+
+Uses LeetCode's GraphQL API (same as their website) to:
+- List problems by difficulty/tag
+- Fetch problem details (description, test cases, solutions)
+- Convert to Recode problem format with TEST_CASES
+
+Note: LeetCode doesn't provide an official public API. This uses their
+internal GraphQL endpoint which may change. Community solutions are
+fetched from publicly available sources.
+"""
+from __future__ import annotations
+
+import json
+import re
+import urllib.error
+import urllib.request
+from dataclasses import dataclass
+from html import unescape
+from pathlib import Path
+
+LEETCODE_GRAPHQL = "https://leetcode.com/graphql"
+LEETCODE_API = "https://leetcode.com/api"
+
+
+@dataclass
+class LeetCodeProblem:
+ id: int
+ title: str
+ title_slug: str
+ difficulty: str # Easy, Medium, Hard
+ description: str
+ topics: list[str]
+ test_cases: list[dict] # [{"input": ..., "expected": ...}]
+ solution_template: str
+ hints: list[str]
+
+
+def _graphql(query: str, variables: dict | None = None) -> dict | None:
+ """Make a GraphQL request to LeetCode."""
+ body = {"query": query}
+ if variables:
+ body["variables"] = variables
+
+ try:
+ req = urllib.request.Request(
+ LEETCODE_GRAPHQL,
+ data=json.dumps(body).encode(),
+ headers={
+ "Content-Type": "application/json",
+ "User-Agent": "Mozilla/5.0 (Recode/0.1)",
+ "Referer": "https://leetcode.com",
+ },
+ method="POST",
+ )
+ with urllib.request.urlopen(req, timeout=15) as resp:
+ return json.loads(resp.read().decode())
+ except Exception:
+ return None
+
+
+def list_problems(
+ difficulty: str = "",
+ tag: str = "",
+ limit: int = 20,
+ offset: int = 0,
+) -> list[dict]:
+ """
+ List LeetCode problems with optional filters.
+
+ Args:
+ difficulty: "Easy", "Medium", "Hard", or "" for all
+ tag: Topic tag slug (e.g., "array", "dynamic-programming")
+ limit: Max results
+ offset: Pagination offset
+ """
+ query = """
+ query problemsetQuestionList($categorySlug: String, $limit: Int, $skip: Int, $filters: QuestionListFilterInput) {
+ problemsetQuestionList: questionList(
+ categorySlug: $categorySlug
+ limit: $limit
+ skip: $skip
+ filters: $filters
+ ) {
+ total: totalNum
+ questions: data {
+ id: questionId
+ title
+ titleSlug: titleSlug
+ difficulty
+ status
+ topicTags: topicTags {
+ name
+ slug
+ }
+ isPaidOnly: isPaidOnly
+ }
+ }
+ }
+ """
+
+ variables = {
+ "categorySlug": "",
+ "skip": offset,
+ "limit": limit,
+ "filters": {},
+ }
+
+ if difficulty:
+ variables["filters"]["difficulty"] = difficulty.upper()
+ if tag:
+ variables["filters"]["tags"] = [tag]
+
+ result = _graphql(query, variables)
+ if not result or "data" not in result:
+ return []
+
+ questions = result["data"]["problemsetQuestionList"]["questions"]
+ problems = []
+ for q in questions:
+ if q.get("isPaidOnly"):
+ continue # Skip premium problems
+ problems.append({
+ "id": int(q["id"]),
+ "title": q["title"],
+ "slug": q["titleSlug"],
+ "difficulty": q["difficulty"].lower(),
+ "topics": [t["name"] for t in q.get("topicTags", [])],
+ })
+
+ return problems
+
+
+def list_free_problems(
+ *,
+ difficulty: str = "",
+ tag: str = "",
+ page_size: int = 100,
+ max_pages: int = 30,
+) -> list[dict]:
+ """List the full free LeetCode catalog for the given filters."""
+ all_problems: list[dict] = []
+ seen_slugs: set[str] = set()
+
+ for page in range(max_pages):
+ batch = list_problems(
+ difficulty=difficulty,
+ tag=tag,
+ limit=page_size,
+ offset=page * page_size,
+ )
+ if not batch:
+ break
+
+ new_count = 0
+ for problem in batch:
+ slug = problem["slug"]
+ if slug in seen_slugs:
+ continue
+ seen_slugs.add(slug)
+ all_problems.append(problem)
+ new_count += 1
+
+ if len(batch) < page_size or new_count == 0:
+ break
+
+ return all_problems
+
+
+def fetch_problem(slug: str) -> LeetCodeProblem | None:
+ """
+ Fetch a specific LeetCode problem with details.
+
+ Args:
+ slug: Problem slug (e.g., "two-sum", "valid-parentheses")
+ """
+ query = """
+ query getQuestionDetail($titleSlug: String!) {
+ question(titleSlug: $titleSlug) {
+ questionId
+ title
+ titleSlug
+ difficulty
+ content
+ topicTags {
+ name
+ slug
+ }
+ hints
+ codeSnippets {
+ lang
+ langSlug
+ code
+ }
+ exampleTestcases
+ sampleTestCase
+ }
+ }
+ """
+
+ result = _graphql(query, {"titleSlug": slug})
+ if not result or "data" not in result or not result["data"].get("question"):
+ return None
+
+ q = result["data"]["question"]
+
+ # Parse HTML description to text
+ description = _html_to_text(q.get("content", ""))
+
+ # Get Python code template
+ template = ""
+ for snippet in q.get("codeSnippets", []):
+ if snippet.get("langSlug") == "python3":
+ template = snippet.get("code", "")
+ break
+
+ # Parse example test cases
+ test_cases = _parse_test_cases(
+ q.get("exampleTestcases", ""),
+ q.get("sampleTestCase", ""),
+ description,
+ )
+
+ return LeetCodeProblem(
+ id=int(q["questionId"]),
+ title=q["title"],
+ title_slug=q["titleSlug"],
+ difficulty=q["difficulty"].lower(),
+ description=description,
+ topics=[t["name"] for t in q.get("topicTags", [])],
+ test_cases=test_cases,
+ solution_template=template,
+ hints=q.get("hints", []),
+ )
+
+
+def _html_to_text(html: str) -> str:
+ """Convert LeetCode HTML description to plain text."""
+ # Remove HTML tags but keep structure
+ text = unescape(html)
+ text = re.sub(r'
]*>(.*?)
', r'\n```\n\1\n```\n', text, flags=re.DOTALL)
+ text = re.sub(r'
]*>(.*?)', r'`\1`', text, flags=re.DOTALL)
+ text = re.sub(r'
]*>(.*?)', r'**\1**', text, flags=re.DOTALL)
+ text = re.sub(r'
]*>(.*?)', r'*\1*', text, flags=re.DOTALL)
+ text = re.sub(r'
]*>', '\n', text)
+ text = re.sub(r'
', '', text)
+ text = re.sub(r'
', '\n', text)
+ text = re.sub(r'
]*>', '- ', text)
+ text = re.sub(r'<[^>]+>', '', text) # Remove remaining tags
+ text = re.sub(r'\n{3,}', '\n\n', text) # Collapse multiple newlines
+ return text.strip()
+
+
+def _parse_test_cases(example_cases: str, sample_case: str, description: str) -> list[dict]:
+ """Parse test cases from LeetCode problem data."""
+ test_cases = []
+
+ # Parse from exampleTestcases field
+ if example_cases:
+ lines = example_cases.strip().split('\n')
+ i = 0
+ while i < len(lines):
+ line = lines[i].strip()
+ if line:
+ test_cases.append({
+ "input": line,
+ "expected": "", # LeetCode doesn't provide expected in this field
+ })
+ i += 1
+
+ # Extract examples from description
+ examples = re.findall(
+ r'Input:\s*(.+?)\s*Output:\s*(.+?)(?:\s*Explanation:.*?)?(?=\n\n|Example|\Z)',
+ description,
+ re.DOTALL,
+ )
+
+ for inp, out in examples:
+ test_cases.append({
+ "input": inp.strip(),
+ "expected": out.strip(),
+ })
+
+ return test_cases
+
+
+def convert_to_recode_problem(problem: LeetCodeProblem) -> dict:
+ """Convert a LeetCode problem to Recode format."""
+ slug = problem.title_slug.replace("-", "_")
+
+ # Build test cases
+ test_code = _build_test_code(problem)
+
+ return {
+ "filename": f"leetcode-{slug}.py",
+ "solution": problem.solution_template,
+ "description": f"[LeetCode #{problem.id}] {problem.title}: {problem.description[:200]}",
+ "difficulty": problem.difficulty,
+ "tags": problem.topics + ["leetcode"],
+ "tests": test_code,
+ "source": f"leetcode/{problem.id}",
+ "hints": problem.hints,
+ }
+
+
+def _build_test_code(problem: LeetCodeProblem) -> str:
+ """Build Recode TEST_CASES from LeetCode test cases."""
+ # Extract the function name from the template
+ func_match = re.search(r'def (\w+)\(', problem.solution_template)
+ func_name = func_match.group(1) if func_match else "solution"
+
+ lines = [
+ "# ── Test cases (from LeetCode) ──",
+ "",
+ f"def _test_leetcode_cases(ns):",
+ f' """Run LeetCode test cases"""',
+ f" fn = ns.get('{func_name}')",
+ f" assert fn is not None, '{func_name} not found'",
+ "",
+ ]
+
+ for i, tc in enumerate(problem.test_cases[:5]): # Limit to 5 test cases
+ inp = tc.get("input", "")
+ expected = tc.get("expected", "")
+
+ if inp and expected:
+ # Parse input format like "nums = [2,7,11,15], target = 9"
+ lines.append(f" # Test case {i + 1}: Input: {inp}")
+ lines.append(f" # Expected: {expected}")
+
+ # Try to extract variable assignments
+ assignments = re.findall(r'(\w+)\s*=\s*(.+?)(?:,|$)', inp)
+ if assignments:
+ for var, val in assignments:
+ lines.append(f" {var} = {val.strip()}")
+ args = ", ".join(a[0] for a in assignments)
+ lines.append(f" result = fn({args})")
+ lines.append(f" # Verify result matches expected: {expected}")
+ lines.append(f" assert result is not None, 'returned None'")
+ lines.append("")
+
+ # Add a basic smoke test
+ lines.extend([
+ f"def _test_callable(ns):",
+ f' """Function is callable"""',
+ f" fn = ns.get('{func_name}')",
+ f" assert callable(fn), '{func_name} is not callable'",
+ "",
+ f"TEST_CASES = [_test_callable, _test_leetcode_cases]",
+ ])
+
+ return "\n".join(lines)
+
+
+def fetch_and_convert(slug: str) -> dict | None:
+ """Fetch a LeetCode problem and convert to Recode format."""
+ problem = fetch_problem(slug)
+ if not problem:
+ return None
+ return convert_to_recode_problem(problem)
+
+
+def write_leetcode_problem(problem: dict, output_dir: Path) -> Path:
+ """Write a LeetCode-sourced problem to a file."""
+ output_dir.mkdir(parents=True, exist_ok=True)
+ filepath = output_dir / problem["filename"]
+
+ tags_str = ", ".join(problem.get("tags", []))
+ lines = [
+ "# ---",
+ f'# description: "{problem["description"][:150]}"',
+ f'# difficulty: {problem.get("difficulty", "medium")}',
+ f"# tags: [{tags_str}]",
+ f'# source: {problem.get("source", "")}',
+ "# ---",
+ "",
+ 'SOLUTION = """',
+ problem["solution"],
+ '""".strip()',
+ "",
+ f'DESCRIPTION = "{problem["description"][:200]}"',
+ ]
+
+ if problem.get("tests"):
+ lines.append("")
+ lines.append(problem["tests"])
+
+ filepath.write_text("\n".join(lines))
+ return filepath
+
+
+def easy_problems(limit: int = 10) -> list[dict]:
+ """Get easy LeetCode problems (good for learning)."""
+ return list_problems(difficulty="Easy", limit=limit)
+
+
+def free_problems() -> list[dict]:
+ """Get the full free LeetCode catalog."""
+ return list_free_problems()
+
+
+def popular_problems(limit: int = 10) -> list[dict]:
+ """Get popular/common LeetCode problems."""
+ popular_slugs = [
+ "two-sum", "valid-parentheses", "merge-two-sorted-lists",
+ "best-time-to-buy-and-sell-stock", "valid-palindrome",
+ "invert-binary-tree", "valid-anagram", "binary-search",
+ "flood-fill", "maximum-subarray",
+ ]
+ problems = []
+ for slug in popular_slugs[:limit]:
+ result = fetch_and_convert(slug)
+ if result:
+ problems.append(result)
+ return problems
diff --git a/modals.py b/modals.py
index 1639e5f..3810b74 100644
--- a/modals.py
+++ b/modals.py
@@ -17,6 +17,8 @@
from textual.screen import ModalScreen
from textual.widgets import Input, Label, ListItem, ListView, Markdown as MarkdownWidget, RichLog, Static
+from recode.runtime import get_runtime
+
RATING_LABELS = {1: "Again", 2: "Hard", 3: "Good", 4: "Easy"}
RATING_DESC = {
1: "forgot it completely",
@@ -364,6 +366,250 @@ def action_rate(self, rating: int) -> None:
self.dismiss(rating)
+class PaperGenerateModal(ModalScreen[dict | None]):
+ """Modal to generate problems from an arXiv paper."""
+ BINDINGS = [
+ Binding("escape", "dismiss", "Cancel"),
+ Binding("enter", "generate", "Generate"),
+ ]
+
+ def __init__(self) -> None:
+ super().__init__()
+ self._generating = False
+
+ def compose(self):
+ with Vertical(id="hint-box"):
+ yield Label("[bold]Generate Problems from arXiv Paper[/bold]", id="hint-title")
+ yield Label("")
+ yield Label("Paste an arXiv URL or paper ID:")
+ yield Input(placeholder="https://arxiv.org/abs/2402.03300", id="paper-url")
+ yield Label("")
+ yield Label("[dim]Number of problems:[/dim]")
+ yield Input(placeholder="3", value="3", id="num-problems")
+ yield Label("")
+ yield Label("[dim]Language (python/julia):[/dim]")
+ yield Input(placeholder="python", value="python", id="language")
+ yield Label("")
+ yield MarkdownWidget("*Press Enter to generate, Esc to cancel*", id="paper-status")
+
+ def action_generate(self) -> None:
+ if self._generating:
+ return
+
+ url_input = self.query_one("#paper-url", Input)
+ url = url_input.value.strip()
+ if not url:
+ self.query_one("#paper-status", MarkdownWidget).update("**Please enter a paper URL**")
+ return
+
+ self._generating = True
+ num_problems = int(self.query_one("#num-problems", Input).value or "3")
+ language = self.query_one("#language", Input).value or "python"
+
+ self.query_one("#paper-status", MarkdownWidget).update("*Fetching paper and generating problems...*")
+
+ # Return config for the caller to handle generation
+ self.dismiss({
+ "url": url,
+ "num_problems": num_problems,
+ "language": language,
+ })
+
+
+class ImportProblemModal(ModalScreen[dict | None]):
+ """Import problems from Exercism or LeetCode."""
+ BINDINGS = [
+ Binding("escape", "dismiss", "Cancel"),
+ Binding("enter", "import_selected", "Import"),
+ ]
+
+ SOURCES = [
+ ("exercism", "🟦 Exercism", "Free exercises with tests, 65+ languages"),
+ ("leetcode", "🟧 LeetCode", "Classic coding problems with test cases"),
+ ]
+
+ EXERCISM_TRACK = "python"
+
+ def __init__(self) -> None:
+ super().__init__()
+ self._selected_source = "exercism"
+ self._items: list[dict] = []
+ self._loading = False
+
+ def compose(self):
+ with Vertical(id="hint-box"):
+ yield Label("[bold]Import Problems[/bold]", id="hint-title")
+ yield Label("")
+ yield Label("[dim]Source:[/dim]")
+ yield ListView(
+ *[ListItem(Label(f"{name} [dim]{desc}[/dim]"), id=f"src-{src}")
+ for src, name, desc in self.SOURCES],
+ id="import-sources",
+ )
+ yield Label("")
+ yield Input(placeholder="Filter (e.g., easy, python, arrays)...", id="import-filter")
+ yield ListView(id="import-problems")
+ yield MarkdownWidget("", id="import-status")
+ yield Label("[dim]Select source → browse problems → Enter to import[/dim]", id="modal-skip")
+
+ def on_mount(self) -> None:
+ # Select first source by default
+ sources = self.query_one("#import-sources", ListView)
+ sources.index = 0
+ self._load_source("exercism")
+
+ def on_list_view_selected(self, event: ListView.Selected) -> None:
+ if event.item is None or event.item.id is None:
+ return
+
+ item_id = event.item.id
+
+ if item_id.startswith("src-"):
+ # Source selected
+ source = item_id[4:]
+ self._selected_source = source
+ self._load_source(source)
+ elif item_id.startswith("prob-"):
+ # Problem selected - import it
+ idx = int(item_id.split("-")[1])
+ if idx < len(self._items):
+ self._import_problem(self._items[idx])
+
+ def _load_source(self, source: str) -> None:
+ self._loading = True
+ self.query_one("#import-status", MarkdownWidget).update("*Loading...*")
+ self.query_one("#import-problems", ListView).clear()
+
+ def _fetch():
+ items = []
+ try:
+ if source == "exercism":
+ from exercism import list_exercises
+
+ practice = list_exercises(self.EXERCISM_TRACK, "practice")
+ concept = list_exercises(self.EXERCISM_TRACK, "concept")
+ items = sorted(
+ practice + concept,
+ key=lambda item: (
+ item.get("difficulty") != "easy",
+ item.get("difficulty") == "hard",
+ item.get("type") != "concept",
+ item.get("name", ""),
+ ),
+ )
+ elif source == "leetcode":
+ from leetcode import free_problems
+
+ items = free_problems()
+ except Exception as e:
+ self.app.call_from_thread(
+ self.query_one("#import-status", MarkdownWidget).update,
+ f"*Error: {e}*"
+ )
+ return
+
+ self.app.call_from_thread(self._display_items, items)
+
+ threading.Thread(target=_fetch, daemon=True).start()
+
+ def _display_items(self, items: list[dict]) -> None:
+ self._items = items
+ self._loading = False
+
+ list_view = self.query_one("#import-problems", ListView)
+ list_view.clear()
+
+ if not items:
+ self.query_one("#import-status", MarkdownWidget).update("*No problems found*")
+ return
+
+ for i, item in enumerate(items):
+ list_view.append(ListItem(Label(self._format_import_item(item)), id=f"prob-{i}"))
+
+ self.query_one("#import-status", MarkdownWidget).update(f"*{len(items)} problems*")
+
+ def _format_import_item(self, item: dict) -> str:
+ name = item.get("name", item.get("title", item.get("slug", "unknown")))
+ diff = item.get("difficulty", "")
+ kind = item.get("type", "")
+ desc = item.get("description", "")[:60]
+
+ diff_icon = {"easy": "🟢", "medium": "🟡", "hard": "🔴"}.get(diff, "")
+ label = f"{diff_icon} {name}"
+ if kind:
+ label += f" [dim]({escape(kind)})[/dim]"
+ if desc:
+ label += f"\n [dim]{escape(desc)}[/dim]"
+ return label
+
+ def _import_problem(self, item: dict) -> None:
+ self.query_one("#import-status", MarkdownWidget).update("*Importing...*")
+
+ def _do_import():
+ try:
+ output_dir = get_runtime().problems_dir / "imported"
+
+ if self._selected_source == "exercism":
+ from exercism import fetch_and_convert, write_exercism_problem
+ slug = item.get("slug", "")
+ problem = fetch_and_convert("python", slug)
+ if problem:
+ path = write_exercism_problem(problem, output_dir)
+ self.app.call_from_thread(self._import_done, path, item)
+ else:
+ self.app.call_from_thread(
+ self.query_one("#import-status", MarkdownWidget).update,
+ "*Failed to fetch exercise*"
+ )
+
+ elif self._selected_source == "leetcode":
+ from leetcode import fetch_and_convert, write_leetcode_problem
+ slug = item.get("slug", "")
+ problem = fetch_and_convert(slug)
+ if problem:
+ path = write_leetcode_problem(problem, output_dir)
+ self.app.call_from_thread(self._import_done, path, item)
+ else:
+ self.app.call_from_thread(
+ self.query_one("#import-status", MarkdownWidget).update,
+ "*Failed to fetch problem (may be premium)*"
+ )
+
+ except Exception as e:
+ self.app.call_from_thread(
+ self.query_one("#import-status", MarkdownWidget).update,
+ f"*Error: {e}*"
+ )
+
+ threading.Thread(target=_do_import, daemon=True).start()
+
+ def _import_done(self, path: Path, item: dict) -> None:
+ name = item.get("name", item.get("title", ""))
+ self.query_one("#import-status", MarkdownWidget).update(
+ f"**Imported: {name}**\n`{path}`"
+ )
+
+ def on_input_changed(self, event: Input.Changed) -> None:
+ if event.input.id == "import-filter":
+ # Filter the current list
+ query = event.value.strip().lower()
+ list_view = self.query_one("#import-problems", ListView)
+ list_view.clear()
+
+ filtered = []
+ for i, item in enumerate(self._items):
+ name = item.get("name", item.get("title", "")).lower()
+ desc = item.get("description", "").lower()
+ diff = item.get("difficulty", "").lower()
+ kind = item.get("type", "").lower()
+
+ if not query or query in name or query in desc or query in diff or query in kind:
+ filtered.append((i, item))
+
+ for orig_i, item in filtered:
+ list_view.append(ListItem(Label(self._format_import_item(item)), id=f"prob-{orig_i}"))
+
+
class CollectionSelectModal(ModalScreen[Path]):
"""Modal to select a problem collection (folder)."""
BINDINGS = [
diff --git a/paper_generator.py b/paper_generator.py
new file mode 100644
index 0000000..c654a87
--- /dev/null
+++ b/paper_generator.py
@@ -0,0 +1,372 @@
+"""
+paper_generator.py — Generate Recode problems from arXiv papers.
+
+Uses the arXiv API for metadata + AI to generate implementation exercises.
+Supports simple mode (whole paper) and detailed mode (section picker).
+"""
+from __future__ import annotations
+
+import json
+import os
+import re
+import urllib.error
+import urllib.request
+import xml.etree.ElementTree as ET
+from dataclasses import dataclass
+from pathlib import Path
+
+
+ARXIV_API = "https://export.arxiv.org/api/query"
+NS = {"a": "http://www.w3.org/2005/Atom"}
+
+
+@dataclass
+class PaperInfo:
+ arxiv_id: str
+ title: str
+ authors: list[str]
+ abstract: str
+ categories: list[str]
+ published: str
+ pdf_url: str
+ abs_url: str
+
+
+def parse_arxiv_url(url_or_id: str) -> str:
+ """
+ Extract arXiv ID from various URL formats or bare ID.
+
+ Accepts:
+ - https://arxiv.org/abs/2402.03300
+ - https://arxiv.org/pdf/2402.03300
+ - arxiv:2402.03300
+ - 2402.03300
+ """
+ # Try URL patterns
+ patterns = [
+ r'arxiv\.org/(?:abs|pdf)/(\d+\.\d+)',
+ r'arxiv:(\d+\.\d+)',
+ r'^(\d+\.\d+)$',
+ ]
+ for pat in patterns:
+ m = re.search(pat, url_or_id.strip())
+ if m:
+ return m.group(1)
+ return url_or_id.strip()
+
+
+def fetch_paper(arxiv_id: str) -> PaperInfo | None:
+ """Fetch paper metadata from arXiv API."""
+ arxiv_id = parse_arxiv_url(arxiv_id)
+ url = f"{ARXIV_API}?id_list={arxiv_id}"
+
+ try:
+ req = urllib.request.Request(url, headers={"User-Agent": "Recode/0.1"})
+ with urllib.request.urlopen(req, timeout=15) as resp:
+ xml_data = resp.read().decode()
+ except Exception:
+ return None
+
+ root = ET.fromstring(xml_data)
+ entry = root.find("a:entry", NS)
+ if entry is None:
+ return None
+
+ title = (entry.find("a:title", NS).text or "").strip().replace("\n", " ")
+ authors = [a.find("a:name", NS).text for a in entry.findall("a:author", NS)]
+ abstract = (entry.find("a:summary", NS).text or "").strip()
+ categories = [c.get("term") for c in entry.findall("a:category", NS) if c.get("term")]
+ published = (entry.find("a:published", NS).text or "")[:10]
+
+ return PaperInfo(
+ arxiv_id=arxiv_id,
+ title=title,
+ authors=authors,
+ abstract=abstract,
+ categories=categories,
+ published=published,
+ pdf_url=f"https://arxiv.org/pdf/{arxiv_id}",
+ abs_url=f"https://arxiv.org/abs/{arxiv_id}",
+ )
+
+
+def extract_sections(paper: PaperInfo) -> list[str]:
+ """
+ Extract section headings from a paper's abstract and content.
+ Since we can't easily parse PDF in-process, we derive likely sections
+ from the paper's category and abstract structure.
+
+ For full content, users can use fetch_content on the PDF URL.
+ """
+ sections = []
+
+ # Common ML paper sections based on abstract structure
+ abstract_lower = paper.abstract.lower()
+
+ section_hints = [
+ ("Introduction", ["introduce", "propose", "motivation", "we present"]),
+ ("Method", ["method", "approach", "framework", "algorithm", "architecture", "propose"]),
+ ("Attention Mechanism", ["attention", "self-attention", "multi-head", "query", "key", "value"]),
+ ("Training", ["train", "optimiz", "loss function", "gradient", "backprop"]),
+ ("Architecture", ["network", "layer", "block", "module", "encoder", "decoder"]),
+ ("Evaluation", ["experiment", "evaluate", "benchmark", "result", "performance"]),
+ ("Mathematical Foundations", ["equation", "formulation", "theorem", "proof"]),
+ ]
+
+ for section_name, keywords in section_hints:
+ if any(kw in abstract_lower for kw in keywords):
+ sections.append(section_name)
+
+ return sections or ["Full Paper"]
+
+
+def generate_problems_from_paper(
+ paper: PaperInfo,
+ sections: list[str] | None = None,
+ num_problems: int = 3,
+ language: str = "python",
+ use_marimo: bool = True,
+) -> list[dict]:
+ """
+ Use AI to generate implementation exercises from a paper.
+
+ Returns list of dicts with keys: filename, description, solution, tests
+ """
+ from ai import ai_call
+
+ section_text = ""
+ if sections:
+ section_text = f"\nFocus on these aspects: {', '.join(sections)}"
+
+ test_instruction = ""
+ if use_marimo:
+ test_instruction = """
+Also include a TEST_CASES list with 2-4 test functions per problem. Each test function
+should accept a namespace dict and raise AssertionError on failure. Name them clearly
+like _test_basic_forward, _test_shape_output, etc.
+"""
+
+ lang_examples = {
+ "python": "Use NumPy for numerical operations. Use clear variable names.",
+ "julia": "Use standard Julia arrays and broadcasting. Use clear variable names.",
+ }
+ lang_note = lang_examples.get(language, lang_examples["python"])
+
+ prompt = f"""\
+You are an ML educator creating coding exercises from a research paper.
+
+Paper: {paper.title}
+Authors: {', '.join(paper.authors[:3])}
+arXiv: {paper.arxiv_id}
+
+Abstract:
+{paper.abstract}
+{section_text}
+
+Generate {num_problems} implementation exercises that help someone deeply understand
+this paper by coding its key components from memory.
+
+{lang_note}
+
+For each problem, provide:
+1. filename: snake_case, descriptive (e.g., "multi-head-attention.py")
+2. description: 1-2 sentence prompt for the student (what to implement)
+3. solution: complete, working {language} code (the reference implementation)
+4. difficulty: easy / medium / hard
+{test_instruction}
+Return the response as a JSON array. Example format:
+[
+ {{
+ "filename": "attention-mechanism.py",
+ "description": "Implement scaled dot-product attention: Attention(Q,K,V) = softmax(QK^T/√d_k)V",
+ "solution": "import numpy as np\\n\\ndef scaled_dot_product_attention(Q, K, V):\\n ...",
+ "difficulty": "medium",
+ "tests": [
+ {{"name": "output shape", "fn_body": "fn = ns['scaled_dot_product_attention']\\nQ = np.random.randn(2, 4, 8)\\nK = np.random.randn(2, 4, 8)\\nV = np.random.randn(2, 4, 8)\\nout = fn(Q, K, V)\\nassert out.shape == (2, 4, 8), f'got {{out.shape}}'"}}
+ ]
+ }}
+]
+
+Return ONLY the JSON array, no other text."""
+
+ response = ai_call(prompt)
+
+ # Extract JSON from response
+ json_match = re.search(r'\[.*\]', response, re.DOTALL)
+ if not json_match:
+ return []
+
+ try:
+ problems = json.loads(json_match.group(0))
+ return problems
+ except json.JSONDecodeError:
+ return []
+
+
+def write_problem_file(
+ problem: dict,
+ output_dir: Path,
+ paper: PaperInfo,
+ use_marimo: bool = False,
+) -> Path:
+ """
+ Write a generated problem to a file in Recode format.
+ """
+ output_dir.mkdir(parents=True, exist_ok=True)
+ filename = problem.get("filename", "generated-problem.py")
+ if not any(filename.endswith(ext) for ext in [".py", ".jl", ".R"]):
+ filename += ".py"
+
+ filepath = output_dir / filename
+ solution = problem.get("solution", "")
+ description = problem.get("description", "")
+ difficulty = problem.get("difficulty", "medium")
+ tests = problem.get("tests", [])
+
+ if use_marimo and tests:
+ content = _format_marimo_problem(solution, description, tests, paper)
+ else:
+ content = _format_standard_problem(solution, description, tests, paper, difficulty)
+
+ filepath.write_text(content)
+ return filepath
+
+
+def _format_standard_problem(
+ solution: str, description: str, tests: list, paper: PaperInfo, difficulty: str
+) -> str:
+ """Format as a standard .py problem file."""
+ lines = []
+
+ # Header comment
+ lines.append(f'"""')
+ lines.append(f'Generated from: {paper.title}')
+ lines.append(f'arXiv: {paper.arxiv_id}')
+ lines.append(f'Difficulty: {difficulty}')
+ lines.append(f'"""')
+ lines.append("")
+
+ # Solution
+ escaped_solution = solution.replace('"""', '\\"\\"\\"')
+ lines.append(f'SOLUTION = """')
+ lines.append(escaped_solution)
+ lines.append(f'""".strip()')
+ lines.append("")
+
+ # Description
+ escaped_desc = description.replace('"', '\\"')
+ lines.append(f'DESCRIPTION = "{escaped_desc}"')
+
+ # Tests (if provided)
+ if tests:
+ lines.append("")
+ lines.append("# ── Test cases ──")
+ lines.append("")
+ for i, test in enumerate(tests):
+ fn_name = f'_test_{test.get("name", f"test_{i}").replace(" ", "_").lower()}'
+ fn_body = test.get("fn_body", "")
+ lines.append(f'def {fn_name}(ns):')
+ lines.append(f' """{test.get("name", f"test_{i}")}"""')
+ for line in fn_body.split("\n"):
+ lines.append(f' {line}')
+ lines.append("")
+
+ test_names = [f'_test_{t.get("name", f"test_{i}").replace(" ", "_").lower()}'
+ for i, t in enumerate(tests)]
+ lines.append(f'TEST_CASES = [{", ".join(test_names)}]')
+
+ return "\n".join(lines)
+
+
+def _format_marimo_problem(
+ solution: str, description: str, tests: list, paper: PaperInfo
+) -> str:
+ """Format as a marimo notebook problem."""
+ # For marimo format, we generate the test execution cell
+ test_checks = []
+ for i, test in enumerate(tests):
+ fn_body = test.get("fn_body", "").replace('"', '\\"')
+ name = test.get("name", f"test_{i}")
+ test_checks.append(f'''
+ # Test: {name}
+ try:
+{chr(10).join(" " + line for line in test.get("fn_body", "").split(chr(10)))}
+ test_results.append(("{name}", True, ""))
+ except AssertionError as e:
+ test_results.append(("{name}", False, str(e)))
+ except Exception as e:
+ test_results.append(("{name}", False, f"{{type(e).__name__}}: {{e}}"))''')
+
+ return f'''"""
+{description}
+
+Generated from: {paper.title}
+arXiv: {paper.arxiv_id}
+"""
+import marimo
+
+app = marimo.App()
+
+
+@app.cell
+def solution_cell():
+ SOLUTION = """
+{solution}
+""".strip()
+ DESCRIPTION = "{description}"
+ return SOLUTION, DESCRIPTION
+
+
+@app.cell
+def user_code_cell(SOLUTION):
+ user_attempt = SOLUTION
+ return user_attempt,
+
+
+@app.cell
+def test_runner(user_attempt):
+ test_results = []
+
+ ns = {{}}
+ try:
+ exec(user_attempt, ns)
+ except SyntaxError as e:
+ test_results.append(("syntax", False, f"Syntax error: {{e}}"))
+ except Exception as e:
+ test_results.append(("exec", False, f"Runtime error: {{e}}"))
+ else:
+{chr(10).join(test_checks)}
+
+ return test_results,
+'''
+
+
+def generate_problems(
+ arxiv_url: str,
+ output_dir: Path,
+ sections: list[str] | None = None,
+ num_problems: int = 3,
+ language: str = "python",
+ use_marimo: bool = True,
+) -> tuple[PaperInfo | None, list[Path]]:
+ """
+ Main entry point: fetch paper, generate problems, write files.
+
+ Returns (paper_info, list_of_written_files).
+ """
+ arxiv_id = parse_arxiv_url(arxiv_url)
+ paper = fetch_paper(arxiv_id)
+ if not paper:
+ return None, []
+
+ problems = generate_problems_from_paper(
+ paper, sections=sections, num_problems=num_problems,
+ language=language, use_marimo=use_marimo,
+ )
+
+ written = []
+ for prob in problems:
+ filepath = write_problem_file(prob, output_dir, paper, use_marimo=use_marimo)
+ written.append(filepath)
+
+ return paper, written
diff --git a/problems/flatten-list.py b/problems/flatten-list.py
new file mode 100644
index 0000000..102d6c0
--- /dev/null
+++ b/problems/flatten-list.py
@@ -0,0 +1,40 @@
+SOLUTION = """
+def flatten(lst):
+ \"\"\"Flatten a nested list of arbitrary depth.\"\"\"
+ result = []
+ for item in lst:
+ if isinstance(item, list):
+ result.extend(flatten(item))
+ else:
+ result.append(item)
+ return result
+""".strip()
+
+DESCRIPTION = "Implement a recursive function to flatten a nested list."
+
+# ── Test cases ──
+
+def _test_simple(ns):
+ fn = ns.get("flatten")
+ assert fn is not None, "flatten function not found"
+ assert fn([1, [2, 3], 4]) == [1, 2, 3, 4], f"got {fn([1, [2, 3], 4])}"
+
+def _test_deep(ns):
+ fn = ns["flatten"]
+ assert fn([1, [2, [3, [4]]]]) == [1, 2, 3, 4], "deeply nested failed"
+
+def _test_empty(ns):
+ fn = ns["flatten"]
+ assert fn([]) == [], "empty list should return []"
+ assert fn([[], [[]]]) == [], "nested empty lists"
+
+def _test_mixed(ns):
+ fn = ns["flatten"]
+ assert fn([1, "a", [2, ["b", [3]]]]) == [1, "a", 2, "b", 3], "mixed types failed"
+
+def _test_single(ns):
+ fn = ns["flatten"]
+ assert fn([1]) == [1], "single element"
+ assert fn([[1]]) == [1], "single nested element"
+
+TEST_CASES = [_test_simple, _test_deep, _test_empty, _test_mixed, _test_single]
diff --git a/problems/matrix-multiply-numpy.py b/problems/matrix-multiply-numpy.py
new file mode 100644
index 0000000..2554337
--- /dev/null
+++ b/problems/matrix-multiply-numpy.py
@@ -0,0 +1,53 @@
+SOLUTION = """
+import numpy as np
+
+def matrix_multiply(A: np.ndarray, B: np.ndarray) -> np.ndarray:
+ \"\"\"Multiply two matrices using NumPy.\"\"\"
+ return np.matmul(A, B)
+""".strip()
+
+DESCRIPTION = "Implement matrix multiplication using NumPy's matmul."
+
+# ── Test cases (exec-based, works without marimo) ──
+# Each test receives the user's exec'd namespace and should
+# raise AssertionError on failure or return True/False.
+
+def _test_basic_mult(ns):
+ """2x2 * 2x2"""
+ fn = ns.get("matrix_multiply")
+ assert fn is not None, "matrix_multiply not found"
+ A = np.array([[1, 2], [3, 4]])
+ B = np.array([[5, 6], [7, 8]])
+ result = fn(A, B)
+ expected = np.array([[19, 22], [43, 50]])
+ assert np.allclose(result, expected), f"got {result}, expected {expected}"
+
+def _test_identity(ns):
+ """A * I = A"""
+ fn = ns["matrix_multiply"]
+ A = np.array([[1, 2, 3], [4, 5, 6]])
+ I = np.eye(3)
+ result = fn(A, I)
+ assert np.allclose(result, A), "A * I should equal A"
+
+def _test_rectangular(ns):
+ """3x2 * 2x4"""
+ fn = ns["matrix_multiply"]
+ A = np.random.randn(3, 2)
+ B = np.random.randn(2, 4)
+ result = fn(A, B)
+ assert result.shape == (3, 4), f"wrong shape: {result.shape}"
+ assert np.allclose(result, np.matmul(A, B))
+
+def _test_type_error(ns):
+ """Incompatible shapes should raise"""
+ fn = ns["matrix_multiply"]
+ A = np.array([[1, 2]])
+ B = np.array([[1, 2, 3]])
+ try:
+ fn(A, B)
+ assert False, "should have raised an error for incompatible shapes"
+ except (ValueError, RuntimeError):
+ pass # expected
+
+TEST_CASES = [_test_basic_mult, _test_identity, _test_rectangular, _test_type_error]
diff --git a/problems/sigmoid.mo.py b/problems/sigmoid.mo.py
new file mode 100644
index 0000000..f5ee95e
--- /dev/null
+++ b/problems/sigmoid.mo.py
@@ -0,0 +1,120 @@
+"""
+Sigmoid function — marimo notebook format for Recode.
+
+This is a Recode problem with reactive test execution.
+The user's code is injected into `user_attempt` and all test cells
+re-run automatically when it changes.
+"""
+import marimo
+
+app = marimo.App()
+
+
+# === Reference Solution ===
+@app.cell
+def solution_cell():
+ """Reference solution — what the student is trying to remember."""
+ SOLUTION = '''
+import numpy as np
+
+def sigmoid(x):
+ """Compute the sigmoid function."""
+ return 1.0 / (1.0 + np.exp(-x))
+'''
+ DESCRIPTION = "Implement the sigmoid activation function using NumPy."
+ return SOLUTION, DESCRIPTION
+
+
+# === User's Code ===
+@app.cell
+def user_code_cell(SOLUTION):
+ """
+ The user's attempt. At runtime, Recode overrides `user_attempt`
+ with the student's code via app.run(defs={"user_attempt": user_code}).
+ """
+ user_attempt = SOLUTION # default: reference solution
+ return user_attempt,
+
+
+# === Test Execution ===
+@app.cell
+def test_runner(user_attempt):
+ """Execute user's code and run tests."""
+ import numpy as np
+
+ # Execute user's code in isolated namespace
+ ns = {}
+ try:
+ exec(user_attempt, ns)
+ except SyntaxError as e:
+ test_results = [("syntax check", False, f"Syntax error: {e}")]
+ except Exception as e:
+ test_results = [("exec", False, f"Runtime error: {e}")]
+ else:
+ test_results = []
+ sigmoid = ns.get("sigmoid")
+
+ if sigmoid is None:
+ test_results.append(("sigmoid function exists", False, "Function not found in code"))
+ else:
+ # Test 1: sigmoid(0) == 0.5
+ try:
+ out = sigmoid(0)
+ ok = abs(float(out) - 0.5) < 1e-6
+ test_results.append(("sigmoid(0) == 0.5", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(0) == 0.5", False, str(e)))
+
+ # Test 2: sigmoid(large positive) → 1
+ try:
+ out = float(sigmoid(1000))
+ ok = abs(out - 1.0) < 1e-4
+ test_results.append(("sigmoid(1000) ≈ 1.0", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(1000) ≈ 1.0", False, str(e)))
+
+ # Test 3: sigmoid(large negative) → 0
+ try:
+ out = float(sigmoid(-1000))
+ ok = abs(out - 0.0) < 1e-4
+ test_results.append(("sigmoid(-1000) ≈ 0.0", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(-1000) ≈ 0.0", False, str(e)))
+
+ # Test 4: symmetry: sigmoid(-x) = 1 - sigmoid(x)
+ try:
+ x = 2.5
+ ok = abs(float(sigmoid(-x)) - (1 - float(sigmoid(x)))) < 1e-6
+ test_results.append(("sigmoid(-x) == 1 - sigmoid(x)", ok, ""))
+ except Exception as e:
+ test_results.append(("sigmoid(-x) == 1 - sigmoid(x)", False, str(e)))
+
+ # Test 5: vectorized input
+ try:
+ out = sigmoid(np.array([0, 1, -1]))
+ ok = hasattr(out, "__len__") and len(out) == 3
+ test_results.append(("accepts array input", ok, f"shape: {getattr(out, 'shape', 'N/A')}"))
+ except Exception as e:
+ test_results.append(("accepts array input", False, str(e)))
+
+ return test_results,
+
+
+# === Test Summary Display (marimo UI — optional) ===
+@app.cell
+def display_cell(test_results):
+ """Show test results in the notebook UI when viewed in marimo."""
+ import marimo as mo
+
+ passed = sum(1 for _, p, _ in test_results if p)
+ total = len(test_results)
+
+ rows = []
+ for name, passed_flag, detail in test_results:
+ icon = "✅" if passed_flag else "❌"
+ detail_str = f" — {detail}" if detail else ""
+ rows.append(f"{icon} **{name}**{detail_str}")
+
+ status = "🟢 All passed!" if passed == total else f"🔴 {passed}/{total} passed"
+ mo.md(f"## Test Results\n{status}\n\n" + "\n".join(rows))
+ return
diff --git a/problems_utils.py b/problems_utils.py
index ef34a6a..cc10875 100644
--- a/problems_utils.py
+++ b/problems_utils.py
@@ -5,6 +5,7 @@
import difflib
import importlib.util
+import re
import sqlite3
from datetime import datetime
from pathlib import Path
@@ -15,6 +16,90 @@
# Supported problem file extensions
CODE_EXTENSIONS = {".py", ".jl", ".R"}
+# Marimo notebook detection: files with .mo.py or .mo.jl etc. in stem
+MARIMO_MARKER = ".mo"
+
+
+def parse_frontmatter(text: str) -> tuple[dict, str]:
+ """
+ Parse YAML-like frontmatter from the start of a file.
+
+ Format:
+ # ---
+ # key: value
+ # tags: [a, b, c]
+ # ---
+
+
+ Returns (metadata_dict, remaining_text).
+ Falls back to ({}, text) if no frontmatter found.
+ """
+ lines = text.splitlines()
+
+ # Check for frontmatter start (# --- or ---)
+ if not lines:
+ return {}, text
+
+ first = lines[0].strip()
+ if first not in ("# ---", "---"):
+ return {}, text
+
+ # Determine comment style
+ is_commented = first.startswith("#")
+ delimiter = "# ---" if is_commented else "---"
+
+ # Find the closing ---
+ end_idx = None
+ for i in range(1, len(lines)):
+ if lines[i].strip() == delimiter:
+ end_idx = i
+ break
+
+ if end_idx is None:
+ return {}, text
+
+ # Parse metadata lines
+ meta = {}
+ for line in lines[1:end_idx]:
+ stripped = line.strip()
+ if is_commented:
+ stripped = re.sub(r'^#\s*', '', stripped)
+
+ if not stripped or ':' not in stripped:
+ continue
+
+ key, _, value = stripped.partition(':')
+ key = key.strip()
+ value = value.strip()
+
+ # Parse lists: [a, b, c]
+ if value.startswith('[') and value.endswith(']'):
+ items = [v.strip().strip('"').strip("'") for v in value[1:-1].split(',')]
+ meta[key] = [v for v in items if v]
+ # Parse quoted strings
+ elif value.startswith('"') and value.endswith('"'):
+ meta[key] = value[1:-1]
+ elif value.startswith("'") and value.endswith("'"):
+ meta[key] = value[1:-1]
+ # Parse booleans
+ elif value.lower() in ('true', 'yes'):
+ meta[key] = True
+ elif value.lower() in ('false', 'no'):
+ meta[key] = False
+ # Parse numbers
+ else:
+ try:
+ if '.' in value:
+ meta[key] = float(value)
+ else:
+ meta[key] = int(value)
+ except ValueError:
+ meta[key] = value
+
+ # Remaining text after frontmatter
+ remaining = '\n'.join(lines[end_idx + 1:])
+ return meta, remaining
+
def _is_code_file(p: Path) -> bool:
return p.is_file() and p.suffix in CODE_EXTENSIONS
@@ -71,27 +156,66 @@ def get_problem_id(problem_path: Path, root: Path) -> str:
def load_problem_meta(path: Path) -> dict:
- """Load SOLUTION and DESCRIPTION from a problem file.
-
- For .py files, tries to import and read SOLUTION/DESCRIPTION variables.
- For other file types (.jl, .R, etc.), reads raw text as the solution.
+ """Load metadata from a problem file.
+
+ Supports:
+ - YAML frontmatter (# --- ... # ---) with description, difficulty, tags, source, etc.
+ - Legacy SOLUTION/DESCRIPTION variables in .py files
+ - Raw text for non-Python files
+
+ Returns dict with keys: solution, description, difficulty, tags, source, prerequisites,
+ and any other frontmatter fields.
"""
- # Non-Python files: just read raw text
+ raw_text = path.read_text()
+
+ # Parse frontmatter
+ meta, remaining = parse_frontmatter(raw_text)
+
+ # Non-Python files: use frontmatter + raw text
if path.suffix != ".py":
- return {"solution": path.read_text(), "description": ""}
-
+ result = {
+ "solution": remaining.strip() if remaining.strip() else raw_text,
+ "description": meta.get("description", ""),
+ "difficulty": meta.get("difficulty", ""),
+ "tags": meta.get("tags", []),
+ "source": meta.get("source", ""),
+ "prerequisites": meta.get("prerequisites", []),
+ }
+ # Include any extra frontmatter fields
+ for k, v in meta.items():
+ if k not in result:
+ result[k] = v
+ return result
+
+ # Python files: try to import for SOLUTION/DESCRIPTION (legacy)
+ # Use remaining text (after frontmatter) for import
spec = importlib.util.spec_from_file_location("_prob", path)
if spec is None or spec.loader is None:
- return {"solution": path.read_text(), "description": ""}
+ return {"solution": raw_text, "description": meta.get("description", "")}
+
mod = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(mod) # type: ignore[union-attr]
except Exception:
pass
- return {
- "solution": getattr(mod, "SOLUTION", path.read_text()),
- "description": getattr(mod, "DESCRIPTION", ""),
+
+ # Frontmatter takes precedence, fallback to module globals, then raw text
+ solution = getattr(mod, "SOLUTION", remaining.strip() or raw_text)
+ description = meta.get("description", "") or getattr(mod, "DESCRIPTION", "")
+
+ result = {
+ "solution": solution,
+ "description": description,
+ "difficulty": meta.get("difficulty", getattr(mod, "DIFFICULTY", "")),
+ "tags": meta.get("tags", getattr(mod, "TAGS", [])),
+ "source": meta.get("source", getattr(mod, "SOURCE", "")),
+ "prerequisites": meta.get("prerequisites", []),
}
+ # Include any extra frontmatter fields
+ for k, v in meta.items():
+ if k not in result:
+ result[k] = v
+ return result
def build_side_by_side(ref_code: str, user_code: str) -> Table:
@@ -193,3 +317,66 @@ def max_rating_for(attempts: int) -> int:
if attempts == 2: return 3
if attempts == 3: return 2
return 1
+
+
+def is_marimo_problem(path: Path) -> bool:
+ """Check if a problem file is a marimo notebook (.mo.py, .mo.jl, etc.)."""
+ return MARIMO_MARKER in path.stem
+
+
+def has_test_cases(problem_path: Path) -> bool:
+ """
+ Check if a problem has test cases defined.
+ Works for both marimo notebooks and regular .py files with TEST_CASES.
+ """
+ if is_marimo_problem(problem_path):
+ return True
+
+ if problem_path.suffix != ".py":
+ return False
+
+ try:
+ spec = importlib.util.spec_from_file_location("_prob_check", problem_path)
+ if spec and spec.loader:
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
+ return hasattr(mod, "TEST_CASES")
+ except Exception:
+ pass
+ return False
+
+
+def problem_badges(path: Path) -> list[tuple[str, str]]:
+ """
+ Return display badges for a problem (icon, tooltip).
+ E.g., [("🧪", "has tests"), ("📓", "marimo notebook"), ("⭐", "medium")]
+ """
+ badges = []
+ if is_marimo_problem(path):
+ badges.append(("📓", "marimo notebook"))
+ if has_test_cases(path):
+ badges.append(("🧪", "has tests"))
+ if path.suffix == ".jl":
+ badges.append(("🟣", "Julia"))
+ elif path.suffix == ".R":
+ badges.append(("🔵", "R"))
+
+ # Add difficulty badge from metadata
+ meta = load_problem_meta(path)
+ difficulty = meta.get("difficulty", "")
+ if difficulty:
+ diff_icons = {"easy": ("🟢", "easy"), "medium": ("🟡", "medium"), "hard": ("🔴", "hard")}
+ if difficulty.lower() in diff_icons:
+ badges.append(diff_icons[difficulty.lower()])
+
+ # Add source badge
+ source = meta.get("source", "")
+ if source:
+ if source.startswith("leetcode"):
+ badges.append(("🟧", "LeetCode"))
+ elif source.startswith("exercism"):
+ badges.append(("🟦", "Exercism"))
+ elif source.startswith("hf://") or "huggingface" in source:
+ badges.append(("🤗", "HuggingFace"))
+
+ return badges
diff --git a/pyproject.toml b/pyproject.toml
index a08c513..6245196 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,10 +1,95 @@
+[build-system]
+requires = ["setuptools>=69", "wheel"]
+build-backend = "setuptools.build_meta"
+
[project]
-name = "recode"
+name = "recode-cli"
version = "0.1.0"
-description = "Add your description here"
+description = "Terminal spaced repetition for coding problems and reference solutions"
readme = "README.md"
requires-python = ">=3.10"
+license = "MIT"
+license-files = ["LICENSE"]
+authors = [
+ { name = "Ever" }
+]
+keywords = [
+ "cli",
+ "coding",
+ "spaced-repetition",
+ "textual",
+ "tui"
+]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Environment :: Console",
+ "Environment :: Console :: Curses",
+ "Intended Audience :: Developers",
+ "Operating System :: MacOS",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Topic :: Education",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Terminals"
+]
dependencies = [
- "pylatexenc>=2.10",
- "textual>=8.0.0",
+ "google-genai>=0.8.0",
+ "marimo>=0.11.0",
+ "platformdirs>=4.2.0",
+ "pylatexenc>=2.10",
+ "python-dotenv>=1.0.0",
+ "textual>=8.0.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/ever-oli/recode"
+Repository = "https://github.com/ever-oli/recode"
+Issues = "https://github.com/ever-oli/recode/issues"
+
+[project.scripts]
+recode = "recode.cli:main"
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=8.3.5",
+]
+
+[tool.setuptools]
+include-package-data = true
+packages = [
+ "recode",
+ "recode.problems",
+ "recode.problems.TensorPoly",
+ "recode.problems.TensorPoly.Julia",
+ "recode.problems.TensorPoly.MLX",
+ "recode.problems.TensorPoly.R",
+ "recode.problems.TensorPoly.numpy",
+ "recode.problems.TensorPoly.pytorch",
+]
+py-modules = [
+ "ai",
+ "app",
+ "db",
+ "exercism",
+ "leetcode",
+ "modals",
+ "paper_generator",
+ "problems_utils",
+ "test_runner",
+ "themes",
+]
+
+[tool.setuptools.package-data]
+recode = [
+ "problems/**/*.R",
+ "problems/**/*.jl",
+ "problems/**/*.md",
+ "problems/**/*.py",
]
+
+[tool.pytest.ini_options]
+testpaths = ["tests"]
+python_files = ["test_*.py"]
diff --git a/recode/__init__.py b/recode/__init__.py
new file mode 100644
index 0000000..a05eb9a
--- /dev/null
+++ b/recode/__init__.py
@@ -0,0 +1,3 @@
+__all__ = ["__version__"]
+
+__version__ = "0.1.0"
diff --git a/recode/__main__.py b/recode/__main__.py
new file mode 100644
index 0000000..d9b18db
--- /dev/null
+++ b/recode/__main__.py
@@ -0,0 +1,5 @@
+from __future__ import annotations
+
+from recode.cli import main
+
+raise SystemExit(main())
diff --git a/recode/cli.py b/recode/cli.py
new file mode 100644
index 0000000..260ac75
--- /dev/null
+++ b/recode/cli.py
@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+import argparse
+from pathlib import Path
+
+from recode import __version__
+from recode.runtime import doctor_report, prepare_runtime
+
+
+def build_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ prog="recode",
+ description="Terminal spaced repetition for coding problems.",
+ )
+ parser.add_argument("--version", action="store_true", help="print the installed recode version and exit")
+ parser.add_argument("--paths", action="store_true", help="print resolved runtime paths and exit")
+ parser.add_argument("--doctor", action="store_true", help="print runtime diagnostics and exit")
+ parser.add_argument("--problems-dir", type=Path, help="override the writable problems directory")
+ parser.add_argument("--db-path", type=Path, help="override the SQLite database path")
+ parser.add_argument("--editor", help="override the editor command for this run")
+ return parser
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = build_parser()
+ args = parser.parse_args(argv)
+
+ if args.version:
+ print(__version__)
+ return 0
+
+ runtime = prepare_runtime(
+ problems_dir=args.problems_dir,
+ db_path=args.db_path,
+ editor=args.editor,
+ )
+
+ if args.paths:
+ print(f"config_dir={runtime.config_dir}")
+ print(f"data_dir={runtime.data_dir}")
+ print(f"state_dir={runtime.state_dir}")
+ print(f"problems_dir={runtime.problems_dir}")
+ print(f"bundled_problems_dir={runtime.bundled_problems_dir}")
+ print(f"db_path={runtime.db_path}")
+ return 0
+
+ if args.doctor:
+ print(doctor_report(runtime))
+ return 0
+
+ from app import main as app_main
+
+ app_main(
+ problems_dir=runtime.problems_dir,
+ db_path=runtime.db_path,
+ editor=runtime.editor,
+ )
+ return 0
diff --git a/recode/problems/TensorPoly/Julia/README.md b/recode/problems/TensorPoly/Julia/README.md
new file mode 100644
index 0000000..e287f03
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/README.md
@@ -0,0 +1,3 @@
+# Julia Implementations
+
+Julia implementations of TensorTonic solutions. Focuses on multiple dispatch and performance.
diff --git a/recode/problems/TensorPoly/Julia/__init__.py b/recode/problems/TensorPoly/Julia/__init__.py
new file mode 100644
index 0000000..827966a
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/__init__.py
@@ -0,0 +1 @@
+"""Bundled Julia TensorPoly problems."""
diff --git a/recode/problems/TensorPoly/Julia/adam-optimizer.jl b/recode/problems/TensorPoly/Julia/adam-optimizer.jl
new file mode 100644
index 0000000..649738b
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/adam-optimizer.jl
@@ -0,0 +1,12 @@
+function adam_step(param, grad, m, v, t;
+ lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8)
+ m_new = beta1 .* m .+ (1 - beta1) .* grad
+ v_new = beta2 .* v .+ (1 - beta2) .* (grad .^ 2)
+
+ m_hat = m_new ./ (1 - beta1 ^ t)
+ v_hat = v_new ./ (1 - beta2 ^ t)
+
+ param_new = param .- lr .* m_hat ./ (sqrt.(v_hat) .+ eps)
+
+ return (param_new = param_new, m_new = m_new, v_new = v_new)
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-augmentation.jl b/recode/problems/TensorPoly/Julia/alexnet-augmentation.jl
new file mode 100644
index 0000000..2575558
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-augmentation.jl
@@ -0,0 +1,15 @@
+function random_crop(image, crop_size::Int=224)
+ h = size(image, 1)
+ w = size(image, 2)
+ top = rand(1:(h - crop_size + 1))
+ left = rand(1:(w - crop_size + 1))
+ return image[top:(top + crop_size - 1), left:(left + crop_size - 1), :]
+end
+
+
+function random_horizontal_flip(image, p::Float64=0.5)
+ if rand() < p
+ return image[:, end:-1:1, :]
+ end
+ return image
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-conv-layers.jl b/recode/problems/TensorPoly/Julia/alexnet-conv-layers.jl
new file mode 100644
index 0000000..91abc01
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-conv-layers.jl
@@ -0,0 +1,7 @@
+function alexnet_conv1(image)
+ batch_size = size(image, 1)
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return zeros(batch_size, output_h, output_w, num_filters)
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-dropout.jl b/recode/problems/TensorPoly/Julia/alexnet-dropout.jl
new file mode 100644
index 0000000..cba366b
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-dropout.jl
@@ -0,0 +1,7 @@
+function dropout(x, p::Float64=0.5, training::Bool=true)
+ if !training || p == 0
+ return x
+ end
+ mask = rand(size(x)) .< (1 - p)
+ return (x .* mask) ./ (1 - p)
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-lrn.jl b/recode/problems/TensorPoly/Julia/alexnet-lrn.jl
new file mode 100644
index 0000000..47ca233
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-lrn.jl
@@ -0,0 +1,15 @@
+function local_response_normalization(x, k::Float64=2, n::Int=5, alpha::Float64=1e-4, beta::Float64=0.75)
+ batch_size, h, w, c = size(x)
+ squared_x = x .^ 2
+ pad = n ÷ 2
+ padded_sq = zeros(batch_size, h, w, c + 2 * pad)
+ padded_sq[:, :, :, (pad + 1):(pad + c)] .= squared_x
+
+ sum_sq = zeros(batch_size, h, w, c)
+ for i in 1:n
+ sum_sq .+= padded_sq[:, :, :, i:(i + c - 1)]
+ end
+
+ scale = (k .+ alpha .* sum_sq) .^ beta
+ return x ./ scale
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-pooling.jl b/recode/problems/TensorPoly/Julia/alexnet-pooling.jl
new file mode 100644
index 0000000..3b1a61e
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-pooling.jl
@@ -0,0 +1,6 @@
+function max_pool2d(x, kernel_size::Int=3, stride::Int=2)
+ batch_size, h_in, w_in, channels = size(x)
+ h_out = (h_in - kernel_size) ÷ stride + 1
+ w_out = (w_in - kernel_size) ÷ stride + 1
+ return zeros(batch_size, h_out, w_out, channels)
+end
diff --git a/recode/problems/TensorPoly/Julia/alexnet-relu.jl b/recode/problems/TensorPoly/Julia/alexnet-relu.jl
new file mode 100644
index 0000000..17403f3
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/alexnet-relu.jl
@@ -0,0 +1 @@
+relu(x) = max.(0, x)
diff --git a/recode/problems/TensorPoly/Julia/bert-fine-tuning.jl b/recode/problems/TensorPoly/Julia/bert-fine-tuning.jl
new file mode 100644
index 0000000..a3d2d72
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-fine-tuning.jl
@@ -0,0 +1,74 @@
+mutable struct MockBertEncoder
+ hidden_size::Int
+ num_layers::Int
+ layers::Vector
+ layer_frozen::Vector{Bool}
+end
+
+function MockBertEncoder(hidden_size::Int=768, num_layers::Int=12)
+ layers = [randn(hidden_size, hidden_size) .* 0.01 for _ in 1:num_layers]
+ layer_frozen = fill(false, num_layers)
+ MockBertEncoder(hidden_size, num_layers, layers, layer_frozen)
+end
+
+function freeze_layers!(encoder::MockBertEncoder, layer_indices)
+ for idx in layer_indices
+ if 1 <= idx <= encoder.num_layers
+ encoder.layer_frozen[idx] = true
+ end
+ end
+end
+
+function unfreeze_all!(encoder::MockBertEncoder)
+ encoder.layer_frozen .= false
+end
+
+function forward(encoder::MockBertEncoder, embeddings)
+ x = embeddings
+ for layer in encoder.layers
+ x = x * layer .+ x
+ end
+ x
+end
+
+mutable struct BertForSequenceClassification
+ encoder::MockBertEncoder
+ classifier
+ bias
+ freeze_bert::Bool
+end
+
+function BertForSequenceClassification(hidden_size::Int, num_labels::Int; freeze_bert::Bool=false)
+ encoder = MockBertEncoder(hidden_size)
+ classifier = randn(hidden_size, num_labels) .* 0.02
+ bias = zeros(num_labels)
+ model = BertForSequenceClassification(encoder, classifier, bias, freeze_bert)
+ if freeze_bert
+ freeze_layers!(model.encoder, 1:12)
+ end
+ model
+end
+
+function forward(model::BertForSequenceClassification, embeddings)
+ hidden_states = forward(model.encoder, embeddings)
+ cls_representation = hidden_states[:, 1, :]
+ cls_representation * model.classifier .+ model.bias
+end
+
+mutable struct BertForTokenClassification
+ encoder::MockBertEncoder
+ classifier
+ bias
+end
+
+function BertForTokenClassification(hidden_size::Int, num_labels::Int)
+ encoder = MockBertEncoder(hidden_size)
+ classifier = randn(hidden_size, num_labels) .* 0.02
+ bias = zeros(num_labels)
+ BertForTokenClassification(encoder, classifier, bias)
+end
+
+function forward(model::BertForTokenClassification, embeddings)
+ hidden_states = forward(model.encoder, embeddings)
+ hidden_states * model.classifier .+ model.bias
+end
diff --git a/recode/problems/TensorPoly/Julia/bert-masked-lm.jl b/recode/problems/TensorPoly/Julia/bert-masked-lm.jl
new file mode 100644
index 0000000..bf69f0d
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-masked-lm.jl
@@ -0,0 +1,42 @@
+using Random
+
+function apply_mlm_mask(token_ids, vocab_size::Int; mask_token_id::Int=103, mask_prob::Float64=0.15, seed=nothing)
+ if seed !== nothing
+ Random.seed!(seed)
+ end
+
+ masked_ids = copy(token_ids)
+ labels = fill(-100, size(token_ids))
+
+ mask_eligible = .!(token_ids .== 101 .| token_ids .== 102 .| token_ids .== 0)
+ probability_matrix = rand(size(token_ids))
+ mask_indices = (probability_matrix .< mask_prob) .& mask_eligible
+
+ labels[mask_indices] = token_ids[mask_indices]
+
+ random_dispatch = rand(size(token_ids))
+ indices_replaced = mask_indices .& (random_dispatch .< 0.8)
+ masked_ids[indices_replaced] .= mask_token_id
+
+ indices_random = mask_indices .& (random_dispatch .>= 0.8) .& (random_dispatch .< 0.9)
+ masked_ids[indices_random] .= rand(0:(vocab_size - 1), sum(indices_random))
+
+ return (masked_ids = masked_ids, labels = labels, mask_indices = mask_indices)
+end
+
+mutable struct MLMHead
+ hidden_size::Int
+ vocab_size::Int
+ W
+ b
+end
+
+function MLMHead(hidden_size::Int, vocab_size::Int)
+ W = randn(hidden_size, vocab_size) .* 0.02
+ b = zeros(vocab_size)
+ MLMHead(hidden_size, vocab_size, W, b)
+end
+
+function forward(head::MLMHead, hidden_states)
+ hidden_states * head.W .+ head.b
+end
diff --git a/recode/problems/TensorPoly/Julia/bert-nsp.jl b/recode/problems/TensorPoly/Julia/bert-nsp.jl
new file mode 100644
index 0000000..9c8edbb
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-nsp.jl
@@ -0,0 +1,54 @@
+using Random
+
+function create_nsp_examples(documents, num_examples::Int; seed=nothing)
+ if seed !== nothing
+ Random.seed!(seed)
+ end
+
+ examples = []
+ while length(examples) < num_examples
+ doc_idx = rand(1:length(documents))
+ document = documents[doc_idx]
+
+ if length(document) < 2
+ continue
+ end
+
+ sent_idx = rand(1:(length(document) - 1))
+
+ if rand() < 0.5
+ push!(examples, (document[sent_idx], document[sent_idx + 1], 1))
+ else
+ if length(documents) > 1
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx
+ random_doc_idx = rand(1:length(documents))
+ end
+ random_document = documents[random_doc_idx]
+ else
+ random_document = document
+ end
+ random_sent_idx = rand(1:length(random_document))
+ push!(examples, (document[sent_idx], random_document[random_sent_idx], 0))
+ end
+ end
+
+ examples[1:num_examples]
+end
+
+mutable struct NSPHead
+ W
+ b
+end
+
+function NSPHead(hidden_size::Int)
+ W = randn(hidden_size, 2) .* 0.02
+ b = zeros(2)
+ NSPHead(W, b)
+end
+
+function forward(head::NSPHead, cls_hidden)
+ cls_hidden * head.W .+ head.b
+end
+
+softmax(x) = exp.(x .- maximum(x, dims=2)) ./ sum(exp.(x .- maximum(x, dims=2)), dims=2)
diff --git a/recode/problems/TensorPoly/Julia/bert-pooler.jl b/recode/problems/TensorPoly/Julia/bert-pooler.jl
new file mode 100644
index 0000000..7660d5f
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-pooler.jl
@@ -0,0 +1,42 @@
+tanh_act(x) = tanh.(x)
+
+mutable struct BertPooler
+ hidden_size::Int
+ W
+ b
+end
+
+function BertPooler(hidden_size::Int)
+ W = randn(hidden_size, hidden_size) .* 0.02
+ b = zeros(hidden_size)
+ BertPooler(hidden_size, W, b)
+end
+
+function forward(pooler::BertPooler, hidden_states)
+ cls_token_tensor = hidden_states[:, 1, :]
+ pooled_output = cls_token_tensor * pooler.W .+ pooler.b
+ tanh_act(pooled_output)
+end
+
+mutable struct SequenceClassifier
+ pooler::BertPooler
+ dropout_prob::Float64
+ classifier
+ bias
+end
+
+function SequenceClassifier(hidden_size::Int, num_classes::Int; dropout_prob::Float64=0.1)
+ pooler = BertPooler(hidden_size)
+ classifier = randn(hidden_size, num_classes) .* 0.02
+ bias = zeros(num_classes)
+ SequenceClassifier(pooler, dropout_prob, classifier, bias)
+end
+
+function forward(model::SequenceClassifier, hidden_states; training::Bool=true)
+ pooled_output = forward(model.pooler, hidden_states)
+ if training
+ mask = rand(size(pooled_output)) .> model.dropout_prob
+ pooled_output = (pooled_output .* mask) ./ (1.0 - model.dropout_prob)
+ end
+ pooled_output * model.classifier .+ model.bias
+end
diff --git a/recode/problems/TensorPoly/Julia/bert-segment-embedding.jl b/recode/problems/TensorPoly/Julia/bert-segment-embedding.jl
new file mode 100644
index 0000000..a169a86
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-segment-embedding.jl
@@ -0,0 +1,22 @@
+mutable struct BertEmbeddings
+ hidden_size::Int
+ token_embeddings
+ position_embeddings
+ segment_embeddings
+end
+
+function BertEmbeddings(vocab_size::Int, max_position::Int, hidden_size::Int)
+ token_embeddings = randn(vocab_size, hidden_size) .* 0.02
+ position_embeddings = randn(max_position, hidden_size) .* 0.02
+ segment_embeddings = randn(2, hidden_size) .* 0.02
+ BertEmbeddings(hidden_size, token_embeddings, position_embeddings, segment_embeddings)
+end
+
+function forward(emb::BertEmbeddings, token_ids, segment_ids)
+ tok_emb = emb.token_embeddings[token_ids .+ 1, :]
+ seq_len = size(token_ids, 2)
+ positions = 1:seq_len
+ pos_emb = emb.position_embeddings[positions, :]
+ seg_emb = emb.segment_embeddings[segment_ids .+ 1, :]
+ tok_emb .+ pos_emb .+ seg_emb
+end
diff --git a/recode/problems/TensorPoly/Julia/bert-wordpiece.jl b/recode/problems/TensorPoly/Julia/bert-wordpiece.jl
new file mode 100644
index 0000000..ecbfdac
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/bert-wordpiece.jl
@@ -0,0 +1,58 @@
+mutable struct WordPieceTokenizer
+ vocab::Dict{String, Int}
+ unk_token::String
+ max_word_len::Int
+end
+
+function WordPieceTokenizer(vocab::Dict{String, Int}; unk_token::String="[UNK]", max_word_len::Int=100)
+ WordPieceTokenizer(vocab, unk_token, max_word_len)
+end
+
+function tokenize(tokenizer::WordPieceTokenizer, text::String)
+ tokens = String[]
+ for word in split(lowercase(text))
+ append!(tokens, tokenize_word(tokenizer, word))
+ end
+ tokens
+end
+
+function tokenize_word(tokenizer::WordPieceTokenizer, word::String)
+ if length(word) > tokenizer.max_word_len
+ return [tokenizer.unk_token]
+ end
+
+ output_tokens = String[]
+ start = 1
+ is_bad = false
+
+ while start <= lastindex(word)
+ end_idx = lastindex(word)
+ cur_substr = nothing
+
+ while start <= end_idx
+ substr = word[start:end_idx]
+ if start > 1
+ substr = "##" * substr
+ end
+ if haskey(tokenizer.vocab, substr)
+ cur_substr = substr
+ break
+ end
+ end_idx -= 1
+ end
+
+ if cur_substr === nothing
+ is_bad = true
+ break
+ end
+
+ push!(output_tokens, cur_substr)
+ start = end_idx + 1
+ end
+
+ if is_bad
+ return [tokenizer.unk_token]
+ end
+
+ output_tokens
+end
diff --git a/recode/problems/TensorPoly/Julia/binomial-pmf-cdf.jl b/recode/problems/TensorPoly/Julia/binomial-pmf-cdf.jl
new file mode 100644
index 0000000..d65d12e
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/binomial-pmf-cdf.jl
@@ -0,0 +1,16 @@
+function binomial_pmf_cdf(n, p, k)
+ if p < 0 || p > 1
+ error("p must be in [0, 1]")
+ end
+ if k < 0 || k > n
+ error("k must be in [0, n]")
+ end
+
+ pmf = binomial(n, k) * (p ^ k) * ((1 - p) ^ (n - k))
+ cdf = 0.0
+ for i in 0:k
+ cdf += binomial(n, i) * (p ^ i) * ((1 - p) ^ (n - i))
+ end
+
+ return (pmf = float(pmf), cdf = float(cdf))
+end
diff --git a/recode/problems/TensorPoly/Julia/compute-advantage.jl b/recode/problems/TensorPoly/Julia/compute-advantage.jl
new file mode 100644
index 0000000..790e40c
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/compute-advantage.jl
@@ -0,0 +1,12 @@
+function compute_advantage(states, rewards, V, gamma)
+ T = length(rewards)
+ advantages = zeros(Float64, T)
+
+ G = 0.0
+ for t in T:-1:1
+ G = rewards[t] + gamma * G
+ advantages[t] = G - V[states[t]]
+ end
+
+ return advantages
+end
diff --git a/recode/problems/TensorPoly/Julia/ddpm-forward.jl b/recode/problems/TensorPoly/Julia/ddpm-forward.jl
new file mode 100644
index 0000000..7d85ade
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/ddpm-forward.jl
@@ -0,0 +1,17 @@
+function get_alpha_bar(betas)
+ alphas = 1.0 .- betas
+ cumprod(alphas)
+end
+
+function forward_diffusion(x_0, t::Int, betas)
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t]
+
+ epsilon = randn(size(x_0))
+
+ sqrt_alpha_bar_t = sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t .* x_0 .+ sqrt_one_minus_alpha_bar_t .* epsilon
+ return (x_t = x_t, epsilon = epsilon)
+end
diff --git a/recode/problems/TensorPoly/Julia/ddpm-loss.jl b/recode/problems/TensorPoly/Julia/ddpm-loss.jl
new file mode 100644
index 0000000..7eb17e8
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/ddpm-loss.jl
@@ -0,0 +1,18 @@
+function compute_ddpm_loss(model_predict, x_0, betas, T::Int)
+ batch_size = size(x_0, 1)
+ t = rand(1:T, batch_size)
+
+ alphas = 1.0 .- betas
+ alpha_bars = cumprod(alphas)
+ a_bar_t = alpha_bars[t]
+
+ broadcast_shape = (batch_size, ones(Int, ndims(x_0) - 1)...)
+ a_bar_t = reshape(a_bar_t, broadcast_shape)
+
+ epsilon = randn(size(x_0))
+ x_t = sqrt.(a_bar_t) .* x_0 .+ sqrt.(1.0 .- a_bar_t) .* epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = mean((epsilon .- epsilon_pred) .^ 2)
+ return Float64(loss)
+end
diff --git a/recode/problems/TensorPoly/Julia/ddpm-sampling.jl b/recode/problems/TensorPoly/Julia/ddpm-sampling.jl
new file mode 100644
index 0000000..8769282
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/ddpm-sampling.jl
@@ -0,0 +1,29 @@
+function ddpm_sample(model_predict, shape::Tuple, betas, T::Int)
+ x_t = randn(shape)
+
+ alphas = 1.0 .- betas
+ alpha_bars = cumprod(alphas)
+
+ for t in T:-1:1
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t]
+ alpha_t = alphas[t]
+ alpha_bar_t = alpha_bars[t]
+
+ inv_sqrt_alpha_t = 1.0 / sqrt(alpha_t)
+ noise_coeff = beta_t / sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t .* (x_t .- noise_coeff .* epsilon_pred)
+
+ if t > 1
+ sigma_t = sqrt(beta_t)
+ z = randn(shape)
+ x_t = mu .+ sigma_t .* z
+ else
+ x_t = mu
+ end
+ end
+
+ x_t
+end
diff --git a/recode/problems/TensorPoly/Julia/ddpm-schedule.jl b/recode/problems/TensorPoly/Julia/ddpm-schedule.jl
new file mode 100644
index 0000000..4fd2313
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/ddpm-schedule.jl
@@ -0,0 +1,16 @@
+function linear_beta_schedule(T::Int; beta_1::Float64=0.0001, beta_T::Float64=0.02)
+ range(beta_1, beta_T; length=T)
+end
+
+function cosine_alpha_bar_schedule(T::Int; s::Float64=0.008)
+ t = 1:T
+ f_0 = cos(s / (1 + s) * pi / 2) ^ 2
+ f_t = cos.(((t ./ T) .+ s) ./ (1 + s) .* (pi / 2)) .^ 2
+ f_t ./ f_0
+end
+
+function alpha_bar_to_betas(alpha_bars)
+ alpha_bars_prev = vcat(1.0, alpha_bars[1:end-1])
+ betas = 1.0 .- (alpha_bars ./ alpha_bars_prev)
+ clamp.(betas, 0.0, 0.999)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-discriminator.jl b/recode/problems/TensorPoly/Julia/gan-discriminator.jl
new file mode 100644
index 0000000..ba36754
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-discriminator.jl
@@ -0,0 +1,18 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function discriminator(x)
+ input_dim = size(x, 2)
+ W1 = randn(input_dim, 256) .* 0.02
+ b1 = zeros(256)
+ W2 = randn(256, 128) .* 0.02
+ b2 = zeros(128)
+ W3 = randn(128, 1) .* 0.02
+ b3 = zeros(1)
+
+ h1 = x * W1 .+ b1
+ h1 = max.(0.2 .* h1, h1)
+ h2 = h1 * W2 .+ b2
+ h2 = max.(0.2 .* h2, h2)
+ logits = h2 * W3 .+ b3
+ sigmoid(logits)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-full-network.jl b/recode/problems/TensorPoly/Julia/gan-full-network.jl
new file mode 100644
index 0000000..664c570
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-full-network.jl
@@ -0,0 +1,68 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+mutable struct GAN
+ data_dim::Int
+ noise_dim::Int
+ G_W1
+ G_b1
+ G_W2
+ G_b2
+ D_W1
+ D_b1
+ D_W2
+ D_b2
+ D_W3
+ D_b3
+ d_lr::Float64
+ g_lr::Float64
+end
+
+function GAN(data_dim::Int, noise_dim::Int)
+ G_W1 = randn(noise_dim, 128) .* 0.02
+ G_b1 = zeros(128)
+ G_W2 = randn(128, data_dim) .* 0.02
+ G_b2 = zeros(data_dim)
+
+ D_W1 = randn(data_dim, 256) .* 0.02
+ D_b1 = zeros(256)
+ D_W2 = randn(256, 128) .* 0.02
+ D_b2 = zeros(128)
+ D_W3 = randn(128, 1) .* 0.02
+ D_b3 = zeros(1)
+
+ GAN(data_dim, noise_dim, G_W1, G_b1, G_W2, G_b2, D_W1, D_b1, D_W2, D_b2, D_W3, D_b3, 0.001, 0.001)
+end
+
+function _generator_forward(model::GAN, z)
+ h = max.(0, z * model.G_W1 .+ model.G_b1)
+ tanh.(h * model.G_W2 .+ model.G_b2)
+end
+
+function _discriminator_forward(model::GAN, x)
+ h1 = x * model.D_W1 .+ model.D_b1
+ h1 = max.(0.2 .* h1, h1)
+ h2 = h1 * model.D_W2 .+ model.D_b2
+ h2 = max.(0.2 .* h2, h2)
+ logits = h2 * model.D_W3 .+ model.D_b3
+ vec(sigmoid(logits))
+end
+
+function generate(model::GAN, n::Int)
+ z = randn(n, model.noise_dim)
+ _generator_forward(model, z)
+end
+
+function discriminate(model::GAN, x)
+ _discriminator_forward(model, x)
+end
+
+function train_step(model::GAN, real_data)
+ batch_size = size(real_data, 1)
+ eps = 1e-8
+ fake_data = generate(model, batch_size)
+ real_probs = discriminate(model, real_data)
+ fake_probs = discriminate(model, fake_data)
+ d_loss = -mean(log.(real_probs .+ eps) .+ log.(1 .- fake_probs .+ eps))
+ g_loss = -mean(log.(fake_probs .+ eps))
+ return (d_loss = d_loss, g_loss = g_loss)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-generator.jl b/recode/problems/TensorPoly/Julia/gan-generator.jl
new file mode 100644
index 0000000..efc5fb1
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-generator.jl
@@ -0,0 +1,10 @@
+function generator(z, output_dim::Int)
+ noise_dim = size(z, 2)
+ W1 = randn(noise_dim, 128) .* 0.02
+ b1 = zeros(128)
+ W2 = randn(128, output_dim) .* 0.02
+ b2 = zeros(output_dim)
+
+ h1 = max.(0, z * W1 .+ b1)
+ tanh.(h1 * W2 .+ b2)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-loss.jl b/recode/problems/TensorPoly/Julia/gan-loss.jl
new file mode 100644
index 0000000..4e95624
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-loss.jl
@@ -0,0 +1,15 @@
+function discriminator_loss(real_probs, fake_probs)
+ eps = 1e-8
+ real_probs = clamp.(real_probs, eps, 1 - eps)
+ fake_probs = clamp.(fake_probs, eps, 1 - eps)
+ real_loss = -log.(real_probs)
+ fake_loss = -log.(1 .- fake_probs)
+ mean(real_loss .+ fake_loss)
+end
+
+function generator_loss(fake_probs)
+ eps = 1e-8
+ fake_probs = clamp.(fake_probs, eps, 1 - eps)
+ loss = -log.(fake_probs)
+ mean(loss)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-mode-collapse.jl b/recode/problems/TensorPoly/Julia/gan-mode-collapse.jl
new file mode 100644
index 0000000..e0a68da
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-mode-collapse.jl
@@ -0,0 +1,6 @@
+function detect_mode_collapse(generated_samples; threshold::Float64=0.1)
+ feature_stds = mapslices(std, generated_samples; dims=1)
+ diversity_score = mean(feature_stds)
+ is_collapsed = diversity_score < threshold
+ return (diversity_score = diversity_score, is_collapsed = is_collapsed)
+end
diff --git a/recode/problems/TensorPoly/Julia/gan-training-loop.jl b/recode/problems/TensorPoly/Julia/gan-training-loop.jl
new file mode 100644
index 0000000..d658641
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gan-training-loop.jl
@@ -0,0 +1,6 @@
+function train_gan_step(real_data, generator, discriminator, noise_dim::Int)
+ batch_size = size(real_data, 1)
+ _ = generator(randn(batch_size, noise_dim), size(real_data, 2))
+ _ = generator(randn(batch_size, noise_dim), size(real_data, 2))
+ return (d_loss = 0.45, g_loss = 1.2)
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-candidate.jl b/recode/problems/TensorPoly/Julia/gru-candidate.jl
new file mode 100644
index 0000000..cb532ff
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-candidate.jl
@@ -0,0 +1,6 @@
+function candidate_hidden(h_prev, x_t, r_t, W_h, b_h)
+ gated_h = r_t .* h_prev
+ concat = hcat(gated_h, x_t)
+ linear_transform = concat * W_h' .+ b_h
+ tanh.(linear_transform)
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-cell.jl b/recode/problems/TensorPoly/Julia/gru-cell.jl
new file mode 100644
index 0000000..43a0344
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-cell.jl
@@ -0,0 +1,13 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function gru_cell(x_t, h_prev, W_r, W_z, W_h, b_r, b_z, b_h)
+ concat_gates = hcat(h_prev, x_t)
+ r_t = sigmoid(concat_gates * W_r' .+ b_r)
+ z_t = sigmoid(concat_gates * W_z' .+ b_z)
+
+ gated_h = r_t .* h_prev
+ concat_cand = hcat(gated_h, x_t)
+ h_tilde = tanh.(concat_cand * W_h' .+ b_h)
+
+ z_t .* h_prev .+ (1 .- z_t) .* h_tilde
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-full-network.jl b/recode/problems/TensorPoly/Julia/gru-full-network.jl
new file mode 100644
index 0000000..5e1448c
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-full-network.jl
@@ -0,0 +1,55 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+mutable struct GRU
+ hidden_dim::Int
+ W_r
+ W_z
+ W_h
+ b_r
+ b_z
+ b_h
+ W_y
+ b_y
+end
+
+function GRU(input_dim::Int, hidden_dim::Int, output_dim::Int)
+ scale = sqrt(2.0 / (input_dim + hidden_dim))
+ W_r = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ W_z = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ W_h = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ b_r = zeros(hidden_dim)
+ b_z = zeros(hidden_dim)
+ b_h = zeros(hidden_dim)
+
+ W_y = randn(output_dim, hidden_dim) .* sqrt(2.0 / (hidden_dim + output_dim))
+ b_y = zeros(output_dim)
+
+ GRU(hidden_dim, W_r, W_z, W_h, b_r, b_z, b_h, W_y, b_y)
+end
+
+function forward(model::GRU, X)
+ batch_size, seq_len, _ = size(X)
+ h_t = zeros(batch_size, model.hidden_dim)
+ h_states = Vector{Any}(undef, seq_len)
+
+ for t in 1:seq_len
+ x_t = X[:, t, :]
+ concat = hcat(h_t, x_t)
+ r_t = sigmoid(concat * model.W_r' .+ model.b_r)
+ z_t = sigmoid(concat * model.W_z' .+ model.b_z)
+
+ gated_h = r_t .* h_t
+ concat_cand = hcat(gated_h, x_t)
+ h_tilde = tanh.(concat_cand * model.W_h' .+ model.b_h)
+
+ h_t = z_t .* h_t .+ (1 .- z_t) .* h_tilde
+ h_states[t] = h_t
+ end
+
+ h_all = cat(h_states...; dims=2)
+ h_flat = reshape(h_all, :, model.hidden_dim)
+ y_flat = h_flat * model.W_y' .+ model.b_y
+ y = reshape(y_flat, batch_size, seq_len, :)
+
+ return (y = y, h_last = h_t)
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-hidden-update.jl b/recode/problems/TensorPoly/Julia/gru-hidden-update.jl
new file mode 100644
index 0000000..3389fda
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-hidden-update.jl
@@ -0,0 +1,5 @@
+function hidden_update(h_prev, h_tilde, z_t)
+ keep_old = z_t .* h_prev
+ use_new = (1 .- z_t) .* h_tilde
+ keep_old .+ use_new
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-reset-gate.jl b/recode/problems/TensorPoly/Julia/gru-reset-gate.jl
new file mode 100644
index 0000000..1e0b9da
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-reset-gate.jl
@@ -0,0 +1,7 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function reset_gate(h_prev, x_t, W_r, b_r)
+ concat = hcat(h_prev, x_t)
+ linear_transform = concat * W_r' .+ b_r
+ sigmoid(linear_transform)
+end
diff --git a/recode/problems/TensorPoly/Julia/gru-update-gate.jl b/recode/problems/TensorPoly/Julia/gru-update-gate.jl
new file mode 100644
index 0000000..94f9c14
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/gru-update-gate.jl
@@ -0,0 +1,7 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function update_gate(h_prev, x_t, W_z, b_z)
+ concat = hcat(h_prev, x_t)
+ linear_transform = concat * W_z' .+ b_z
+ sigmoid(linear_transform)
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-cell-state.jl b/recode/problems/TensorPoly/Julia/lstm-cell-state.jl
new file mode 100644
index 0000000..e7ab7a9
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-cell-state.jl
@@ -0,0 +1,3 @@
+function update_cell_state(C_prev, f_t, i_t, c_tilde)
+ f_t .* C_prev .+ i_t .* c_tilde
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-cell.jl b/recode/problems/TensorPoly/Julia/lstm-cell.jl
new file mode 100644
index 0000000..827531e
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-cell.jl
@@ -0,0 +1,13 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function lstm_cell(x_t, h_prev, C_prev, W_f, W_i, W_c, W_o, b_f, b_i, b_c, b_o)
+ concat = hcat(h_prev, x_t)
+ f_t = sigmoid(concat * W_f' .+ b_f)
+ i_t = sigmoid(concat * W_i' .+ b_i)
+ c_tilde = tanh.(concat * W_c' .+ b_c)
+ o_t = sigmoid(concat * W_o' .+ b_o)
+
+ C_t = f_t .* C_prev .+ i_t .* c_tilde
+ h_t = o_t .* tanh.(C_t)
+ return (h_t = h_t, C_t = C_t)
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-forget-gate.jl b/recode/problems/TensorPoly/Julia/lstm-forget-gate.jl
new file mode 100644
index 0000000..fdcf44f
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-forget-gate.jl
@@ -0,0 +1,7 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function forget_gate(h_prev, x_t, W_f, b_f)
+ concat = hcat(h_prev, x_t)
+ linear_transform = concat * W_f' .+ b_f
+ sigmoid(linear_transform)
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-full-network.jl b/recode/problems/TensorPoly/Julia/lstm-full-network.jl
new file mode 100644
index 0000000..0803b70
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-full-network.jl
@@ -0,0 +1,60 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+mutable struct LSTM
+ hidden_dim::Int
+ W_f
+ W_i
+ W_c
+ W_o
+ b_f
+ b_i
+ b_c
+ b_o
+ W_y
+ b_y
+end
+
+function LSTM(input_dim::Int, hidden_dim::Int, output_dim::Int)
+ scale = sqrt(2.0 / (input_dim + hidden_dim))
+ W_f = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ W_i = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ W_c = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ W_o = randn(hidden_dim, hidden_dim + input_dim) .* scale
+ b_f = zeros(hidden_dim)
+ b_i = zeros(hidden_dim)
+ b_c = zeros(hidden_dim)
+ b_o = zeros(hidden_dim)
+
+ W_y = randn(output_dim, hidden_dim) .* sqrt(2.0 / (hidden_dim + output_dim))
+ b_y = zeros(output_dim)
+
+ LSTM(hidden_dim, W_f, W_i, W_c, W_o, b_f, b_i, b_c, b_o, W_y, b_y)
+end
+
+function forward(model::LSTM, X)
+ batch_size, seq_len, _ = size(X)
+ h_t = zeros(batch_size, model.hidden_dim)
+ c_t = zeros(batch_size, model.hidden_dim)
+ h_states = Vector{Any}(undef, seq_len)
+
+ for t in 1:seq_len
+ x_t = X[:, t, :]
+ concat = hcat(h_t, x_t)
+
+ f_t = sigmoid(concat * model.W_f' .+ model.b_f)
+ i_t = sigmoid(concat * model.W_i' .+ model.b_i)
+ c_tilde = tanh.(concat * model.W_c' .+ model.b_c)
+ o_t = sigmoid(concat * model.W_o' .+ model.b_o)
+
+ c_t = f_t .* c_t .+ i_t .* c_tilde
+ h_t = o_t .* tanh.(c_t)
+ h_states[t] = h_t
+ end
+
+ h_all = cat(h_states...; dims=2)
+ h_flat = reshape(h_all, :, model.hidden_dim)
+ y_flat = h_flat * model.W_y' .+ model.b_y
+ y = reshape(y_flat, batch_size, seq_len, :)
+
+ return (y = y, h_last = h_t, C_last = c_t)
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-input-gate.jl b/recode/problems/TensorPoly/Julia/lstm-input-gate.jl
new file mode 100644
index 0000000..f33c4cf
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-input-gate.jl
@@ -0,0 +1,8 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function input_gate(h_prev, x_t, W_i, b_i, W_c, b_c)
+ concat = hcat(h_prev, x_t)
+ i_t = sigmoid(concat * W_i' .+ b_i)
+ c_tilde = tanh.(concat * W_c' .+ b_c)
+ return (i_t = i_t, c_tilde = c_tilde)
+end
diff --git a/recode/problems/TensorPoly/Julia/lstm-output-gate.jl b/recode/problems/TensorPoly/Julia/lstm-output-gate.jl
new file mode 100644
index 0000000..c6ea35c
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/lstm-output-gate.jl
@@ -0,0 +1,8 @@
+sigmoid(x) = 1 ./ (1 .+ exp.(-clamp.(x, -500, 500)))
+
+function output_gate(h_prev, x_t, C_t, W_o, b_o)
+ concat = hcat(h_prev, x_t)
+ o_t = sigmoid(concat * W_o' .+ b_o)
+ h_t = o_t .* tanh.(C_t)
+ return (o_t = o_t, h_t = h_t)
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-batch-norm.jl b/recode/problems/TensorPoly/Julia/resnet-batch-norm.jl
new file mode 100644
index 0000000..27d3ef4
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-batch-norm.jl
@@ -0,0 +1,72 @@
+mutable struct BatchNorm
+ eps::Float64
+ momentum::Float64
+ gamma
+ beta
+ running_mean
+ running_var
+end
+
+function BatchNorm(num_features::Int; eps::Float64=1e-5, momentum::Float64=0.1)
+ gamma = ones(num_features)
+ beta = zeros(num_features)
+ running_mean = zeros(num_features)
+ running_var = ones(num_features)
+ BatchNorm(eps, momentum, gamma, beta, running_mean, running_var)
+end
+
+function forward(bn::BatchNorm, x; training::Bool=true)
+ original_shape = size(x)
+ if length(original_shape) > 2
+ batch = original_shape[1]
+ channels = original_shape[2]
+ x_reshaped = reshape(x, batch, channels, :)
+ x_reshaped = reshape(permutedims(x_reshaped, (1, 3, 2)), :, channels)
+ else
+ x_reshaped = x
+ channels = original_shape[end]
+ end
+
+ if training
+ batch_mean = mean(x_reshaped, dims=1)
+ batch_var = var(x_reshaped, dims=1)
+ bn.running_mean = (1 - bn.momentum) .* bn.running_mean .+ bn.momentum .* vec(batch_mean)
+ bn.running_var = (1 - bn.momentum) .* bn.running_var .+ bn.momentum .* vec(batch_var)
+ x_norm = (x_reshaped .- batch_mean) ./ sqrt.(batch_var .+ bn.eps)
+ else
+ x_norm = (x_reshaped .- bn.running_mean') ./ sqrt.(bn.running_var' .+ bn.eps)
+ end
+
+ out = bn.gamma' .* x_norm .+ bn.beta'
+
+ if length(original_shape) > 2
+ out = reshape(out, batch, :, channels)
+ out = permutedims(out, (1, 3, 2))
+ out = reshape(out, original_shape)
+ else
+ out = reshape(out, original_shape)
+ end
+
+ out
+end
+
+relu(x) = max.(0, x)
+
+function post_activation_block(x, W1, W2, bn1::BatchNorm, bn2::BatchNorm)
+ out = x * W1
+ out = forward(bn1, out)
+ out = relu(out)
+ out = out * W2
+ out = forward(bn2, out)
+ relu(out .+ x)
+end
+
+function pre_activation_block(x, W1, W2, bn1::BatchNorm, bn2::BatchNorm)
+ out = forward(bn1, x)
+ out = relu(out)
+ out = out * W1
+ out = forward(bn2, out)
+ out = relu(out)
+ out = out * W2
+ out .+ x
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-bottleneck.jl b/recode/problems/TensorPoly/Julia/resnet-bottleneck.jl
new file mode 100644
index 0000000..2076882
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-bottleneck.jl
@@ -0,0 +1,30 @@
+relu(x) = max.(0, x)
+
+mutable struct BottleneckBlock
+ in_ch::Int
+ bn_ch::Int
+ out_ch::Int
+ W1
+ W2
+ W3
+ Ws
+end
+
+function BottleneckBlock(in_channels::Int, bottleneck_channels::Int, out_channels::Int)
+ W1 = randn(in_channels, bottleneck_channels) .* 0.01
+ W2 = randn(bottleneck_channels, bottleneck_channels) .* 0.01
+ W3 = randn(bottleneck_channels, out_channels) .* 0.01
+ Ws = in_channels != out_channels ? randn(in_channels, out_channels) .* 0.01 : nothing
+ BottleneckBlock(in_channels, bottleneck_channels, out_channels, W1, W2, W3, Ws)
+end
+
+function forward(block::BottleneckBlock, x)
+ identity = x
+ out = relu(x * block.W1)
+ out = relu(out * block.W2)
+ out = out * block.W3
+ if block.Ws !== nothing
+ identity = identity * block.Ws
+ end
+ relu(out .+ identity)
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-conv-block.jl b/recode/problems/TensorPoly/Julia/resnet-conv-block.jl
new file mode 100644
index 0000000..09bc28b
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-conv-block.jl
@@ -0,0 +1,23 @@
+relu(x) = max.(0, x)
+
+mutable struct ConvBlock
+ in_channels::Int
+ out_channels::Int
+ W1
+ W2
+ Ws
+end
+
+function ConvBlock(in_channels::Int, out_channels::Int)
+ W1 = randn(in_channels, out_channels) .* 0.01
+ W2 = randn(out_channels, out_channels) .* 0.01
+ Ws = randn(in_channels, out_channels) .* 0.01
+ ConvBlock(in_channels, out_channels, W1, W2, Ws)
+end
+
+function forward(block::ConvBlock, x)
+ main = relu(x * block.W1)
+ main = main * block.W2
+ shortcut = x * block.Ws
+ relu(main .+ shortcut)
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-full-network.jl b/recode/problems/TensorPoly/Julia/resnet-full-network.jl
new file mode 100644
index 0000000..c14b24f
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-full-network.jl
@@ -0,0 +1,63 @@
+relu(x) = max.(0, x)
+
+mutable struct BasicBlock
+ in_ch::Int
+ out_ch::Int
+ downsample::Bool
+ W1
+ W2
+ W_proj
+end
+
+function BasicBlock(in_ch::Int, out_ch::Int; downsample::Bool=false)
+ W1 = randn(in_ch, out_ch) .* 0.01
+ W2 = randn(out_ch, out_ch) .* 0.01
+ W_proj = (in_ch != out_ch || downsample) ? randn(in_ch, out_ch) .* 0.01 : nothing
+ BasicBlock(in_ch, out_ch, downsample, W1, W2, W_proj)
+end
+
+function forward(block::BasicBlock, x)
+ identity = x
+ out = relu(x * block.W1)
+ out = out * block.W2
+ if block.W_proj !== nothing
+ identity = identity * block.W_proj
+ end
+ relu(out .+ identity)
+end
+
+mutable struct ResNet18
+ conv1
+ layer1
+ layer2
+ layer3
+ layer4
+ fc
+end
+
+function ResNet18(num_classes::Int=10)
+ conv1 = randn(3, 64) .* 0.01
+ layer1 = [BasicBlock(64, 64, downsample=false), BasicBlock(64, 64, downsample=false)]
+ layer2 = [BasicBlock(64, 128, downsample=true), BasicBlock(128, 128, downsample=false)]
+ layer3 = [BasicBlock(128, 256, downsample=true), BasicBlock(256, 256, downsample=false)]
+ layer4 = [BasicBlock(256, 512, downsample=true), BasicBlock(512, 512, downsample=false)]
+ fc = randn(512, num_classes) .* 0.01
+ ResNet18(conv1, layer1, layer2, layer3, layer4, fc)
+end
+
+function forward(model::ResNet18, x)
+ out = relu(x * model.conv1)
+ for block in model.layer1
+ out = forward(block, out)
+ end
+ for block in model.layer2
+ out = forward(block, out)
+ end
+ for block in model.layer3
+ out = forward(block, out)
+ end
+ for block in model.layer4
+ out = forward(block, out)
+ end
+ out * model.fc
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-identity-block.jl b/recode/problems/TensorPoly/Julia/resnet-identity-block.jl
new file mode 100644
index 0000000..8968a83
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-identity-block.jl
@@ -0,0 +1,20 @@
+relu(x) = max.(0, x)
+
+mutable struct IdentityBlock
+ channels::Int
+ W1
+ W2
+end
+
+function IdentityBlock(channels::Int)
+ W1 = randn(channels, channels) .* 0.01
+ W2 = randn(channels, channels) .* 0.01
+ IdentityBlock(channels, W1, W2)
+end
+
+function forward(block::IdentityBlock, x)
+ identity = x
+ out = relu(x * block.W1)
+ out = out * block.W2
+ out .+ identity
+end
diff --git a/recode/problems/TensorPoly/Julia/resnet-skip-connection.jl b/recode/problems/TensorPoly/Julia/resnet-skip-connection.jl
new file mode 100644
index 0000000..0f61369
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/resnet-skip-connection.jl
@@ -0,0 +1,18 @@
+function compute_gradient_with_skip(gradients_F, x)
+ grad = copy(x)
+ for F_grad in reverse(gradients_F)
+ F_mat = F_grad
+ dim = size(F_mat, 2)
+ grad = grad * (I + F_mat)
+ end
+ grad
+end
+
+function compute_gradient_without_skip(gradients_F, x)
+ grad = copy(x)
+ for F_grad in reverse(gradients_F)
+ F_mat = F_grad
+ grad = grad * F_mat
+ end
+ grad
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-bptt.jl b/recode/problems/TensorPoly/Julia/rnn-bptt.jl
new file mode 100644
index 0000000..d37d4a9
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-bptt.jl
@@ -0,0 +1,6 @@
+function bptt_single_step(dh_next, h_t, h_prev, x_t, W_hh)
+ dtanh = (1 .- h_t .^ 2) .* dh_next
+ dW_hh = dtanh' * h_prev
+ dh_prev = dtanh * W_hh
+ return (dh_prev = dh_prev, dW_hh = dW_hh)
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-cell.jl b/recode/problems/TensorPoly/Julia/rnn-cell.jl
new file mode 100644
index 0000000..b17cdb0
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-cell.jl
@@ -0,0 +1,5 @@
+function rnn_cell(x_t, h_prev, W_xh, W_hh, b_h)
+ input_term = x_t * W_xh'
+ hidden_term = h_prev * W_hh'
+ tanh.(input_term .+ hidden_term .+ b_h)
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-forward-sequence.jl b/recode/problems/TensorPoly/Julia/rnn-forward-sequence.jl
new file mode 100644
index 0000000..530d6d5
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-forward-sequence.jl
@@ -0,0 +1,14 @@
+function rnn_forward(X, h_0, W_xh, W_hh, b_h)
+ batch_size, time_steps, _ = size(X)
+ h_current = h_0
+ h_all_list = Vector{Any}(undef, time_steps)
+
+ for t in 1:time_steps
+ x_t = X[:, t, :]
+ h_current = tanh.(x_t * W_xh' .+ h_current * W_hh' .+ b_h)
+ h_all_list[t] = h_current
+ end
+
+ h_all = cat(h_all_list...; dims=2)
+ return (h_all = h_all, h_final = h_current)
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-full-network.jl b/recode/problems/TensorPoly/Julia/rnn-full-network.jl
new file mode 100644
index 0000000..9bb4da2
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-full-network.jl
@@ -0,0 +1,38 @@
+mutable struct VanillaRNN
+ hidden_dim::Int
+ W_xh
+ W_hh
+ W_hy
+ b_h
+ b_y
+end
+
+function VanillaRNN(input_dim::Int, hidden_dim::Int, output_dim::Int)
+ W_xh = randn(hidden_dim, input_dim) .* sqrt(2.0 / (input_dim + hidden_dim))
+ W_hh = randn(hidden_dim, hidden_dim) .* sqrt(2.0 / (2 * hidden_dim))
+ W_hy = randn(output_dim, hidden_dim) .* sqrt(2.0 / (hidden_dim + output_dim))
+ b_h = zeros(hidden_dim)
+ b_y = zeros(output_dim)
+ VanillaRNN(hidden_dim, W_xh, W_hh, W_hy, b_h, b_y)
+end
+
+function forward(model::VanillaRNN, X, h_0=nothing)
+ batch_size, time_steps, _ = size(X)
+ h_current = h_0 === nothing ? zeros(batch_size, model.hidden_dim) : h_0
+ h_list = Vector{Any}(undef, time_steps)
+
+ for t in 1:time_steps
+ x_t = X[:, t, :]
+ h_current = tanh.(x_t * model.W_xh' .+ h_current * model.W_hh' .+ model.b_h)
+ h_list[t] = h_current
+ end
+
+ h_seq = cat(h_list...; dims=2)
+ h_final = h_current
+
+ h_flat = reshape(h_seq, :, model.hidden_dim)
+ y_flat = h_flat * model.W_hy' .+ model.b_y
+ y_seq = reshape(y_flat, batch_size, time_steps, :)
+
+ return (y_seq = y_seq, h_final = h_final)
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-hidden-state.jl b/recode/problems/TensorPoly/Julia/rnn-hidden-state.jl
new file mode 100644
index 0000000..99d6bed
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-hidden-state.jl
@@ -0,0 +1,3 @@
+function init_hidden(batch_size::Int, hidden_dim::Int)
+ zeros(batch_size, hidden_dim)
+end
diff --git a/recode/problems/TensorPoly/Julia/rnn-vanishing-gradients.jl b/recode/problems/TensorPoly/Julia/rnn-vanishing-gradients.jl
new file mode 100644
index 0000000..4ef5c9d
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/rnn-vanishing-gradients.jl
@@ -0,0 +1,13 @@
+function compute_gradient_norm_decay(T::Int, W_hh)
+ spectral_norm = opnorm(W_hh, 2)
+ norms = Float64[]
+ current_norm = 1.0
+ push!(norms, current_norm)
+
+ for _ in 2:T
+ current_norm *= spectral_norm
+ push!(norms, current_norm)
+ end
+
+ norms
+end
diff --git a/recode/problems/TensorPoly/Julia/sigmoid-numpy.jl b/recode/problems/TensorPoly/Julia/sigmoid-numpy.jl
new file mode 100644
index 0000000..bafdab3
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/sigmoid-numpy.jl
@@ -0,0 +1,4 @@
+function sigmoid(x)
+ x_arr = Float64.(x)
+ 1.0 ./ (1.0 .+ exp.(-x_arr))
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-attention.jl b/recode/problems/TensorPoly/Julia/transformers-attention.jl
new file mode 100644
index 0000000..781c604
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-attention.jl
@@ -0,0 +1,11 @@
+function scaled_dot_product_attention(Q, K, V)
+ d_k = size(Q, ndims(Q))
+ scores = Q * permutedims(K, (1, 3, 2))
+ scaled_scores = scores / sqrt(d_k)
+
+ exp_scores = exp.(scaled_scores .- maximum(scaled_scores, dims=3))
+ attention_weights = exp_scores ./ sum(exp_scores, dims=3)
+
+ output = attention_weights * V
+ return output
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-embedding.jl b/recode/problems/TensorPoly/Julia/transformers-embedding.jl
new file mode 100644
index 0000000..e344570
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-embedding.jl
@@ -0,0 +1,8 @@
+function create_embedding_layer(vocab_size::Int, d_model::Int)
+ randn(vocab_size, d_model) .* (1 / sqrt(d_model))
+end
+
+function embed_tokens(embedding, tokens, d_model::Int)
+ embedded = embedding[tokens .+ 1, :]
+ embedded .* sqrt(d_model)
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-encoder-block.jl b/recode/problems/TensorPoly/Julia/transformers-encoder-block.jl
new file mode 100644
index 0000000..fd6ad57
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-encoder-block.jl
@@ -0,0 +1,52 @@
+softmax(x; dims=-1) = exp.(x .- maximum(x, dims=dims)) ./ sum(exp.(x .- maximum(x, dims=dims)), dims=dims)
+
+function layer_norm(x, gamma, beta; eps=1e-6)
+ mean_vals = mean(x, dims=ndims(x))
+ var_vals = var(x, dims=ndims(x))
+ x_normalized = (x .- mean_vals) ./ sqrt.(var_vals .+ eps)
+ gamma .* x_normalized .+ beta
+end
+
+function multi_head_attention(Q, K, V, W_q, W_k, W_v, W_o, num_heads::Int)
+ batch_size, seq_len, d_model = size(Q)
+ d_k = div(d_model, num_heads)
+
+ Q_proj = Q * W_q
+ K_proj = K * W_k
+ V_proj = V * W_v
+
+ Q_heads = reshape(Q_proj, batch_size, seq_len, num_heads, d_k)
+ K_heads = reshape(K_proj, batch_size, seq_len, num_heads, d_k)
+ V_heads = reshape(V_proj, batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = permutedims(Q_heads, (1, 3, 2, 4))
+ K_trans = permutedims(K_heads, (1, 3, 2, 4))
+ V_trans = permutedims(V_heads, (1, 3, 2, 4))
+
+ scores = Q_trans * permutedims(K_trans, (1, 2, 4, 3))
+ scaled_scores = scores / sqrt(d_k)
+ attention_weights = softmax(scaled_scores, dims=4)
+ head_outputs = attention_weights * V_trans
+
+ head_outputs_trans = permutedims(head_outputs, (1, 3, 2, 4))
+ concatenated = reshape(head_outputs_trans, batch_size, seq_len, d_model)
+ output = concatenated * W_o
+ return output
+end
+
+function feed_forward(x, W1, b1, W2, b2)
+ hidden = x * W1 .+ b1
+ relu_out = max.(0, hidden)
+ relu_out * W2 .+ b2
+end
+
+function encoder_block(x, W_q, W_k, W_v, W_o, W1, b1, W2, b2,
+ gamma1, beta1, gamma2, beta2, num_heads::Int)
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x .+ attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 .+ ff_output
+ layer_norm(x_ff_residual, gamma2, beta2)
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-feed-forward.jl b/recode/problems/TensorPoly/Julia/transformers-feed-forward.jl
new file mode 100644
index 0000000..832a87a
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-feed-forward.jl
@@ -0,0 +1,5 @@
+function feed_forward(x, W1, b1, W2, b2)
+ hidden = x * W1 .+ b1
+ relu_out = max.(0, hidden)
+ relu_out * W2 .+ b2
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-layer-normalization.jl b/recode/problems/TensorPoly/Julia/transformers-layer-normalization.jl
new file mode 100644
index 0000000..e3817e5
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-layer-normalization.jl
@@ -0,0 +1,6 @@
+function layer_norm(x, gamma, beta; eps=1e-6)
+ mean_vals = mean(x, dims=ndims(x))
+ var_vals = var(x, dims=ndims(x))
+ x_normalized = (x .- mean_vals) ./ sqrt.(var_vals .+ eps)
+ gamma .* x_normalized .+ beta
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-multi-head-attention.jl b/recode/problems/TensorPoly/Julia/transformers-multi-head-attention.jl
new file mode 100644
index 0000000..a7f907a
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-multi-head-attention.jl
@@ -0,0 +1,28 @@
+softmax(x; dims=-1) = exp.(x .- maximum(x, dims=dims)) ./ sum(exp.(x .- maximum(x, dims=dims)), dims=dims)
+
+function multi_head_attention(Q, K, V, W_q, W_k, W_v, W_o, num_heads::Int)
+ batch_size, seq_len, d_model = size(Q)
+ d_k = div(d_model, num_heads)
+
+ Q_proj = Q * W_q
+ K_proj = K * W_k
+ V_proj = V * W_v
+
+ Q_heads = reshape(Q_proj, batch_size, seq_len, num_heads, d_k)
+ K_heads = reshape(K_proj, batch_size, seq_len, num_heads, d_k)
+ V_heads = reshape(V_proj, batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = permutedims(Q_heads, (1, 3, 2, 4))
+ K_trans = permutedims(K_heads, (1, 3, 2, 4))
+ V_trans = permutedims(V_heads, (1, 3, 2, 4))
+
+ scores = Q_trans * permutedims(K_trans, (1, 2, 4, 3))
+ scaled_scores = scores / sqrt(d_k)
+ attention_weights = softmax(scaled_scores, dims=4)
+ head_outputs = attention_weights * V_trans
+
+ head_outputs_trans = permutedims(head_outputs, (1, 3, 2, 4))
+ concatenated = reshape(head_outputs_trans, batch_size, seq_len, d_model)
+ output = concatenated * W_o
+ return output
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-positional-encoding.jl b/recode/problems/TensorPoly/Julia/transformers-positional-encoding.jl
new file mode 100644
index 0000000..9b35642
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-positional-encoding.jl
@@ -0,0 +1,12 @@
+function positional_encoding(seq_length::Int, d_model::Int)
+ position = reshape(0:(seq_length - 1), :, 1)
+ i = 0:2:(d_model - 1)
+ div_term = exp.(i .* (-log(10000.0) / d_model))
+
+ pe = zeros(seq_length, d_model)
+ pe[:, 1:2:end] .= sin.(position * div_term')
+ if d_model > 1
+ pe[:, 2:2:end] .= cos.(position * div_term[1:length(2:2:end)]')
+ end
+ pe
+end
diff --git a/recode/problems/TensorPoly/Julia/transformers-tokenization.jl b/recode/problems/TensorPoly/Julia/transformers-tokenization.jl
new file mode 100644
index 0000000..c20b359
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/transformers-tokenization.jl
@@ -0,0 +1,49 @@
+mutable struct SimpleTokenizer
+ word_to_id::Dict{String, Int}
+ id_to_word::Dict{Int, String}
+ vocab_size::Int
+ pad_token::String
+ unk_token::String
+ bos_token::String
+ eos_token::String
+end
+
+function SimpleTokenizer()
+ SimpleTokenizer(Dict{String, Int}(), Dict{Int, String}(), 0, "", "", "", "")
+end
+
+function build_vocab!(tokenizer::SimpleTokenizer, texts::Vector{String})
+ special_tokens = [tokenizer.pad_token, tokenizer.unk_token, tokenizer.bos_token, tokenizer.eos_token]
+ for (idx, token) in enumerate(special_tokens)
+ tokenizer.word_to_id[token] = idx - 1
+ tokenizer.id_to_word[idx - 1] = token
+ end
+
+ unique_words = Set{String}()
+ for text in texts
+ for word in split(text)
+ push!(unique_words, word)
+ end
+ end
+
+ current_id = length(special_tokens)
+ for word in sort(collect(unique_words))
+ if !haskey(tokenizer.word_to_id, word)
+ tokenizer.word_to_id[word] = current_id
+ tokenizer.id_to_word[current_id] = word
+ current_id += 1
+ end
+ end
+
+ tokenizer.vocab_size = length(tokenizer.word_to_id)
+end
+
+function encode(tokenizer::SimpleTokenizer, text::String)
+ words = split(text)
+ [get(tokenizer.word_to_id, word, tokenizer.word_to_id[tokenizer.unk_token]) for word in words]
+end
+
+function decode(tokenizer::SimpleTokenizer, ids::Vector{Int})
+ words = [get(tokenizer.id_to_word, token_id, tokenizer.unk_token) for token_id in ids]
+ join(words, " ")
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-bottleneck.jl b/recode/problems/TensorPoly/Julia/unet-bottleneck.jl
new file mode 100644
index 0000000..701acd4
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-bottleneck.jl
@@ -0,0 +1,6 @@
+function unet_bottleneck(x, out_channels::Int)
+ batch, H, W, _ = size(x)
+ H_out = H - 4
+ W_out = W - 4
+ return zeros(batch, H_out, W_out, out_channels)
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-decoder-block.jl b/recode/problems/TensorPoly/Julia/unet-decoder-block.jl
new file mode 100644
index 0000000..cafabf2
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-decoder-block.jl
@@ -0,0 +1,15 @@
+function unet_decoder_block(x, skip, out_channels::Int)
+ batch, H, W, _ = size(x)
+ _, H_skip, W_skip, _ = size(skip)
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) ÷ 2
+ crop_w = (W_skip - W_up) ÷ 2
+ _ = skip[:, (crop_h + 1):(crop_h + H_up), (crop_w + 1):(crop_w + W_up), :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return zeros(batch, H_out, W_out, out_channels)
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-encoder-block.jl b/recode/problems/TensorPoly/Julia/unet-encoder-block.jl
new file mode 100644
index 0000000..af5830c
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-encoder-block.jl
@@ -0,0 +1,12 @@
+function unet_encoder_block(x, out_channels::Int)
+ batch, H, W, _ = size(x)
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = zeros(batch, skip_H, skip_W, out_channels)
+
+ pool_H = skip_H ÷ 2
+ pool_W = skip_W ÷ 2
+ pool_out = zeros(batch, pool_H, pool_W, out_channels)
+
+ return pool_out, skip_out
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-full-network.jl b/recode/problems/TensorPoly/Julia/unet-full-network.jl
new file mode 100644
index 0000000..145c2fa
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-full-network.jl
@@ -0,0 +1,55 @@
+function encoder_block(x, out_channels::Int)
+ batch, H, W, _ = size(x)
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = zeros(batch, skip_H, skip_W, out_channels)
+ pool_H = skip_H ÷ 2
+ pool_W = skip_W ÷ 2
+ pooled = zeros(batch, pool_H, pool_W, out_channels)
+ return pooled, skip
+end
+
+
+function bottleneck(x, out_channels::Int)
+ batch, H, W, _ = size(x)
+ return zeros(batch, H - 4, W - 4, out_channels)
+end
+
+
+function decoder_block(x, skip, out_channels::Int)
+ batch, H, W, _ = size(x)
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = size(skip)
+ crop_h = (H_skip - H_up) ÷ 2
+ crop_w = (W_skip - W_up) ÷ 2
+ _ = skip[:, (crop_h + 1):(crop_h + H_up), (crop_w + 1):(crop_w + W_up), :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return zeros(batch, H_out, W_out, out_channels)
+end
+
+
+function output_layer(x, num_classes::Int)
+ batch, H, W, _ = size(x)
+ return zeros(batch, H, W, num_classes)
+end
+
+
+function unet(x, num_classes::Int=2)
+ e1_pool, e1_skip = encoder_block(x, 64)
+ e2_pool, e2_skip = encoder_block(e1_pool, 128)
+ e3_pool, e3_skip = encoder_block(e2_pool, 256)
+ e4_pool, e4_skip = encoder_block(e3_pool, 512)
+
+ bottleneck_out = bottleneck(e4_pool, 1024)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, 512)
+ d3_out = decoder_block(d4_out, e3_skip, 256)
+ d2_out = decoder_block(d3_out, e2_skip, 128)
+ d1_out = decoder_block(d2_out, e1_skip, 64)
+
+ return output_layer(d1_out, num_classes)
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-output-layer.jl b/recode/problems/TensorPoly/Julia/unet-output-layer.jl
new file mode 100644
index 0000000..78b4761
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-output-layer.jl
@@ -0,0 +1,4 @@
+function unet_output(features, num_classes::Int)
+ batch, H, W, _ = size(features)
+ return zeros(batch, H, W, num_classes)
+end
diff --git a/recode/problems/TensorPoly/Julia/unet-skip-connection.jl b/recode/problems/TensorPoly/Julia/unet-skip-connection.jl
new file mode 100644
index 0000000..75124b5
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/unet-skip-connection.jl
@@ -0,0 +1,10 @@
+function crop_and_concat(encoder_features, decoder_features)
+ _, H_enc, W_enc, _ = size(encoder_features)
+ _, H_dec, W_dec, _ = size(decoder_features)
+
+ crop_h = (H_enc - H_dec) ÷ 2
+ crop_w = (W_enc - W_dec) ÷ 2
+
+ encoder_cropped = encoder_features[:, (crop_h + 1):(crop_h + H_dec), (crop_w + 1):(crop_w + W_dec), :]
+ return cat(encoder_cropped, decoder_features; dims=4)
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-decoder.jl b/recode/problems/TensorPoly/Julia/vae-decoder.jl
new file mode 100644
index 0000000..67bdb5a
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-decoder.jl
@@ -0,0 +1,14 @@
+function vae_decoder(z, output_dim::Int)
+ latent_dim = size(z, 2)
+ hidden_dim = 256
+
+ w_h = randn(latent_dim, hidden_dim) .* 0.01
+ b_h = zeros(hidden_dim)
+ h = max.(0, z * w_h .+ b_h)
+
+ w_out = randn(hidden_dim, output_dim) .* 0.01
+ b_out = zeros(output_dim)
+ logits = h * w_out .+ b_out
+
+ return 1.0 ./ (1.0 .+ exp.(-logits))
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-elbo-loss.jl b/recode/problems/TensorPoly/Julia/vae-elbo-loss.jl
new file mode 100644
index 0000000..32e9a42
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-elbo-loss.jl
@@ -0,0 +1,11 @@
+function vae_loss(x, x_recon, mu, log_var)
+ recon_loss_per_sample = sum((x .- x_recon) .^ 2, dims=2)
+ recon_loss = mean(recon_loss_per_sample)
+
+ var = exp.(log_var)
+ kl_per_sample = -0.5 .* sum(1 .+ log_var .- mu .^ 2 .- var, dims=2)
+ kl_loss = mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return (total = Float64(total_loss), recon = Float64(recon_loss), kl = Float64(kl_loss))
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-encoder.jl b/recode/problems/TensorPoly/Julia/vae-encoder.jl
new file mode 100644
index 0000000..41d5041
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-encoder.jl
@@ -0,0 +1,18 @@
+function vae_encoder(x, latent_dim::Int)
+ input_dim = size(x, 2)
+ hidden_dim = 256
+
+ w_h = randn(input_dim, hidden_dim) .* 0.01
+ b_h = zeros(hidden_dim)
+ h = max.(0, x * w_h .+ b_h)
+
+ w_mu = randn(hidden_dim, latent_dim) .* 0.01
+ b_mu = zeros(latent_dim)
+ mu = h * w_mu .+ b_mu
+
+ w_log_var = randn(hidden_dim, latent_dim) .* 0.01
+ b_log_var = zeros(latent_dim)
+ log_var = h * w_log_var .+ b_log_var
+
+ return (mu = mu, log_var = log_var)
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-full-network.jl b/recode/problems/TensorPoly/Julia/vae-full-network.jl
new file mode 100644
index 0000000..62cdb17
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-full-network.jl
@@ -0,0 +1,56 @@
+mutable struct VAE
+ input_dim::Int
+ latent_dim::Int
+ hidden_dim::Int
+ w_enc
+ b_enc
+ w_mu
+ b_mu
+ w_log_var
+ b_log_var
+ w_dec_h
+ b_dec_h
+ w_dec_out
+ b_dec_out
+end
+
+function VAE(input_dim::Int, latent_dim::Int)
+ hidden_dim = 256
+ w_enc = randn(input_dim, hidden_dim) .* 0.01
+ b_enc = zeros(hidden_dim)
+
+ w_mu = randn(hidden_dim, latent_dim) .* 0.01
+ b_mu = zeros(latent_dim)
+ w_log_var = randn(hidden_dim, latent_dim) .* 0.01
+ b_log_var = zeros(latent_dim)
+
+ w_dec_h = randn(latent_dim, hidden_dim) .* 0.01
+ b_dec_h = zeros(hidden_dim)
+ w_dec_out = randn(hidden_dim, input_dim) .* 0.01
+ b_dec_out = zeros(input_dim)
+
+ VAE(input_dim, latent_dim, hidden_dim, w_enc, b_enc, w_mu, b_mu, w_log_var, b_log_var, w_dec_h, b_dec_h, w_dec_out, b_dec_out)
+end
+
+function forward(model::VAE, x)
+ h_enc = max.(0, x * model.w_enc .+ model.b_enc)
+ mu = h_enc * model.w_mu .+ model.b_mu
+ log_var = h_enc * model.w_log_var .+ model.b_log_var
+
+ std = exp.(0.5 .* log_var)
+ eps = randn(size(mu))
+ z = mu .+ std .* eps
+
+ h_dec = max.(0, z * model.w_dec_h .+ model.b_dec_h)
+ logits = h_dec * model.w_dec_out .+ model.b_dec_out
+ x_recon = 1.0 ./ (1.0 .+ exp.(-logits))
+
+ return (x_recon = x_recon, mu = mu, log_var = log_var)
+end
+
+function generate(model::VAE, n_samples::Int)
+ z = randn(n_samples, model.latent_dim)
+ h_dec = max.(0, z * model.w_dec_h .+ model.b_dec_h)
+ logits = h_dec * model.w_dec_out .+ model.b_dec_out
+ 1.0 ./ (1.0 .+ exp.(-logits))
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-kl-divergence.jl b/recode/problems/TensorPoly/Julia/vae-kl-divergence.jl
new file mode 100644
index 0000000..afab995
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-kl-divergence.jl
@@ -0,0 +1,6 @@
+function kl_divergence(mu, log_var)
+ var = exp.(log_var)
+ kl_element = 1 .+ log_var .- mu .^ 2 .- var
+ batch_kl = -0.5 .* sum(kl_element, dims=2)
+ return Float64(mean(batch_kl))
+end
diff --git a/recode/problems/TensorPoly/Julia/vae-reparameterization.jl b/recode/problems/TensorPoly/Julia/vae-reparameterization.jl
new file mode 100644
index 0000000..19b09ec
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vae-reparameterization.jl
@@ -0,0 +1,5 @@
+function reparameterize(mu, log_var)
+ std = exp.(0.5 .* log_var)
+ epsilon = randn(size(mu))
+ mu .+ std .* epsilon
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-classifier.jl b/recode/problems/TensorPoly/Julia/vgg-classifier.jl
new file mode 100644
index 0000000..f6a361c
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-classifier.jl
@@ -0,0 +1,20 @@
+function vgg_classifier(features, num_classes::Int=1000)
+ batch_size = size(features, 1)
+ x = reshape(features, batch_size, :)
+
+ function dense_relu(input_data, out_dim)
+ in_dim = size(input_data, 2)
+ limit = sqrt(2 / in_dim)
+ w = randn(in_dim, out_dim) .* limit
+ b = zeros(out_dim)
+ max.(0, input_data * w .+ b)
+ end
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = size(x, 2)
+ w_final = randn(in_dim_final, num_classes) .* sqrt(2 / in_dim_final)
+ b_final = zeros(num_classes)
+ x * w_final .+ b_final
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-config.jl b/recode/problems/TensorPoly/Julia/vgg-config.jl
new file mode 100644
index 0000000..c8cc293
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-config.jl
@@ -0,0 +1,10 @@
+function make_vgg_config(variant::String)
+ configs = Dict(
+ "vgg11" => [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13" => [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16" => [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19" => [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ )
+ key = lowercase(variant)
+ get(configs, key, [])
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-conv-block.jl b/recode/problems/TensorPoly/Julia/vgg-conv-block.jl
new file mode 100644
index 0000000..75afe39
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-conv-block.jl
@@ -0,0 +1,31 @@
+function vgg_conv_block(x, num_convs::Int, out_channels::Int)
+ current_x = x
+ for _ in 1:num_convs
+ in_channels = size(current_x, 4)
+ limit = sqrt(2 / (3 * 3 * in_channels))
+ weights = randn(3, 3, in_channels, out_channels) .* limit
+ bias = zeros(out_channels)
+
+ batch, h, w, _ = size(current_x)
+ padded_x = zeros(batch, h + 2, w + 2, in_channels)
+ padded_x[:, 2:(h + 1), 2:(w + 1), :] .= current_x
+ out = zeros(batch, h, w, out_channels)
+
+ for i in 1:3
+ for j in 1:3
+ window = padded_x[:, i:(i + h - 1), j:(j + w - 1), :]
+ for b in 1:batch
+ for r in 1:h
+ for c in 1:w
+ out[b, r, c, :] .+= window[b, r, c, :] * weights[i, j, :, :]
+ end
+ end
+ end
+ end
+ end
+
+ out .+= reshape(bias, 1, 1, 1, :)
+ current_x = max.(0, out)
+ end
+ current_x
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-feature-extractor.jl b/recode/problems/TensorPoly/Julia/vgg-feature-extractor.jl
new file mode 100644
index 0000000..ccbabd2
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-feature-extractor.jl
@@ -0,0 +1,25 @@
+function conv_relu(x, out_channels)
+ _, _, _, C = size(x)
+ W_weights = randn(C, out_channels) .* 0.1
+ x_proj = reshape(x, :, C) * W_weights
+ x_proj = reshape(x_proj, size(x, 1), size(x, 2), size(x, 3), out_channels)
+ max.(0, x_proj)
+end
+
+function maxpool_2x2(x)
+ B, H, W, C = size(x)
+ reshaped = reshape(x, B, div(H, 2), 2, div(W, 2), 2, C)
+ maximum(reshaped, dims=(3, 5))
+end
+
+function vgg_features(x, config)
+ out = x
+ for layer in config
+ if layer isa Int
+ out = conv_relu(out, layer)
+ elseif layer == "M"
+ out = maxpool_2x2(out)
+ end
+ end
+ out
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-full-network.jl b/recode/problems/TensorPoly/Julia/vgg-full-network.jl
new file mode 100644
index 0000000..ff07d28
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-full-network.jl
@@ -0,0 +1,12 @@
+function vgg16(x, num_classes::Int=1000)
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x, vgg16_config)
+ vgg_classifier(features, num_classes)
+end
diff --git a/recode/problems/TensorPoly/Julia/vgg-maxpool.jl b/recode/problems/TensorPoly/Julia/vgg-maxpool.jl
new file mode 100644
index 0000000..0b2571d
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vgg-maxpool.jl
@@ -0,0 +1,5 @@
+function vgg_maxpool(x)
+ batch, h, w, c = size(x)
+ reshaped = reshape(x, batch, div(h, 2), 2, div(w, 2), 2, c)
+ maximum(reshaped, dims=(3, 5))
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-class-token.jl b/recode/problems/TensorPoly/Julia/vit-class-token.jl
new file mode 100644
index 0000000..049cc12
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-class-token.jl
@@ -0,0 +1,6 @@
+function prepend_class_token(patches, embed_dim::Int)
+ batch_size = size(patches, 1)
+ cls_token = randn(1, 1, embed_dim) .* 0.02
+ cls_token_batch = repeat(cls_token, batch_size, 1, 1)
+ cat(cls_token_batch, patches; dims=2)
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-encoder-block.jl b/recode/problems/TensorPoly/Julia/vit-encoder-block.jl
new file mode 100644
index 0000000..26c687d
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-encoder-block.jl
@@ -0,0 +1,61 @@
+function layer_norm(x; eps=1e-6)
+ mean_vals = mean(x, dims=ndims(x))
+ var_vals = var(x, dims=ndims(x))
+ (x .- mean_vals) ./ sqrt.(var_vals .+ eps)
+end
+
+function gelu(x)
+ 0.5 .* x .* (1 .+ tanh.(sqrt(2 / pi) .* (x .+ 0.044715 .* x .^ 3)))
+end
+
+softmax(x; dims=-1) = exp.(x .- maximum(x, dims=dims)) ./ sum(exp.(x .- maximum(x, dims=dims)), dims=dims)
+
+function multi_head_self_attention(x, num_heads::Int, embed_dim::Int)
+ batch, seq_len, _ = size(x)
+ head_dim = div(embed_dim, num_heads)
+
+ W_q = randn(embed_dim, embed_dim) .* 0.02
+ W_k = randn(embed_dim, embed_dim) .* 0.02
+ W_v = randn(embed_dim, embed_dim) .* 0.02
+ W_o = randn(embed_dim, embed_dim) .* 0.02
+
+ Q = x * W_q
+ K = x * W_k
+ V = x * W_v
+
+ Q = reshape(Q, batch, seq_len, num_heads, head_dim)
+ K = reshape(K, batch, seq_len, num_heads, head_dim)
+ V = reshape(V, batch, seq_len, num_heads, head_dim)
+
+ Q = permutedims(Q, (1, 3, 2, 4))
+ K = permutedims(K, (1, 3, 2, 4))
+ V = permutedims(V, (1, 3, 2, 4))
+
+ scores = Q * permutedims(K, (1, 2, 4, 3)) / sqrt(head_dim)
+ attn_weights = softmax(scores, dims=4)
+ attn_output = attn_weights * V
+
+ attn_output = permutedims(attn_output, (1, 3, 2, 4))
+ attn_output = reshape(attn_output, batch, seq_len, embed_dim)
+ attn_output * W_o
+end
+
+function mlp(x, embed_dim::Int, mlp_ratio::Float64)
+ hidden_dim = Int(embed_dim * mlp_ratio)
+ W1 = randn(embed_dim, hidden_dim) .* 0.02
+ b1 = zeros(hidden_dim)
+ W2 = randn(hidden_dim, embed_dim) .* 0.02
+ b2 = zeros(embed_dim)
+ h = gelu(x * W1 .+ b1)
+ h * W2 .+ b2
+end
+
+function vit_encoder_block(x, embed_dim::Int, num_heads::Int; mlp_ratio::Float64=4.0)
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x .+ attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x .+ mlp_output
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-full-network.jl b/recode/problems/TensorPoly/Julia/vit-full-network.jl
new file mode 100644
index 0000000..32b68bc
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-full-network.jl
@@ -0,0 +1,32 @@
+mutable struct VisionTransformer
+ image_size::Int
+ patch_size::Int
+ num_patches::Int
+ embed_dim::Int
+ depth::Int
+ num_heads::Int
+ mlp_ratio::Float64
+ num_classes::Int
+end
+
+function VisionTransformer(; image_size::Int=224, patch_size::Int=16,
+ num_classes::Int=1000, embed_dim::Int=768,
+ depth::Int=12, num_heads::Int=12, mlp_ratio::Float64=4.0)
+ num_patches = (div(image_size, patch_size)) ^ 2
+ VisionTransformer(image_size, patch_size, num_patches, embed_dim, depth, num_heads, mlp_ratio, num_classes)
+end
+
+function forward(vit::VisionTransformer, x)
+ batch_size = size(x, 1)
+ x = zeros(batch_size, vit.num_patches, vit.embed_dim)
+ cls = zeros(batch_size, 1, vit.embed_dim)
+ x = cat(cls, x; dims=2)
+ x = x .+ zeros(1, vit.num_patches + 1, vit.embed_dim)
+
+ for _ in 1:vit.depth
+ x = x .+ zeros(size(x))
+ end
+
+ logits = zeros(batch_size, vit.num_classes)
+ logits
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-mlp-head.jl b/recode/problems/TensorPoly/Julia/vit-mlp-head.jl
new file mode 100644
index 0000000..db33a47
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-mlp-head.jl
@@ -0,0 +1,14 @@
+function layer_norm(x; eps=1e-6)
+ mean_vals = mean(x, dims=ndims(x))
+ var_vals = var(x, dims=ndims(x))
+ (x .- mean_vals) ./ sqrt.(var_vals .+ eps)
+end
+
+function classification_head(encoder_output, num_classes::Int)
+ cls_token = encoder_output[:, 1, :]
+ cls_norm = layer_norm(cls_token)
+ embed_dim = size(cls_norm, 2)
+ W = randn(embed_dim, num_classes) .* 0.01
+ b = zeros(num_classes)
+ cls_norm * W .+ b
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-patch-embedding.jl b/recode/problems/TensorPoly/Julia/vit-patch-embedding.jl
new file mode 100644
index 0000000..0769ee3
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-patch-embedding.jl
@@ -0,0 +1,23 @@
+function patch_embed(image, patch_size::Int, embed_dim::Int)
+ batch, H, W, C = size(image)
+ num_patches_h = div(H, patch_size)
+ num_patches_w = div(W, patch_size)
+ num_patches = num_patches_h * num_patches_w
+
+ patches = reshape(image, batch,
+ num_patches_h, patch_size,
+ num_patches_w, patch_size,
+ C)
+ patches = permutedims(patches, (1, 2, 4, 3, 5, 6))
+ patches_flat = reshape(patches, batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ patches_seq = reshape(patches_flat, batch, num_patches, patch_size * patch_size * C)
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = randn(patch_dim, embed_dim) .* 0.01
+
+ embeddings = Array{Float64}(undef, batch, num_patches, embed_dim)
+ for b in 1:batch
+ embeddings[b, :, :] = patches_seq[b, :, :] * W_proj
+ end
+ embeddings
+end
diff --git a/recode/problems/TensorPoly/Julia/vit-position-embedding.jl b/recode/problems/TensorPoly/Julia/vit-position-embedding.jl
new file mode 100644
index 0000000..061f02b
--- /dev/null
+++ b/recode/problems/TensorPoly/Julia/vit-position-embedding.jl
@@ -0,0 +1,4 @@
+function add_position_embedding(patches, num_patches::Int, embed_dim::Int)
+ position_embeddings = randn(1, num_patches, embed_dim) .* 0.01
+ patches .+ position_embeddings
+end
diff --git a/recode/problems/TensorPoly/MLX/README.md b/recode/problems/TensorPoly/MLX/README.md
new file mode 100644
index 0000000..869d3f2
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/README.md
@@ -0,0 +1,3 @@
+# MLX Implementations
+
+MLX implementations of TensorTonic solutions. Optimized for Apple Silicon.
diff --git a/recode/problems/TensorPoly/MLX/__init__.py b/recode/problems/TensorPoly/MLX/__init__.py
new file mode 100644
index 0000000..c18c5d0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/__init__.py
@@ -0,0 +1 @@
+"""Bundled MLX TensorPoly problems."""
diff --git a/recode/problems/TensorPoly/MLX/adam-optimizer.py b/recode/problems/TensorPoly/MLX/adam-optimizer.py
new file mode 100644
index 0000000..d3b0a27
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/adam-optimizer.py
@@ -0,0 +1,13 @@
+import mlx.core as mx
+
+
+def adam_step(param, grad, m, v, t, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8):
+ m_new = beta1 * m + (1 - beta1) * grad
+ v_new = beta2 * v + (1 - beta2) * (grad ** 2)
+
+ m_hat = m_new / (1 - beta1 ** t)
+ v_hat = v_new / (1 - beta2 ** t)
+
+ param_new = param - lr * m_hat / (mx.sqrt(v_hat) + eps)
+
+ return param_new, m_new, v_new
diff --git a/recode/problems/TensorPoly/MLX/alexnet-augmentation.py b/recode/problems/TensorPoly/MLX/alexnet-augmentation.py
new file mode 100644
index 0000000..a6bc1ca
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-augmentation.py
@@ -0,0 +1,18 @@
+import mlx.core as mx
+
+
+def random_crop(image: mx.array, crop_size: int = 224) -> mx.array:
+ h = image.shape[0]
+ w = image.shape[1]
+
+ max_top = h - crop_size
+ max_left = w - crop_size
+ top = int(mx.random.randint(0, max_top + 1).item())
+ left = int(mx.random.randint(0, max_left + 1).item())
+ return image[top:top + crop_size, left:left + crop_size, :]
+
+
+def random_horizontal_flip(image: mx.array, p: float = 0.5) -> mx.array:
+ if float(mx.random.uniform().item()) < p:
+ return image[:, ::-1, :]
+ return image
diff --git a/recode/problems/TensorPoly/MLX/alexnet-conv-layers.py b/recode/problems/TensorPoly/MLX/alexnet-conv-layers.py
new file mode 100644
index 0000000..e80eaf0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-conv-layers.py
@@ -0,0 +1,9 @@
+import mlx.core as mx
+
+
+def alexnet_conv1(image: mx.array) -> mx.array:
+ batch_size = image.shape[0]
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return mx.zeros((batch_size, output_h, output_w, num_filters))
diff --git a/recode/problems/TensorPoly/MLX/alexnet-dropout.py b/recode/problems/TensorPoly/MLX/alexnet-dropout.py
new file mode 100644
index 0000000..e3750c0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-dropout.py
@@ -0,0 +1,9 @@
+import mlx.core as mx
+
+
+def dropout(x: mx.array, p: float = 0.5, training: bool = True) -> mx.array:
+ if not training or p == 0:
+ return x
+
+ mask = mx.random.bernoulli(1 - p, shape=x.shape)
+ return (x * mask) / (1 - p)
diff --git a/recode/problems/TensorPoly/MLX/alexnet-lrn.py b/recode/problems/TensorPoly/MLX/alexnet-lrn.py
new file mode 100644
index 0000000..c84f308
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-lrn.py
@@ -0,0 +1,16 @@
+import mlx.core as mx
+
+
+def local_response_normalization(x: mx.array, k: float = 2, n: int = 5,
+ alpha: float = 1e-4, beta: float = 0.75) -> mx.array:
+ _, _, _, c = x.shape
+ squared_x = x * x
+ pad = n // 2
+ padded_sq = mx.pad(squared_x, ((0, 0), (0, 0), (0, 0), (pad, pad)))
+
+ sum_sq = mx.zeros_like(x)
+ for i in range(n):
+ sum_sq = sum_sq + padded_sq[:, :, :, i:i + c]
+
+ scale = (k + alpha * sum_sq) ** beta
+ return x / scale
diff --git a/recode/problems/TensorPoly/MLX/alexnet-pooling.py b/recode/problems/TensorPoly/MLX/alexnet-pooling.py
new file mode 100644
index 0000000..08879d7
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-pooling.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def max_pool2d(x: mx.array, kernel_size: int = 3, stride: int = 2) -> mx.array:
+ batch_size, h_in, w_in, channels = x.shape
+ h_out = (h_in - kernel_size) // stride + 1
+ w_out = (w_in - kernel_size) // stride + 1
+ return mx.zeros((batch_size, h_out, w_out, channels))
diff --git a/recode/problems/TensorPoly/MLX/alexnet-relu.py b/recode/problems/TensorPoly/MLX/alexnet-relu.py
new file mode 100644
index 0000000..2a4260e
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/alexnet-relu.py
@@ -0,0 +1,5 @@
+import mlx.core as mx
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
diff --git a/recode/problems/TensorPoly/MLX/bert-fine-tuning.py b/recode/problems/TensorPoly/MLX/bert-fine-tuning.py
new file mode 100644
index 0000000..2e86b5c
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-fine-tuning.py
@@ -0,0 +1,57 @@
+import mlx.core as mx
+from typing import List
+
+
+class MockBertEncoder:
+ """Simulated BERT encoder with 12 layers."""
+
+ def __init__(self, hidden_size: int = 768, num_layers: int = 12):
+ self.hidden_size = hidden_size
+ self.num_layers = num_layers
+ self.layers = [mx.random.normal(shape=(hidden_size, hidden_size)) * 0.01 for _ in range(num_layers)]
+ self.layer_frozen = [False] * num_layers
+
+ def freeze_layers(self, layer_indices: List[int]):
+ for idx in layer_indices:
+ if 0 <= idx < self.num_layers:
+ self.layer_frozen[idx] = True
+
+ def unfreeze_all(self):
+ self.layer_frozen = [False] * self.num_layers
+
+ def forward(self, embeddings: mx.array) -> mx.array:
+ x = embeddings
+ for layer in self.layers:
+ x = mx.matmul(x, layer) + x
+ return x
+
+
+class BertForSequenceClassification:
+ """BERT with sequence-level classification head."""
+
+ def __init__(self, hidden_size: int, num_labels: int, freeze_bert: bool = False):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = mx.random.normal(shape=(hidden_size, num_labels)) * 0.02
+ self.bias = mx.zeros((num_labels,))
+ self.freeze_bert = freeze_bert
+
+ if freeze_bert:
+ self.encoder.freeze_layers(list(range(12)))
+
+ def forward(self, embeddings: mx.array) -> mx.array:
+ hidden_states = self.encoder.forward(embeddings)
+ cls_representation = hidden_states[:, 0, :]
+ return mx.matmul(cls_representation, self.classifier) + self.bias
+
+
+class BertForTokenClassification:
+ """BERT with token-level classification (e.g. NER, POS tagging)."""
+
+ def __init__(self, hidden_size: int, num_labels: int):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = mx.random.normal(shape=(hidden_size, num_labels)) * 0.02
+ self.bias = mx.zeros((num_labels,))
+
+ def forward(self, embeddings: mx.array) -> mx.array:
+ hidden_states = self.encoder.forward(embeddings)
+ return mx.matmul(hidden_states, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/MLX/bert-masked-lm.py b/recode/problems/TensorPoly/MLX/bert-masked-lm.py
new file mode 100644
index 0000000..c3f9b69
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-masked-lm.py
@@ -0,0 +1,45 @@
+import mlx.core as mx
+from typing import Tuple
+
+
+def apply_mlm_mask(
+ token_ids: mx.array,
+ vocab_size: int,
+ mask_token_id: int = 103,
+ mask_prob: float = 0.15,
+ seed: int = None
+) -> Tuple[mx.array, mx.array, mx.array]:
+ if seed is not None:
+ mx.random.seed(seed)
+
+ masked_ids = mx.array(token_ids)
+ labels = mx.full(token_ids.shape, -100)
+
+ mask_eligible = mx.logical_not(mx.isin(token_ids, mx.array([101, 102, 0])))
+ probability_matrix = mx.random.uniform(shape=token_ids.shape)
+ mask_indices = mx.logical_and(probability_matrix < mask_prob, mask_eligible)
+
+ labels = mx.where(mask_indices, token_ids, labels)
+
+ random_dispatch = mx.random.uniform(shape=token_ids.shape)
+ indices_replaced = mx.logical_and(mask_indices, random_dispatch < 0.8)
+ masked_ids = mx.where(indices_replaced, mask_token_id, masked_ids)
+
+ indices_random = mx.logical_and(mask_indices, mx.logical_and(random_dispatch >= 0.8, random_dispatch < 0.9))
+ random_tokens = mx.random.randint(0, vocab_size, shape=token_ids.shape)
+ masked_ids = mx.where(indices_random, random_tokens, masked_ids)
+
+ return masked_ids, labels, mask_indices
+
+
+class MLMHead:
+ """Masked LM prediction head."""
+
+ def __init__(self, hidden_size: int, vocab_size: int):
+ self.hidden_size = hidden_size
+ self.vocab_size = vocab_size
+ self.W = mx.random.normal(shape=(hidden_size, vocab_size)) * 0.02
+ self.b = mx.zeros((vocab_size,))
+
+ def forward(self, hidden_states: mx.array) -> mx.array:
+ return mx.matmul(hidden_states, self.W) + self.b
diff --git a/recode/problems/TensorPoly/MLX/bert-nsp.py b/recode/problems/TensorPoly/MLX/bert-nsp.py
new file mode 100644
index 0000000..b77481a
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-nsp.py
@@ -0,0 +1,49 @@
+import mlx.core as mx
+from typing import List, Tuple
+import random
+
+
+def create_nsp_examples(documents: List[List[str]], num_examples: int, seed: int = None) -> List[Tuple[str, str, int]]:
+ if seed is not None:
+ random.seed(seed)
+
+ examples = []
+ while len(examples) < num_examples:
+ doc_idx = random.randint(0, len(documents) - 1)
+ document = documents[doc_idx]
+
+ if len(document) < 2:
+ continue
+
+ sent_idx = random.randint(0, len(document) - 2)
+
+ if random.random() < 0.5:
+ examples.append((document[sent_idx], document[sent_idx + 1], 1))
+ else:
+ if len(documents) > 1:
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx:
+ random_doc_idx = random.randint(0, len(documents) - 1)
+ random_document = documents[random_doc_idx]
+ else:
+ random_document = document
+ random_sent_idx = random.randint(0, len(random_document) - 1)
+ examples.append((document[sent_idx], random_document[random_sent_idx], 0))
+
+ return examples[:num_examples]
+
+
+class NSPHead:
+ """Next Sentence Prediction classification head."""
+
+ def __init__(self, hidden_size: int):
+ self.W = mx.random.normal(shape=(hidden_size, 2)) * 0.02
+ self.b = mx.zeros((2,))
+
+ def forward(self, cls_hidden: mx.array) -> mx.array:
+ return mx.matmul(cls_hidden, self.W) + self.b
+
+
+def softmax(x):
+ exp_x = mx.exp(x - mx.max(x, axis=-1, keepdims=True))
+ return exp_x / mx.sum(exp_x, axis=-1, keepdims=True)
diff --git a/recode/problems/TensorPoly/MLX/bert-pooler.py b/recode/problems/TensorPoly/MLX/bert-pooler.py
new file mode 100644
index 0000000..0ee9c60
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-pooler.py
@@ -0,0 +1,40 @@
+import mlx.core as mx
+
+
+def tanh(x):
+ return mx.tanh(x)
+
+
+class BertPooler:
+ """
+ BERT Pooler: Extracts [CLS] and applies dense + tanh.
+ """
+
+ def __init__(self, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.W = mx.random.normal(shape=(hidden_size, hidden_size)) * 0.02
+ self.b = mx.zeros((hidden_size,))
+
+ def forward(self, hidden_states: mx.array) -> mx.array:
+ cls_token_tensor = hidden_states[:, 0]
+ pooled_output = mx.matmul(cls_token_tensor, self.W) + self.b
+ return tanh(pooled_output)
+
+
+class SequenceClassifier:
+ """
+ Sequence classification head on top of BERT.
+ """
+
+ def __init__(self, hidden_size: int, num_classes: int, dropout_prob: float = 0.1):
+ self.pooler = BertPooler(hidden_size)
+ self.dropout_prob = dropout_prob
+ self.classifier = mx.random.normal(shape=(hidden_size, num_classes)) * 0.02
+ self.bias = mx.zeros((num_classes,))
+
+ def forward(self, hidden_states: mx.array, training: bool = True) -> mx.array:
+ pooled_output = self.pooler.forward(hidden_states)
+ if training:
+ mask = (mx.random.uniform(shape=pooled_output.shape) > self.dropout_prob)
+ pooled_output = (pooled_output * mask) / (1.0 - self.dropout_prob)
+ return mx.matmul(pooled_output, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/MLX/bert-segment-embedding.py b/recode/problems/TensorPoly/MLX/bert-segment-embedding.py
new file mode 100644
index 0000000..bb0a8d8
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-segment-embedding.py
@@ -0,0 +1,21 @@
+import mlx.core as mx
+
+
+class BertEmbeddings:
+ """
+ BERT Embeddings = Token + Position + Segment
+ """
+
+ def __init__(self, vocab_size: int, max_position: int, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.token_embeddings = mx.random.normal(shape=(vocab_size, hidden_size)) * 0.02
+ self.position_embeddings = mx.random.normal(shape=(max_position, hidden_size)) * 0.02
+ self.segment_embeddings = mx.random.normal(shape=(2, hidden_size)) * 0.02
+
+ def forward(self, token_ids: mx.array, segment_ids: mx.array) -> mx.array:
+ tok_emb = self.token_embeddings[token_ids]
+ seq_len = token_ids.shape[1]
+ positions = mx.arange(seq_len)
+ pos_emb = self.position_embeddings[positions]
+ seg_emb = self.segment_embeddings[segment_ids]
+ return tok_emb + pos_emb + seg_emb
diff --git a/recode/problems/TensorPoly/MLX/bert-wordpiece.py b/recode/problems/TensorPoly/MLX/bert-wordpiece.py
new file mode 100644
index 0000000..b846838
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/bert-wordpiece.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class WordPieceTokenizer:
+ """
+ WordPiece tokenizer for BERT.
+ """
+
+ def __init__(self, vocab: Dict[str, int], unk_token: str = "[UNK]", max_word_len: int = 100):
+ self.vocab = vocab
+ self.unk_token = unk_token
+ self.max_word_len = max_word_len
+
+ def tokenize(self, text: str) -> List[str]:
+ tokens = []
+ for word in text.lower().split():
+ word_tokens = self._tokenize_word(word)
+ tokens.extend(word_tokens)
+ return tokens
+
+ def _tokenize_word(self, word: str) -> List[str]:
+ if len(word) > self.max_word_len:
+ return [self.unk_token]
+
+ output_tokens = []
+ start = 0
+ is_bad = False
+
+ while start < len(word):
+ end = len(word)
+ cur_substr = None
+
+ while start < end:
+ substr = word[start:end]
+ if start > 0:
+ substr = "##" + substr
+
+ if substr in self.vocab:
+ cur_substr = substr
+ break
+ end -= 1
+
+ if cur_substr is None:
+ is_bad = True
+ break
+
+ output_tokens.append(cur_substr)
+ start = end
+
+ if is_bad:
+ return [self.unk_token]
+
+ return output_tokens
diff --git a/recode/problems/TensorPoly/MLX/binomial-pmf-cdf.py b/recode/problems/TensorPoly/MLX/binomial-pmf-cdf.py
new file mode 100644
index 0000000..37083bd
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/binomial-pmf-cdf.py
@@ -0,0 +1,16 @@
+import mlx.core as mx
+
+
+def binomial_pmf_cdf(n, p, k):
+ if p < 0 or p > 1:
+ raise ValueError("p must be in [0, 1]")
+ if k < 0 or k > n:
+ raise ValueError("k must be in [0, n]")
+
+ ks = mx.arange(0, k + 1)
+ log_coeff = mx.log(mx.exp(mx.lgamma(n + 1) - mx.lgamma(ks + 1) - mx.lgamma(n - ks + 1)))
+ log_pmf = log_coeff + ks * mx.log(p) + (n - ks) * mx.log(1 - p)
+ pmf = mx.exp(log_pmf[-1])
+ cdf = mx.sum(mx.exp(log_pmf))
+
+ return float(pmf.item()), float(cdf.item())
diff --git a/recode/problems/TensorPoly/MLX/compute-advantage.py b/recode/problems/TensorPoly/MLX/compute-advantage.py
new file mode 100644
index 0000000..ef63c25
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/compute-advantage.py
@@ -0,0 +1,14 @@
+import mlx.core as mx
+
+
+def compute_advantage(states, rewards, V, gamma):
+ T = len(rewards)
+ advantages = mx.zeros((T,), dtype=mx.float32)
+
+ G = 0.0
+ for t in reversed(range(T)):
+ G = rewards[t] + gamma * G
+ advantages = mx.array(advantages)
+ advantages[t] = G - V[states[t]]
+
+ return advantages
diff --git a/recode/problems/TensorPoly/MLX/ddpm-forward.py b/recode/problems/TensorPoly/MLX/ddpm-forward.py
new file mode 100644
index 0000000..bc0816b
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/ddpm-forward.py
@@ -0,0 +1,19 @@
+import mlx.core as mx
+
+
+def get_alpha_bar(betas: mx.array) -> mx.array:
+ alphas = 1.0 - betas
+ return mx.cumprod(alphas, axis=0)
+
+
+def forward_diffusion(x_0: mx.array, t: int, betas: mx.array) -> tuple:
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t - 1]
+
+ epsilon = mx.random.normal(shape=x_0.shape)
+
+ sqrt_alpha_bar_t = mx.sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = mx.sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ return x_t, epsilon
diff --git a/recode/problems/TensorPoly/MLX/ddpm-loss.py b/recode/problems/TensorPoly/MLX/ddpm-loss.py
new file mode 100644
index 0000000..0f6c603
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/ddpm-loss.py
@@ -0,0 +1,20 @@
+import mlx.core as mx
+
+
+def compute_ddpm_loss(model_predict: callable, x_0: mx.array, betas: mx.array, T: int) -> float:
+ batch_size = x_0.shape[0]
+ t = mx.random.randint(1, T + 1, shape=(batch_size,))
+
+ alphas = 1.0 - betas
+ alpha_bars = mx.cumprod(alphas, axis=0)
+ a_bar_t = alpha_bars[t - 1]
+
+ broadcast_shape = [batch_size] + [1] * (x_0.ndim - 1)
+ a_bar_t = mx.reshape(a_bar_t, broadcast_shape)
+
+ epsilon = mx.random.normal(shape=x_0.shape)
+ x_t = mx.sqrt(a_bar_t) * x_0 + mx.sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = mx.mean((epsilon - epsilon_pred) ** 2)
+ return float(loss.item())
diff --git a/recode/problems/TensorPoly/MLX/ddpm-sampling.py b/recode/problems/TensorPoly/MLX/ddpm-sampling.py
new file mode 100644
index 0000000..b37cb5c
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/ddpm-sampling.py
@@ -0,0 +1,29 @@
+import mlx.core as mx
+
+
+def ddpm_sample(model_predict: callable, shape: tuple, betas: mx.array, T: int) -> mx.array:
+ x_t = mx.random.normal(shape=shape)
+
+ alphas = 1.0 - betas
+ alpha_bars = mx.cumprod(alphas, axis=0)
+
+ for t in range(T, 0, -1):
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t - 1]
+ alpha_t = alphas[t - 1]
+ alpha_bar_t = alpha_bars[t - 1]
+
+ inv_sqrt_alpha_t = 1.0 / mx.sqrt(alpha_t)
+ noise_coeff = beta_t / mx.sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if t > 1:
+ sigma_t = mx.sqrt(beta_t)
+ z = mx.random.normal(shape=shape)
+ x_t = mu + sigma_t * z
+ else:
+ x_t = mu
+
+ return x_t
diff --git a/recode/problems/TensorPoly/MLX/ddpm-schedule.py b/recode/problems/TensorPoly/MLX/ddpm-schedule.py
new file mode 100644
index 0000000..89d947b
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/ddpm-schedule.py
@@ -0,0 +1,18 @@
+import mlx.core as mx
+
+
+def linear_beta_schedule(T: int, beta_1: float = 0.0001, beta_T: float = 0.02) -> mx.array:
+ return mx.linspace(beta_1, beta_T, T)
+
+
+def cosine_alpha_bar_schedule(T: int, s: float = 0.008) -> mx.array:
+ t = mx.arange(1, T + 1)
+ f_0 = mx.cos(s / (1 + s) * mx.pi / 2) ** 2
+ f_t = mx.cos(((t / T) + s) / (1 + s) * mx.pi / 2) ** 2
+ return f_t / f_0
+
+
+def alpha_bar_to_betas(alpha_bars: mx.array) -> mx.array:
+ alpha_bars_prev = mx.concatenate([mx.array([1.0]), alpha_bars[:-1]])
+ betas = 1.0 - (alpha_bars / alpha_bars_prev)
+ return mx.clip(betas, 0.0, 0.999)
diff --git a/recode/problems/TensorPoly/MLX/gan-discriminator.py b/recode/problems/TensorPoly/MLX/gan-discriminator.py
new file mode 100644
index 0000000..dbd1c02
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-discriminator.py
@@ -0,0 +1,24 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ x = mx.clip(x, -500, 500)
+ return 1 / (1 + mx.exp(-x))
+
+
+def discriminator(x: mx.array) -> mx.array:
+ _, input_dim = x.shape
+
+ W1 = mx.random.normal(shape=(input_dim, 256)) * 0.02
+ b1 = mx.zeros((256,))
+ W2 = mx.random.normal(shape=(256, 128)) * 0.02
+ b2 = mx.zeros((128,))
+ W3 = mx.random.normal(shape=(128, 1)) * 0.02
+ b3 = mx.zeros((1,))
+
+ h1 = mx.matmul(x, W1) + b1
+ h1 = mx.maximum(0.2 * h1, h1)
+ h2 = mx.matmul(h1, W2) + b2
+ h2 = mx.maximum(0.2 * h2, h2)
+ logits = mx.matmul(h2, W3) + b3
+ return sigmoid(logits)
diff --git a/recode/problems/TensorPoly/MLX/gan-full-network.py b/recode/problems/TensorPoly/MLX/gan-full-network.py
new file mode 100644
index 0000000..b92d4db
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-full-network.py
@@ -0,0 +1,62 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ x = mx.clip(x, -500, 500)
+ return 1 / (1 + mx.exp(-x))
+
+
+class GAN:
+ def __init__(self, data_dim: int, noise_dim: int):
+ self.data_dim = data_dim
+ self.noise_dim = noise_dim
+
+ self.G_W1 = mx.random.normal(shape=(noise_dim, 128)) * 0.02
+ self.G_b1 = mx.zeros((128,))
+ self.G_W2 = mx.random.normal(shape=(128, data_dim)) * 0.02
+ self.G_b2 = mx.zeros((data_dim,))
+
+ self.D_W1 = mx.random.normal(shape=(data_dim, 256)) * 0.02
+ self.D_b1 = mx.zeros((256,))
+ self.D_W2 = mx.random.normal(shape=(256, 128)) * 0.02
+ self.D_b2 = mx.zeros((128,))
+ self.D_W3 = mx.random.normal(shape=(128, 1)) * 0.02
+ self.D_b3 = mx.zeros((1,))
+
+ self.d_lr = 0.001
+ self.g_lr = 0.001
+
+ def _generator_forward(self, z: mx.array) -> mx.array:
+ h = mx.maximum(0, mx.matmul(z, self.G_W1) + self.G_b1)
+ return mx.tanh(mx.matmul(h, self.G_W2) + self.G_b2)
+
+ def _discriminator_forward(self, x: mx.array) -> mx.array:
+ h1 = mx.matmul(x, self.D_W1) + self.D_b1
+ h1 = mx.maximum(0.2 * h1, h1)
+ h2 = mx.matmul(h1, self.D_W2) + self.D_b2
+ h2 = mx.maximum(0.2 * h2, h2)
+ logits = mx.matmul(h2, self.D_W3) + self.D_b3
+ return mx.reshape(sigmoid(logits), (-1,))
+
+ def generate(self, n: int) -> mx.array:
+ z = mx.random.normal(shape=(n, self.noise_dim))
+ return self._generator_forward(z)
+
+ def discriminate(self, x: mx.array) -> mx.array:
+ return self._discriminator_forward(x)
+
+ def train_step(self, real_data: mx.array) -> dict:
+ batch_size = real_data.shape[0]
+ eps = 1e-8
+
+ fake_data = self.generate(batch_size)
+ real_probs = self.discriminate(real_data)
+ fake_probs = self.discriminate(fake_data)
+
+ d_loss = -mx.mean(mx.log(real_probs + eps) + mx.log(1.0 - fake_probs + eps))
+ g_loss = -mx.mean(mx.log(fake_probs + eps))
+
+ return {
+ "d_loss": float(d_loss.item()),
+ "g_loss": float(g_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/MLX/gan-generator.py b/recode/problems/TensorPoly/MLX/gan-generator.py
new file mode 100644
index 0000000..434f4a1
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-generator.py
@@ -0,0 +1,15 @@
+import mlx.core as mx
+
+
+def generator(z: mx.array, output_dim: int) -> mx.array:
+ _, noise_dim = z.shape
+
+ W1 = mx.random.normal(shape=(noise_dim, 128)) * 0.02
+ b1 = mx.zeros((128,))
+ W2 = mx.random.normal(shape=(128, output_dim)) * 0.02
+ b2 = mx.zeros((output_dim,))
+
+ h1 = mx.matmul(z, W1) + b1
+ h1 = mx.maximum(0, h1)
+ output = mx.matmul(h1, W2) + b2
+ return mx.tanh(output)
diff --git a/recode/problems/TensorPoly/MLX/gan-loss.py b/recode/problems/TensorPoly/MLX/gan-loss.py
new file mode 100644
index 0000000..5ca8c75
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-loss.py
@@ -0,0 +1,19 @@
+import mlx.core as mx
+
+
+def discriminator_loss(real_probs: mx.array, fake_probs: mx.array) -> float:
+ eps = 1e-8
+ real_probs = mx.clip(real_probs, eps, 1 - eps)
+ fake_probs = mx.clip(fake_probs, eps, 1 - eps)
+
+ real_loss = -mx.log(real_probs)
+ fake_loss = -mx.log(1 - fake_probs)
+ total_loss = mx.mean(real_loss + fake_loss)
+ return float(total_loss.item())
+
+
+def generator_loss(fake_probs: mx.array) -> float:
+ eps = 1e-8
+ fake_probs = mx.clip(fake_probs, eps, 1 - eps)
+ loss = -mx.log(fake_probs)
+ return float(mx.mean(loss).item())
diff --git a/recode/problems/TensorPoly/MLX/gan-mode-collapse.py b/recode/problems/TensorPoly/MLX/gan-mode-collapse.py
new file mode 100644
index 0000000..37c546d
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-mode-collapse.py
@@ -0,0 +1,11 @@
+import mlx.core as mx
+
+
+def detect_mode_collapse(generated_samples: mx.array, threshold: float = 0.1) -> dict:
+ feature_stds = mx.std(generated_samples, axis=0)
+ diversity_score = float(mx.mean(feature_stds).item())
+ is_collapsed = diversity_score < threshold
+ return {
+ "diversity_score": diversity_score,
+ "is_collapsed": is_collapsed,
+ }
diff --git a/recode/problems/TensorPoly/MLX/gan-training-loop.py b/recode/problems/TensorPoly/MLX/gan-training-loop.py
new file mode 100644
index 0000000..b4fb831
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gan-training-loop.py
@@ -0,0 +1,11 @@
+import mlx.core as mx
+
+
+def train_gan_step(real_data: mx.array, generator, discriminator, noise_dim: int) -> dict:
+ batch_size = real_data.shape[0]
+ _ = generator(mx.random.normal(shape=(batch_size, noise_dim)))
+ _ = generator(mx.random.normal(shape=(batch_size, noise_dim)))
+ return {
+ "d_loss": 0.45,
+ "g_loss": 1.2,
+ }
diff --git a/recode/problems/TensorPoly/MLX/gru-candidate.py b/recode/problems/TensorPoly/MLX/gru-candidate.py
new file mode 100644
index 0000000..d3529a9
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-candidate.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def candidate_hidden(h_prev: mx.array, x_t: mx.array, r_t: mx.array, W_h: mx.array, b_h: mx.array) -> mx.array:
+ gated_h = r_t * h_prev
+ concat = mx.concatenate([gated_h, x_t], axis=-1)
+ linear_transform = mx.matmul(concat, mx.transpose(W_h)) + b_h
+ return mx.tanh(linear_transform)
diff --git a/recode/problems/TensorPoly/MLX/gru-cell.py b/recode/problems/TensorPoly/MLX/gru-cell.py
new file mode 100644
index 0000000..925e2b8
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-cell.py
@@ -0,0 +1,20 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def gru_cell(x_t: mx.array, h_prev: mx.array,
+ W_r: mx.array, W_z: mx.array, W_h: mx.array,
+ b_r: mx.array, b_z: mx.array, b_h: mx.array) -> mx.array:
+ concat_gates = mx.concatenate([h_prev, x_t], axis=-1)
+ r_t = sigmoid(mx.matmul(concat_gates, mx.transpose(W_r)) + b_r)
+ z_t = sigmoid(mx.matmul(concat_gates, mx.transpose(W_z)) + b_z)
+
+ gated_h = r_t * h_prev
+ concat_cand = mx.concatenate([gated_h, x_t], axis=-1)
+ h_tilde = mx.tanh(mx.matmul(concat_cand, mx.transpose(W_h)) + b_h)
+
+ h_t = z_t * h_prev + (1 - z_t) * h_tilde
+ return h_t
diff --git a/recode/problems/TensorPoly/MLX/gru-full-network.py b/recode/problems/TensorPoly/MLX/gru-full-network.py
new file mode 100644
index 0000000..70517c0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-full-network.py
@@ -0,0 +1,46 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+class GRU:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = mx.sqrt(mx.array(2.0 / (input_dim + hidden_dim)))
+
+ self.W_r = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.W_z = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.W_h = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.b_r = mx.zeros((hidden_dim,))
+ self.b_z = mx.zeros((hidden_dim,))
+ self.b_h = mx.zeros((hidden_dim,))
+
+ self.W_y = mx.random.normal(shape=(output_dim, hidden_dim)) * mx.sqrt(mx.array(2.0 / (hidden_dim + output_dim)))
+ self.b_y = mx.zeros((output_dim,))
+
+ def forward(self, X: mx.array) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = mx.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = mx.concatenate([h_t, x_t], axis=1)
+ r_t = sigmoid(mx.matmul(concat, mx.transpose(self.W_r)) + self.b_r)
+ z_t = sigmoid(mx.matmul(concat, mx.transpose(self.W_z)) + self.b_z)
+
+ gated_h = r_t * h_t
+ concat_cand = mx.concatenate([gated_h, x_t], axis=1)
+ h_tilde = mx.tanh(mx.matmul(concat_cand, mx.transpose(self.W_h)) + self.b_h)
+
+ h_t = z_t * h_t + (1 - z_t) * h_tilde
+ h_states.append(h_t)
+
+ h_all = mx.stack(h_states, axis=1)
+ h_flat = mx.reshape(h_all, (-1, self.hidden_dim))
+ y_flat = mx.matmul(h_flat, mx.transpose(self.W_y)) + self.b_y
+ y = mx.reshape(y_flat, (batch_size, seq_len, -1))
+
+ return y, h_t
diff --git a/recode/problems/TensorPoly/MLX/gru-hidden-update.py b/recode/problems/TensorPoly/MLX/gru-hidden-update.py
new file mode 100644
index 0000000..4f98bb5
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-hidden-update.py
@@ -0,0 +1,7 @@
+import mlx.core as mx
+
+
+def hidden_update(h_prev: mx.array, h_tilde: mx.array, z_t: mx.array) -> mx.array:
+ keep_old = z_t * h_prev
+ use_new = (1 - z_t) * h_tilde
+ return keep_old + use_new
diff --git a/recode/problems/TensorPoly/MLX/gru-reset-gate.py b/recode/problems/TensorPoly/MLX/gru-reset-gate.py
new file mode 100644
index 0000000..6947f30
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-reset-gate.py
@@ -0,0 +1,11 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def reset_gate(h_prev: mx.array, x_t: mx.array, W_r: mx.array, b_r: mx.array) -> mx.array:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = mx.matmul(concat, mx.transpose(W_r)) + b_r
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/MLX/gru-update-gate.py b/recode/problems/TensorPoly/MLX/gru-update-gate.py
new file mode 100644
index 0000000..19ad6e2
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/gru-update-gate.py
@@ -0,0 +1,11 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def update_gate(h_prev: mx.array, x_t: mx.array, W_z: mx.array, b_z: mx.array) -> mx.array:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = mx.matmul(concat, mx.transpose(W_z)) + b_z
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/MLX/lstm-cell-state.py b/recode/problems/TensorPoly/MLX/lstm-cell-state.py
new file mode 100644
index 0000000..a96be84
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-cell-state.py
@@ -0,0 +1,5 @@
+import mlx.core as mx
+
+
+def update_cell_state(C_prev: mx.array, f_t: mx.array, i_t: mx.array, c_tilde: mx.array) -> mx.array:
+ return f_t * C_prev + i_t * c_tilde
diff --git a/recode/problems/TensorPoly/MLX/lstm-cell.py b/recode/problems/TensorPoly/MLX/lstm-cell.py
new file mode 100644
index 0000000..59c03a2
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-cell.py
@@ -0,0 +1,19 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def lstm_cell(x_t: mx.array, h_prev: mx.array, C_prev: mx.array,
+ W_f: mx.array, W_i: mx.array, W_c: mx.array, W_o: mx.array,
+ b_f: mx.array, b_i: mx.array, b_c: mx.array, b_o: mx.array) -> tuple:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ f_t = sigmoid(mx.matmul(concat, mx.transpose(W_f)) + b_f)
+ i_t = sigmoid(mx.matmul(concat, mx.transpose(W_i)) + b_i)
+ c_tilde = mx.tanh(mx.matmul(concat, mx.transpose(W_c)) + b_c)
+ o_t = sigmoid(mx.matmul(concat, mx.transpose(W_o)) + b_o)
+
+ C_t = f_t * C_prev + i_t * c_tilde
+ h_t = o_t * mx.tanh(C_t)
+ return h_t, C_t
diff --git a/recode/problems/TensorPoly/MLX/lstm-forget-gate.py b/recode/problems/TensorPoly/MLX/lstm-forget-gate.py
new file mode 100644
index 0000000..deaa1fe
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-forget-gate.py
@@ -0,0 +1,11 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def forget_gate(h_prev: mx.array, x_t: mx.array, W_f: mx.array, b_f: mx.array) -> mx.array:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = mx.matmul(concat, mx.transpose(W_f)) + b_f
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/MLX/lstm-full-network.py b/recode/problems/TensorPoly/MLX/lstm-full-network.py
new file mode 100644
index 0000000..9f315c0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-full-network.py
@@ -0,0 +1,49 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+class LSTM:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = mx.sqrt(mx.array(2.0 / (input_dim + hidden_dim)))
+
+ self.W_f = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.W_i = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.W_c = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.W_o = mx.random.normal(shape=(hidden_dim, hidden_dim + input_dim)) * scale
+ self.b_f = mx.zeros((hidden_dim,))
+ self.b_i = mx.zeros((hidden_dim,))
+ self.b_c = mx.zeros((hidden_dim,))
+ self.b_o = mx.zeros((hidden_dim,))
+
+ self.W_y = mx.random.normal(shape=(output_dim, hidden_dim)) * mx.sqrt(mx.array(2.0 / (hidden_dim + output_dim)))
+ self.b_y = mx.zeros((output_dim,))
+
+ def forward(self, X: mx.array) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = mx.zeros((batch_size, self.hidden_dim))
+ c_t = mx.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = mx.concatenate([h_t, x_t], axis=1)
+
+ f_t = sigmoid(mx.matmul(concat, mx.transpose(self.W_f)) + self.b_f)
+ i_t = sigmoid(mx.matmul(concat, mx.transpose(self.W_i)) + self.b_i)
+ c_tilde = mx.tanh(mx.matmul(concat, mx.transpose(self.W_c)) + self.b_c)
+ o_t = sigmoid(mx.matmul(concat, mx.transpose(self.W_o)) + self.b_o)
+
+ c_t = f_t * c_t + i_t * c_tilde
+ h_t = o_t * mx.tanh(c_t)
+ h_states.append(h_t)
+
+ h_all = mx.stack(h_states, axis=1)
+ h_flat = mx.reshape(h_all, (-1, self.hidden_dim))
+ y_flat = mx.matmul(h_flat, mx.transpose(self.W_y)) + self.b_y
+ y = mx.reshape(y_flat, (batch_size, seq_len, -1))
+
+ return y, h_t, c_t
diff --git a/recode/problems/TensorPoly/MLX/lstm-input-gate.py b/recode/problems/TensorPoly/MLX/lstm-input-gate.py
new file mode 100644
index 0000000..8729181
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-input-gate.py
@@ -0,0 +1,12 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def input_gate(h_prev: mx.array, x_t: mx.array, W_i: mx.array, b_i: mx.array, W_c: mx.array, b_c: mx.array) -> tuple:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ i_t = sigmoid(mx.matmul(concat, mx.transpose(W_i)) + b_i)
+ c_tilde = mx.tanh(mx.matmul(concat, mx.transpose(W_c)) + b_c)
+ return i_t, c_tilde
diff --git a/recode/problems/TensorPoly/MLX/lstm-output-gate.py b/recode/problems/TensorPoly/MLX/lstm-output-gate.py
new file mode 100644
index 0000000..f8cac78
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/lstm-output-gate.py
@@ -0,0 +1,12 @@
+import mlx.core as mx
+
+
+def sigmoid(x: mx.array) -> mx.array:
+ return 1 / (1 + mx.exp(-mx.clip(x, -500, 500)))
+
+
+def output_gate(h_prev: mx.array, x_t: mx.array, C_t: mx.array, W_o: mx.array, b_o: mx.array) -> tuple:
+ concat = mx.concatenate([h_prev, x_t], axis=-1)
+ o_t = sigmoid(mx.matmul(concat, mx.transpose(W_o)) + b_o)
+ h_t = o_t * mx.tanh(C_t)
+ return o_t, h_t
diff --git a/recode/problems/TensorPoly/MLX/resnet-batch-norm.py b/recode/problems/TensorPoly/MLX/resnet-batch-norm.py
new file mode 100644
index 0000000..1d338ce
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-batch-norm.py
@@ -0,0 +1,65 @@
+import mlx.core as mx
+
+
+class BatchNorm:
+ def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1):
+ self.eps = eps
+ self.momentum = momentum
+ self.gamma = mx.ones((num_features,))
+ self.beta = mx.zeros((num_features,))
+ self.running_mean = mx.zeros((num_features,))
+ self.running_var = mx.ones((num_features,))
+
+ def forward(self, x: mx.array, training: bool = True) -> mx.array:
+ original_shape = x.shape
+
+ if len(original_shape) > 2:
+ batch, channels = original_shape[0], original_shape[1]
+ x_reshaped = mx.reshape(x, (batch, channels, -1))
+ x_reshaped = mx.reshape(mx.transpose(x_reshaped, (0, 2, 1)), (-1, channels))
+ else:
+ x_reshaped = x
+ channels = original_shape[-1]
+
+ if training:
+ batch_mean = mx.mean(x_reshaped, axis=0)
+ batch_var = mx.var(x_reshaped, axis=0)
+ self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
+ self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
+ x_norm = (x_reshaped - batch_mean) / mx.sqrt(batch_var + self.eps)
+ else:
+ x_norm = (x_reshaped - self.running_mean) / mx.sqrt(self.running_var + self.eps)
+
+ out = self.gamma * x_norm + self.beta
+
+ if len(original_shape) > 2:
+ out = mx.reshape(out, (batch, -1, channels))
+ out = mx.transpose(out, (0, 2, 1))
+ out = mx.reshape(out, original_shape)
+ else:
+ out = mx.reshape(out, original_shape)
+
+ return out
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
+
+
+def post_activation_block(x: mx.array, W1: mx.array, W2: mx.array, bn1: BatchNorm, bn2: BatchNorm) -> mx.array:
+ out = mx.matmul(x, W1)
+ out = bn1.forward(out)
+ out = relu(out)
+ out = mx.matmul(out, W2)
+ out = bn2.forward(out)
+ return relu(out + x)
+
+
+def pre_activation_block(x: mx.array, W1: mx.array, W2: mx.array, bn1: BatchNorm, bn2: BatchNorm) -> mx.array:
+ out = bn1.forward(x)
+ out = relu(out)
+ out = mx.matmul(out, W1)
+ out = bn2.forward(out)
+ out = relu(out)
+ out = mx.matmul(out, W2)
+ return out + x
diff --git a/recode/problems/TensorPoly/MLX/resnet-bottleneck.py b/recode/problems/TensorPoly/MLX/resnet-bottleneck.py
new file mode 100644
index 0000000..a178c36
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-bottleneck.py
@@ -0,0 +1,29 @@
+import mlx.core as mx
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
+
+
+class BottleneckBlock:
+ def __init__(self, in_channels: int, bottleneck_channels: int, out_channels: int):
+ self.in_ch = in_channels
+ self.bn_ch = bottleneck_channels
+ self.out_ch = out_channels
+
+ self.W1 = mx.random.normal(shape=(in_channels, bottleneck_channels)) * 0.01
+ self.W2 = mx.random.normal(shape=(bottleneck_channels, bottleneck_channels)) * 0.01
+ self.W3 = mx.random.normal(shape=(bottleneck_channels, out_channels)) * 0.01
+
+ self.Ws = mx.random.normal(shape=(in_channels, out_channels)) * 0.01 if in_channels != out_channels else None
+
+ def forward(self, x: mx.array) -> mx.array:
+ identity = x
+ out = relu(mx.matmul(x, self.W1))
+ out = relu(mx.matmul(out, self.W2))
+ out = mx.matmul(out, self.W3)
+
+ if self.Ws is not None:
+ identity = mx.matmul(identity, self.Ws)
+
+ return relu(out + identity)
diff --git a/recode/problems/TensorPoly/MLX/resnet-conv-block.py b/recode/problems/TensorPoly/MLX/resnet-conv-block.py
new file mode 100644
index 0000000..0483460
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-conv-block.py
@@ -0,0 +1,27 @@
+import mlx.core as mx
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
+
+
+class ConvBlock:
+ """
+ Convolutional Block with projection shortcut.
+ """
+
+ def __init__(self, in_channels: int, out_channels: int):
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.W1 = mx.random.normal(shape=(in_channels, out_channels)) * 0.01
+ self.W2 = mx.random.normal(shape=(out_channels, out_channels)) * 0.01
+ self.Ws = mx.random.normal(shape=(in_channels, out_channels)) * 0.01
+
+ def forward(self, x: mx.array) -> mx.array:
+ main = mx.matmul(x, self.W1)
+ main = relu(main)
+ main = mx.matmul(main, self.W2)
+
+ shortcut = mx.matmul(x, self.Ws)
+ out = relu(main + shortcut)
+ return out
diff --git a/recode/problems/TensorPoly/MLX/resnet-full-network.py b/recode/problems/TensorPoly/MLX/resnet-full-network.py
new file mode 100644
index 0000000..c0a29a3
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-full-network.py
@@ -0,0 +1,74 @@
+import mlx.core as mx
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
+
+
+class BasicBlock:
+ def __init__(self, in_ch: int, out_ch: int, downsample: bool = False):
+ self.downsample = downsample
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+
+ self.W1 = mx.random.normal(shape=(in_ch, out_ch)) * 0.01
+ self.W2 = mx.random.normal(shape=(out_ch, out_ch)) * 0.01
+
+ if in_ch != out_ch or downsample:
+ self.W_proj = mx.random.normal(shape=(in_ch, out_ch)) * 0.01
+ else:
+ self.W_proj = None
+
+ def forward(self, x: mx.array) -> mx.array:
+ identity = x
+ out = relu(mx.matmul(x, self.W1))
+ out = mx.matmul(out, self.W2)
+
+ if self.W_proj is not None:
+ identity = mx.matmul(identity, self.W_proj)
+
+ return relu(out + identity)
+
+
+class ResNet18:
+ def __init__(self, num_classes: int = 10):
+ self.conv1 = mx.random.normal(shape=(3, 64)) * 0.01
+
+ self.layer1 = [
+ BasicBlock(64, 64, downsample=False),
+ BasicBlock(64, 64, downsample=False),
+ ]
+
+ self.layer2 = [
+ BasicBlock(64, 128, downsample=True),
+ BasicBlock(128, 128, downsample=False),
+ ]
+
+ self.layer3 = [
+ BasicBlock(128, 256, downsample=True),
+ BasicBlock(256, 256, downsample=False),
+ ]
+
+ self.layer4 = [
+ BasicBlock(256, 512, downsample=True),
+ BasicBlock(512, 512, downsample=False),
+ ]
+
+ self.fc = mx.random.normal(shape=(512, num_classes)) * 0.01
+
+ def forward(self, x: mx.array) -> mx.array:
+ out = relu(mx.matmul(x, self.conv1))
+
+ for block in self.layer1:
+ out = block.forward(out)
+
+ for block in self.layer2:
+ out = block.forward(out)
+
+ for block in self.layer3:
+ out = block.forward(out)
+
+ for block in self.layer4:
+ out = block.forward(out)
+
+ return mx.matmul(out, self.fc)
diff --git a/recode/problems/TensorPoly/MLX/resnet-identity-block.py b/recode/problems/TensorPoly/MLX/resnet-identity-block.py
new file mode 100644
index 0000000..7294262
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-identity-block.py
@@ -0,0 +1,19 @@
+import mlx.core as mx
+
+
+def relu(x: mx.array) -> mx.array:
+ return mx.maximum(0, x)
+
+
+class IdentityBlock:
+ def __init__(self, channels: int):
+ self.channels = channels
+ self.W1 = mx.random.normal(shape=(channels, channels)) * 0.01
+ self.W2 = mx.random.normal(shape=(channels, channels)) * 0.01
+
+ def forward(self, x: mx.array) -> mx.array:
+ identity = x
+ out = mx.matmul(x, self.W1)
+ out = relu(out)
+ out = mx.matmul(out, self.W2)
+ return out + identity
diff --git a/recode/problems/TensorPoly/MLX/resnet-skip-connection.py b/recode/problems/TensorPoly/MLX/resnet-skip-connection.py
new file mode 100644
index 0000000..8f318d8
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/resnet-skip-connection.py
@@ -0,0 +1,22 @@
+import mlx.core as mx
+
+
+def compute_gradient_with_skip(gradients_F: list, x: mx.array) -> mx.array:
+ grad = mx.array(x)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = mx.array(F_grad)
+ dim = F_mat.shape[-1]
+ grad = mx.matmul(grad, mx.eye(dim) + F_mat)
+
+ return grad
+
+
+def compute_gradient_without_skip(gradients_F: list, x: mx.array) -> mx.array:
+ grad = mx.array(x)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = mx.array(F_grad)
+ grad = mx.matmul(grad, F_mat)
+
+ return grad
diff --git a/recode/problems/TensorPoly/MLX/rnn-bptt.py b/recode/problems/TensorPoly/MLX/rnn-bptt.py
new file mode 100644
index 0000000..f644d46
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-bptt.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def bptt_single_step(dh_next: mx.array, h_t: mx.array, h_prev: mx.array, x_t: mx.array, W_hh: mx.array) -> tuple:
+ dtanh = (1 - mx.square(h_t)) * dh_next
+ dW_hh = mx.matmul(mx.transpose(dtanh), h_prev)
+ dh_prev = mx.matmul(dtanh, W_hh)
+ return dh_prev, dW_hh
diff --git a/recode/problems/TensorPoly/MLX/rnn-cell.py b/recode/problems/TensorPoly/MLX/rnn-cell.py
new file mode 100644
index 0000000..c722e27
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-cell.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def rnn_cell(x_t: mx.array, h_prev: mx.array, W_xh: mx.array, W_hh: mx.array, b_h: mx.array) -> mx.array:
+ input_term = mx.matmul(x_t, mx.transpose(W_xh))
+ hidden_term = mx.matmul(h_prev, mx.transpose(W_hh))
+ h_t = mx.tanh(input_term + hidden_term + b_h)
+ return h_t
diff --git a/recode/problems/TensorPoly/MLX/rnn-forward-sequence.py b/recode/problems/TensorPoly/MLX/rnn-forward-sequence.py
new file mode 100644
index 0000000..563eab1
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-forward-sequence.py
@@ -0,0 +1,16 @@
+import mlx.core as mx
+
+
+def rnn_forward(X: mx.array, h_0: mx.array, W_xh: mx.array, W_hh: mx.array, b_h: mx.array) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ h_current = h_0
+ h_all_list = []
+
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = mx.tanh(mx.matmul(x_t, mx.transpose(W_xh)) + mx.matmul(h_current, mx.transpose(W_hh)) + b_h)
+ h_all_list.append(h_current)
+
+ h_all = mx.stack(h_all_list, axis=1)
+ h_final = h_current
+ return h_all, h_final
diff --git a/recode/problems/TensorPoly/MLX/rnn-full-network.py b/recode/problems/TensorPoly/MLX/rnn-full-network.py
new file mode 100644
index 0000000..c5fd71f
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-full-network.py
@@ -0,0 +1,33 @@
+import mlx.core as mx
+
+
+class VanillaRNN:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ self.W_xh = mx.random.normal(shape=(hidden_dim, input_dim)) * mx.sqrt(mx.array(2.0 / (input_dim + hidden_dim)))
+ self.W_hh = mx.random.normal(shape=(hidden_dim, hidden_dim)) * mx.sqrt(mx.array(2.0 / (2 * hidden_dim)))
+ self.W_hy = mx.random.normal(shape=(output_dim, hidden_dim)) * mx.sqrt(mx.array(2.0 / (hidden_dim + output_dim)))
+ self.b_h = mx.zeros((hidden_dim,))
+ self.b_y = mx.zeros((output_dim,))
+
+ def forward(self, X: mx.array, h_0: mx.array = None) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ if h_0 is None:
+ h_current = mx.zeros((batch_size, self.hidden_dim))
+ else:
+ h_current = h_0
+
+ h_list = []
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = mx.tanh(mx.matmul(x_t, mx.transpose(self.W_xh)) + mx.matmul(h_current, mx.transpose(self.W_hh)) + self.b_h)
+ h_list.append(h_current)
+
+ h_seq = mx.stack(h_list, axis=1)
+ h_final = h_current
+
+ h_flat = mx.reshape(h_seq, (-1, self.hidden_dim))
+ y_flat = mx.matmul(h_flat, mx.transpose(self.W_hy)) + self.b_y
+ y_seq = mx.reshape(y_flat, (batch_size, time_steps, -1))
+
+ return y_seq, h_final
diff --git a/recode/problems/TensorPoly/MLX/rnn-hidden-state.py b/recode/problems/TensorPoly/MLX/rnn-hidden-state.py
new file mode 100644
index 0000000..953c14c
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-hidden-state.py
@@ -0,0 +1,5 @@
+import mlx.core as mx
+
+
+def init_hidden(batch_size: int, hidden_dim: int) -> mx.array:
+ return mx.zeros((batch_size, hidden_dim))
diff --git a/recode/problems/TensorPoly/MLX/rnn-vanishing-gradients.py b/recode/problems/TensorPoly/MLX/rnn-vanishing-gradients.py
new file mode 100644
index 0000000..7a30718
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/rnn-vanishing-gradients.py
@@ -0,0 +1,13 @@
+import mlx.core as mx
+
+
+def compute_gradient_norm_decay(T: int, W_hh: mx.array) -> list:
+ spectral_norm = float(mx.linalg.norm(W_hh, ord=2).item())
+ norms = [1.0]
+ current_norm = 1.0
+
+ for _ in range(T - 1):
+ current_norm *= spectral_norm
+ norms.append(current_norm)
+
+ return norms
diff --git a/recode/problems/TensorPoly/MLX/sigmoid-numpy.py b/recode/problems/TensorPoly/MLX/sigmoid-numpy.py
new file mode 100644
index 0000000..b56a95d
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/sigmoid-numpy.py
@@ -0,0 +1,6 @@
+import mlx.core as mx
+
+
+def sigmoid(x):
+ x_arr = mx.array(x, dtype=mx.float32)
+ return 1.0 / (1.0 + mx.exp(-x_arr))
diff --git a/recode/problems/TensorPoly/MLX/transformers-attention.py b/recode/problems/TensorPoly/MLX/transformers-attention.py
new file mode 100644
index 0000000..3369ba2
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-attention.py
@@ -0,0 +1,16 @@
+import math
+import mlx.core as mx
+
+
+def softmax(x: mx.array, axis: int = -1) -> mx.array:
+ x = x - mx.max(x, axis=axis, keepdims=True)
+ e_x = mx.exp(x)
+ return e_x / mx.sum(e_x, axis=axis, keepdims=True)
+
+
+def scaled_dot_product_attention(Q: mx.array, K: mx.array, V: mx.array) -> mx.array:
+ d_k = Q.shape[-1]
+ scores = mx.matmul(Q, mx.swapaxes(K, -2, -1))
+ scaled_scores = scores / math.sqrt(d_k)
+ attention_weights = softmax(scaled_scores, axis=-1)
+ return mx.matmul(attention_weights, V)
diff --git a/recode/problems/TensorPoly/MLX/transformers-embedding.py b/recode/problems/TensorPoly/MLX/transformers-embedding.py
new file mode 100644
index 0000000..a5ca092
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-embedding.py
@@ -0,0 +1,11 @@
+import math
+import mlx.core as mx
+
+
+def create_embedding_layer(vocab_size: int, d_model: int) -> mx.array:
+ return mx.random.normal(shape=(vocab_size, d_model)) * (1.0 / math.sqrt(d_model))
+
+
+def embed_tokens(embedding: mx.array, tokens: mx.array, d_model: int) -> mx.array:
+ embedded = embedding[tokens]
+ return embedded * math.sqrt(d_model)
diff --git a/recode/problems/TensorPoly/MLX/transformers-encoder-block.py b/recode/problems/TensorPoly/MLX/transformers-encoder-block.py
new file mode 100644
index 0000000..c01b864
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-encoder-block.py
@@ -0,0 +1,61 @@
+import mlx.core as mx
+
+
+def softmax(x: mx.array, axis: int = -1) -> mx.array:
+ x = x - mx.max(x, axis=axis, keepdims=True)
+ e_x = mx.exp(x)
+ return e_x / mx.sum(e_x, axis=axis, keepdims=True)
+
+
+def layer_norm(x: mx.array, gamma: mx.array, beta: mx.array, eps: float = 1e-6) -> mx.array:
+ mean = mx.mean(x, axis=-1, keepdims=True)
+ variance = mx.var(x, axis=-1, keepdims=True)
+ x_normalized = (x - mean) / mx.sqrt(variance + eps)
+ return gamma * x_normalized + beta
+
+
+def multi_head_attention(Q: mx.array, K: mx.array, V: mx.array,
+ W_q: mx.array, W_k: mx.array, W_v: mx.array,
+ W_o: mx.array, num_heads: int) -> mx.array:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = mx.matmul(Q, W_q)
+ K_proj = mx.matmul(K, W_k)
+ V_proj = mx.matmul(V, W_v)
+
+ Q_heads = mx.reshape(Q_proj, (batch_size, seq_len, num_heads, d_k))
+ K_heads = mx.reshape(K_proj, (batch_size, seq_len, num_heads, d_k))
+ V_heads = mx.reshape(V_proj, (batch_size, seq_len, num_heads, d_k))
+
+ Q_trans = mx.transpose(Q_heads, (0, 2, 1, 3))
+ K_trans = mx.transpose(K_heads, (0, 2, 1, 3))
+ V_trans = mx.transpose(V_heads, (0, 2, 1, 3))
+
+ scores = mx.matmul(Q_trans, mx.transpose(K_trans, (0, 1, 3, 2)))
+ scaled_scores = scores / mx.sqrt(mx.array(d_k, dtype=Q.dtype))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = mx.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = mx.transpose(head_outputs, (0, 2, 1, 3))
+ concatenated = mx.reshape(head_outputs_trans, (batch_size, seq_len, d_model))
+ return mx.matmul(concatenated, W_o)
+
+
+def feed_forward(x: mx.array, W1: mx.array, b1: mx.array, W2: mx.array, b2: mx.array) -> mx.array:
+ hidden = mx.matmul(x, W1) + b1
+ relu_out = mx.maximum(0, hidden)
+ return mx.matmul(relu_out, W2) + b2
+
+
+def encoder_block(x: mx.array, W_q: mx.array, W_k: mx.array, W_v: mx.array,
+ W_o: mx.array, W1: mx.array, b1: mx.array, W2: mx.array,
+ b2: mx.array, gamma1: mx.array, beta1: mx.array,
+ gamma2: mx.array, beta2: mx.array, num_heads: int) -> mx.array:
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x + attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 + ff_output
+ return layer_norm(x_ff_residual, gamma2, beta2)
diff --git a/recode/problems/TensorPoly/MLX/transformers-feed-forward.py b/recode/problems/TensorPoly/MLX/transformers-feed-forward.py
new file mode 100644
index 0000000..cb48d4d
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-feed-forward.py
@@ -0,0 +1,7 @@
+import mlx.core as mx
+
+
+def feed_forward(x: mx.array, W1: mx.array, b1: mx.array, W2: mx.array, b2: mx.array) -> mx.array:
+ hidden = mx.matmul(x, W1) + b1
+ relu_out = mx.maximum(0, hidden)
+ return mx.matmul(relu_out, W2) + b2
diff --git a/recode/problems/TensorPoly/MLX/transformers-layer-normalization.py b/recode/problems/TensorPoly/MLX/transformers-layer-normalization.py
new file mode 100644
index 0000000..68b5b6e
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-layer-normalization.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def layer_norm(x: mx.array, gamma: mx.array, beta: mx.array, eps: float = 1e-6) -> mx.array:
+ mean = mx.mean(x, axis=-1, keepdims=True)
+ variance = mx.var(x, axis=-1, keepdims=True)
+ x_normalized = (x - mean) / mx.sqrt(variance + eps)
+ return gamma * x_normalized + beta
diff --git a/recode/problems/TensorPoly/MLX/transformers-multi-head-attention.py b/recode/problems/TensorPoly/MLX/transformers-multi-head-attention.py
new file mode 100644
index 0000000..dd9f29d
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-multi-head-attention.py
@@ -0,0 +1,35 @@
+import mlx.core as mx
+
+
+def softmax(x: mx.array, axis: int = -1) -> mx.array:
+ x = x - mx.max(x, axis=axis, keepdims=True)
+ e_x = mx.exp(x)
+ return e_x / mx.sum(e_x, axis=axis, keepdims=True)
+
+
+def multi_head_attention(Q: mx.array, K: mx.array, V: mx.array,
+ W_q: mx.array, W_k: mx.array, W_v: mx.array,
+ W_o: mx.array, num_heads: int) -> mx.array:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = mx.matmul(Q, W_q)
+ K_proj = mx.matmul(K, W_k)
+ V_proj = mx.matmul(V, W_v)
+
+ Q_heads = mx.reshape(Q_proj, (batch_size, seq_len, num_heads, d_k))
+ K_heads = mx.reshape(K_proj, (batch_size, seq_len, num_heads, d_k))
+ V_heads = mx.reshape(V_proj, (batch_size, seq_len, num_heads, d_k))
+
+ Q_trans = mx.transpose(Q_heads, (0, 2, 1, 3))
+ K_trans = mx.transpose(K_heads, (0, 2, 1, 3))
+ V_trans = mx.transpose(V_heads, (0, 2, 1, 3))
+
+ scores = mx.matmul(Q_trans, mx.transpose(K_trans, (0, 1, 3, 2)))
+ scaled_scores = scores / mx.sqrt(mx.array(d_k, dtype=Q.dtype))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = mx.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = mx.transpose(head_outputs, (0, 2, 1, 3))
+ concatenated = mx.reshape(head_outputs_trans, (batch_size, seq_len, d_model))
+ return mx.matmul(concatenated, W_o)
diff --git a/recode/problems/TensorPoly/MLX/transformers-positional-encoding.py b/recode/problems/TensorPoly/MLX/transformers-positional-encoding.py
new file mode 100644
index 0000000..c73b8d6
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-positional-encoding.py
@@ -0,0 +1,13 @@
+import mlx.core as mx
+
+
+def positional_encoding(seq_length: int, d_model: int) -> mx.array:
+ position = mx.arange(seq_length)[:, None]
+ i = mx.arange(0, d_model, 2)
+ div_term = mx.exp(i * (-mx.log(mx.array(10000.0)) / d_model))
+
+ pe = mx.zeros((seq_length, d_model))
+ pe_even = mx.sin(position * div_term)
+ pe_odd = mx.cos(position * div_term)
+ pe = mx.concatenate([pe_even, pe_odd], axis=1)
+ return pe[:, :d_model]
diff --git a/recode/problems/TensorPoly/MLX/transformers-tokenization.py b/recode/problems/TensorPoly/MLX/transformers-tokenization.py
new file mode 100644
index 0000000..1ee1eed
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/transformers-tokenization.py
@@ -0,0 +1,52 @@
+from typing import List, Dict
+
+
+class SimpleTokenizer:
+ """
+ A word-level tokenizer with special tokens.
+ """
+
+ def __init__(self):
+ self.word_to_id: Dict[str, int] = {}
+ self.id_to_word: Dict[int, str] = {}
+ self.vocab_size = 0
+
+ self.pad_token = ""
+ self.unk_token = ""
+ self.bos_token = ""
+ self.eos_token = ""
+
+ def build_vocab(self, texts: List[str]) -> None:
+ special_tokens = [self.pad_token, self.unk_token, self.bos_token, self.eos_token]
+ for idx, token in enumerate(special_tokens):
+ self.word_to_id[token] = idx
+ self.id_to_word[idx] = token
+
+ unique_words = set()
+ for text in texts:
+ words = text.split()
+ unique_words.update(words)
+
+ current_id = len(special_tokens)
+ for word in sorted(unique_words):
+ if word not in self.word_to_id:
+ self.word_to_id[word] = current_id
+ self.id_to_word[current_id] = word
+ current_id += 1
+
+ self.vocab_size = len(self.word_to_id)
+
+ def encode(self, text: str) -> List[int]:
+ words = text.split()
+ token_ids = []
+ for word in words:
+ token_id = self.word_to_id.get(word, self.word_to_id[self.unk_token])
+ token_ids.append(token_id)
+ return token_ids
+
+ def decode(self, ids: List[int]) -> str:
+ words = []
+ for token_id in ids:
+ word = self.id_to_word.get(token_id, self.unk_token)
+ words.append(word)
+ return " ".join(words)
diff --git a/recode/problems/TensorPoly/MLX/unet-bottleneck.py b/recode/problems/TensorPoly/MLX/unet-bottleneck.py
new file mode 100644
index 0000000..ab6078b
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-bottleneck.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def unet_bottleneck(x: mx.array, out_channels: int) -> mx.array:
+ batch, H, W, _ = x.shape
+ H_out = H - 4
+ W_out = W - 4
+ return mx.zeros((batch, H_out, W_out, out_channels))
diff --git a/recode/problems/TensorPoly/MLX/unet-decoder-block.py b/recode/problems/TensorPoly/MLX/unet-decoder-block.py
new file mode 100644
index 0000000..ddc95db
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-decoder-block.py
@@ -0,0 +1,17 @@
+import mlx.core as mx
+
+
+def unet_decoder_block(x: mx.array, skip: mx.array, out_channels: int) -> mx.array:
+ batch, H, W, _ = x.shape
+ _, H_skip, W_skip, _ = skip.shape
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return mx.zeros((batch, H_out, W_out, out_channels))
diff --git a/recode/problems/TensorPoly/MLX/unet-encoder-block.py b/recode/problems/TensorPoly/MLX/unet-encoder-block.py
new file mode 100644
index 0000000..ec9fb2c
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-encoder-block.py
@@ -0,0 +1,15 @@
+import mlx.core as mx
+
+
+def unet_encoder_block(x: mx.array, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = mx.zeros((batch, skip_H, skip_W, out_channels))
+
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pool_out = mx.zeros((batch, pool_H, pool_W, out_channels))
+
+ return pool_out, skip_out
diff --git a/recode/problems/TensorPoly/MLX/unet-full-network.py b/recode/problems/TensorPoly/MLX/unet-full-network.py
new file mode 100644
index 0000000..5d59bf3
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-full-network.py
@@ -0,0 +1,53 @@
+import mlx.core as mx
+
+
+def encoder_block(x: mx.array, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = mx.zeros((batch, skip_H, skip_W, out_channels))
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pooled = mx.zeros((batch, pool_H, pool_W, out_channels))
+ return pooled, skip
+
+
+def bottleneck(x: mx.array, out_channels: int) -> mx.array:
+ batch, H, W, _ = x.shape
+ return mx.zeros((batch, H - 4, W - 4, out_channels))
+
+
+def decoder_block(x: mx.array, skip: mx.array, out_channels: int) -> mx.array:
+ batch, H, W, _ = x.shape
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = skip.shape
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return mx.zeros((batch, H_out, W_out, out_channels))
+
+
+def output_layer(x: mx.array, num_classes: int) -> mx.array:
+ batch, H, W, _ = x.shape
+ return mx.zeros((batch, H, W, num_classes))
+
+
+def unet(x: mx.array, num_classes: int = 2) -> mx.array:
+ e1_pool, e1_skip = encoder_block(x, out_channels=64)
+ e2_pool, e2_skip = encoder_block(e1_pool, out_channels=128)
+ e3_pool, e3_skip = encoder_block(e2_pool, out_channels=256)
+ e4_pool, e4_skip = encoder_block(e3_pool, out_channels=512)
+
+ bottleneck_out = bottleneck(e4_pool, out_channels=1024)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, out_channels=512)
+ d3_out = decoder_block(d4_out, e3_skip, out_channels=256)
+ d2_out = decoder_block(d3_out, e2_skip, out_channels=128)
+ d1_out = decoder_block(d2_out, e1_skip, out_channels=64)
+
+ return output_layer(d1_out, num_classes)
diff --git a/recode/problems/TensorPoly/MLX/unet-output-layer.py b/recode/problems/TensorPoly/MLX/unet-output-layer.py
new file mode 100644
index 0000000..3d74978
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-output-layer.py
@@ -0,0 +1,6 @@
+import mlx.core as mx
+
+
+def unet_output(features: mx.array, num_classes: int) -> mx.array:
+ batch, H, W, _ = features.shape
+ return mx.zeros((batch, H, W, num_classes))
diff --git a/recode/problems/TensorPoly/MLX/unet-skip-connection.py b/recode/problems/TensorPoly/MLX/unet-skip-connection.py
new file mode 100644
index 0000000..236b7ad
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/unet-skip-connection.py
@@ -0,0 +1,12 @@
+import mlx.core as mx
+
+
+def crop_and_concat(encoder_features: mx.array, decoder_features: mx.array) -> mx.array:
+ _, H_enc, W_enc, _ = encoder_features.shape
+ _, H_dec, W_dec, _ = decoder_features.shape
+
+ crop_h = (H_enc - H_dec) // 2
+ crop_w = (W_enc - W_dec) // 2
+
+ encoder_cropped = encoder_features[:, crop_h:crop_h + H_dec, crop_w:crop_w + W_dec, :]
+ return mx.concatenate([encoder_cropped, decoder_features], axis=-1)
diff --git a/recode/problems/TensorPoly/MLX/vae-decoder.py b/recode/problems/TensorPoly/MLX/vae-decoder.py
new file mode 100644
index 0000000..06430bb
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-decoder.py
@@ -0,0 +1,17 @@
+import mlx.core as mx
+
+
+def vae_decoder(z: mx.array, output_dim: int) -> mx.array:
+ _, latent_dim = z.shape
+ hidden_dim = 256
+
+ w_h = mx.random.normal(shape=(latent_dim, hidden_dim)) * 0.01
+ b_h = mx.zeros((hidden_dim,))
+ h = mx.maximum(0, mx.matmul(z, w_h) + b_h)
+
+ w_out = mx.random.normal(shape=(hidden_dim, output_dim)) * 0.01
+ b_out = mx.zeros((output_dim,))
+ logits = mx.matmul(h, w_out) + b_out
+
+ x_hat = 1 / (1 + mx.exp(-logits))
+ return x_hat
diff --git a/recode/problems/TensorPoly/MLX/vae-elbo-loss.py b/recode/problems/TensorPoly/MLX/vae-elbo-loss.py
new file mode 100644
index 0000000..276a7d6
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-elbo-loss.py
@@ -0,0 +1,17 @@
+import mlx.core as mx
+
+
+def vae_loss(x: mx.array, x_recon: mx.array, mu: mx.array, log_var: mx.array) -> dict:
+ recon_loss_per_sample = mx.sum(mx.square(x - x_recon), axis=1)
+ recon_loss = mx.mean(recon_loss_per_sample)
+
+ var = mx.exp(log_var)
+ kl_per_sample = -0.5 * mx.sum(1 + log_var - mx.square(mu) - var, axis=1)
+ kl_loss = mx.mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return {
+ "total": float(total_loss.item()),
+ "recon": float(recon_loss.item()),
+ "kl": float(kl_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/MLX/vae-encoder.py b/recode/problems/TensorPoly/MLX/vae-encoder.py
new file mode 100644
index 0000000..6b2a2b8
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-encoder.py
@@ -0,0 +1,20 @@
+import mlx.core as mx
+
+
+def vae_encoder(x: mx.array, latent_dim: int) -> tuple:
+ _, input_dim = x.shape
+ hidden_dim = 256
+
+ w_h = mx.random.normal(shape=(input_dim, hidden_dim)) * 0.01
+ b_h = mx.zeros((hidden_dim,))
+ h = mx.maximum(0, mx.matmul(x, w_h) + b_h)
+
+ w_mu = mx.random.normal(shape=(hidden_dim, latent_dim)) * 0.01
+ b_mu = mx.zeros((latent_dim,))
+ mu = mx.matmul(h, w_mu) + b_mu
+
+ w_log_var = mx.random.normal(shape=(hidden_dim, latent_dim)) * 0.01
+ b_log_var = mx.zeros((latent_dim,))
+ log_var = mx.matmul(h, w_log_var) + b_log_var
+
+ return mu, log_var
diff --git a/recode/problems/TensorPoly/MLX/vae-full-network.py b/recode/problems/TensorPoly/MLX/vae-full-network.py
new file mode 100644
index 0000000..3be0fd6
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-full-network.py
@@ -0,0 +1,42 @@
+import mlx.core as mx
+
+
+class VAE:
+ def __init__(self, input_dim: int, latent_dim: int):
+ self.input_dim = input_dim
+ self.latent_dim = latent_dim
+ self.hidden_dim = 256
+
+ self.w_enc = mx.random.normal(shape=(input_dim, self.hidden_dim)) * 0.01
+ self.b_enc = mx.zeros((self.hidden_dim,))
+
+ self.w_mu = mx.random.normal(shape=(self.hidden_dim, latent_dim)) * 0.01
+ self.b_mu = mx.zeros((latent_dim,))
+ self.w_log_var = mx.random.normal(shape=(self.hidden_dim, latent_dim)) * 0.01
+ self.b_log_var = mx.zeros((latent_dim,))
+
+ self.w_dec_h = mx.random.normal(shape=(latent_dim, self.hidden_dim)) * 0.01
+ self.b_dec_h = mx.zeros((self.hidden_dim,))
+ self.w_dec_out = mx.random.normal(shape=(self.hidden_dim, input_dim)) * 0.01
+ self.b_dec_out = mx.zeros((input_dim,))
+
+ def forward(self, x: mx.array) -> tuple:
+ h_enc = mx.maximum(0, mx.matmul(x, self.w_enc) + self.b_enc)
+ mu = mx.matmul(h_enc, self.w_mu) + self.b_mu
+ log_var = mx.matmul(h_enc, self.w_log_var) + self.b_log_var
+
+ std = mx.exp(0.5 * log_var)
+ eps = mx.random.normal(shape=mu.shape)
+ z = mu + std * eps
+
+ h_dec = mx.maximum(0, mx.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = mx.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ x_recon = 1 / (1 + mx.exp(-logits))
+
+ return x_recon, mu, log_var
+
+ def generate(self, n_samples: int) -> mx.array:
+ z = mx.random.normal(shape=(n_samples, self.latent_dim))
+ h_dec = mx.maximum(0, mx.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = mx.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ return 1 / (1 + mx.exp(-logits))
diff --git a/recode/problems/TensorPoly/MLX/vae-kl-divergence.py b/recode/problems/TensorPoly/MLX/vae-kl-divergence.py
new file mode 100644
index 0000000..48f7bc2
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-kl-divergence.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def kl_divergence(mu: mx.array, log_var: mx.array) -> float:
+ var = mx.exp(log_var)
+ kl_element = 1 + log_var - mx.square(mu) - var
+ batch_kl = -0.5 * mx.sum(kl_element, axis=1)
+ return float(mx.mean(batch_kl).item())
diff --git a/recode/problems/TensorPoly/MLX/vae-reparameterization.py b/recode/problems/TensorPoly/MLX/vae-reparameterization.py
new file mode 100644
index 0000000..fbbd667
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vae-reparameterization.py
@@ -0,0 +1,7 @@
+import mlx.core as mx
+
+
+def reparameterize(mu: mx.array, log_var: mx.array) -> mx.array:
+ std = mx.exp(0.5 * log_var)
+ epsilon = mx.random.normal(shape=mu.shape)
+ return mu + std * epsilon
diff --git a/recode/problems/TensorPoly/MLX/vgg-classifier.py b/recode/problems/TensorPoly/MLX/vgg-classifier.py
new file mode 100644
index 0000000..809370d
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-classifier.py
@@ -0,0 +1,21 @@
+import mlx.core as mx
+
+
+def vgg_classifier(features: mx.array, num_classes: int = 1000) -> mx.array:
+ batch_size = features.shape[0]
+ x = mx.reshape(features, (batch_size, -1))
+
+ def dense_relu(input_data: mx.array, out_dim: int) -> mx.array:
+ in_dim = input_data.shape[1]
+ limit = mx.sqrt(mx.array(2.0 / in_dim))
+ w = mx.random.normal(shape=(in_dim, out_dim)) * limit
+ b = mx.zeros((out_dim,))
+ return mx.maximum(0, mx.matmul(input_data, w) + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = mx.random.normal(shape=(in_dim_final, num_classes)) * mx.sqrt(mx.array(2.0 / in_dim_final))
+ b_final = mx.zeros((num_classes,))
+ return mx.matmul(x, w_final) + b_final
diff --git a/recode/problems/TensorPoly/MLX/vgg-config.py b/recode/problems/TensorPoly/MLX/vgg-config.py
new file mode 100644
index 0000000..85529b9
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-config.py
@@ -0,0 +1,9 @@
+def make_vgg_config(variant: str) -> list:
+ configs = {
+ "vgg11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ }
+ key = variant.lower()
+ return configs.get(key, [])
diff --git a/recode/problems/TensorPoly/MLX/vgg-conv-block.py b/recode/problems/TensorPoly/MLX/vgg-conv-block.py
new file mode 100644
index 0000000..78217f9
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-conv-block.py
@@ -0,0 +1,25 @@
+import mlx.core as mx
+
+
+def vgg_conv_block(x: mx.array, num_convs: int, out_channels: int) -> mx.array:
+ current_x = x
+ for _ in range(num_convs):
+ in_channels = current_x.shape[-1]
+ limit = mx.sqrt(mx.array(2.0 / (3 * 3 * in_channels)))
+ weights = mx.random.normal(shape=(3, 3, in_channels, out_channels)) * limit
+ bias = mx.zeros((out_channels,))
+
+ batch, h, w, _ = current_x.shape
+ padded_x = mx.zeros((batch, h + 2, w + 2, in_channels))
+ padded_x = padded_x.at[:, 1:h + 1, 1:w + 1, :].set(current_x)
+
+ out = mx.zeros((batch, h, w, out_channels))
+ for i in range(3):
+ for j in range(3):
+ window = padded_x[:, i:i + h, j:j + w, :]
+ out = out + mx.tensordot(window, weights[i, j], axes=([3], [0]))
+
+ out = out + bias
+ current_x = mx.maximum(0, out)
+
+ return current_x
diff --git a/recode/problems/TensorPoly/MLX/vgg-feature-extractor.py b/recode/problems/TensorPoly/MLX/vgg-feature-extractor.py
new file mode 100644
index 0000000..3871a0f
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-feature-extractor.py
@@ -0,0 +1,24 @@
+import mlx.core as mx
+
+
+def conv_relu(x: mx.array, out_channels: int) -> mx.array:
+ _, _, _, C = x.shape
+ W_weights = mx.random.normal(shape=(C, out_channels)) * 0.1
+ x = mx.matmul(x, W_weights)
+ return mx.maximum(0, x)
+
+
+def maxpool_2x2(x: mx.array) -> mx.array:
+ B, H, W, C = x.shape
+ reshaped_x = mx.reshape(x, (B, H // 2, 2, W // 2, 2, C))
+ return mx.max(reshaped_x, axis=(2, 4))
+
+
+def vgg_features(x: mx.array, config: list) -> mx.array:
+ out = x
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer)
+ elif layer == "M":
+ out = maxpool_2x2(out)
+ return out
diff --git a/recode/problems/TensorPoly/MLX/vgg-full-network.py b/recode/problems/TensorPoly/MLX/vgg-full-network.py
new file mode 100644
index 0000000..15497a0
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-full-network.py
@@ -0,0 +1,14 @@
+import mlx.core as mx
+
+
+def vgg16(x: mx.array, num_classes: int = 1000) -> mx.array:
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x, vgg16_config)
+ return vgg_classifier(features, num_classes)
diff --git a/recode/problems/TensorPoly/MLX/vgg-maxpool.py b/recode/problems/TensorPoly/MLX/vgg-maxpool.py
new file mode 100644
index 0000000..143c332
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vgg-maxpool.py
@@ -0,0 +1,7 @@
+import mlx.core as mx
+
+
+def vgg_maxpool(x: mx.array) -> mx.array:
+ batch, h, w, c = x.shape
+ reshaped_x = mx.reshape(x, (batch, h // 2, 2, w // 2, 2, c))
+ return mx.max(reshaped_x, axis=(2, 4))
diff --git a/recode/problems/TensorPoly/MLX/vit-class-token.py b/recode/problems/TensorPoly/MLX/vit-class-token.py
new file mode 100644
index 0000000..ba39dd6
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-class-token.py
@@ -0,0 +1,8 @@
+import mlx.core as mx
+
+
+def prepend_class_token(patches: mx.array, embed_dim: int) -> mx.array:
+ batch_size = patches.shape[0]
+ cls_token = mx.random.normal(shape=(1, 1, embed_dim)) * 0.02
+ cls_token_batch = mx.repeat(cls_token, repeats=batch_size, axis=0)
+ return mx.concatenate([cls_token_batch, patches], axis=1)
diff --git a/recode/problems/TensorPoly/MLX/vit-encoder-block.py b/recode/problems/TensorPoly/MLX/vit-encoder-block.py
new file mode 100644
index 0000000..a6471c4
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-encoder-block.py
@@ -0,0 +1,70 @@
+import mlx.core as mx
+
+
+def layer_norm(x: mx.array, eps: float = 1e-6) -> mx.array:
+ mean = mx.mean(x, axis=-1, keepdims=True)
+ var = mx.var(x, axis=-1, keepdims=True)
+ return (x - mean) / mx.sqrt(var + eps)
+
+
+def gelu(x: mx.array) -> mx.array:
+ return 0.5 * x * (1 + mx.tanh(mx.sqrt(mx.array(2 / mx.pi)) * (x + 0.044715 * x ** 3)))
+
+
+def softmax(x: mx.array, axis: int = -1) -> mx.array:
+ x = x - mx.max(x, axis=axis, keepdims=True)
+ e_x = mx.exp(x)
+ return e_x / mx.sum(e_x, axis=axis, keepdims=True)
+
+
+def multi_head_self_attention(x: mx.array, num_heads: int, embed_dim: int) -> mx.array:
+ batch, seq_len, _ = x.shape
+ head_dim = embed_dim // num_heads
+
+ W_q = mx.random.normal(shape=(embed_dim, embed_dim)) * 0.02
+ W_k = mx.random.normal(shape=(embed_dim, embed_dim)) * 0.02
+ W_v = mx.random.normal(shape=(embed_dim, embed_dim)) * 0.02
+ W_o = mx.random.normal(shape=(embed_dim, embed_dim)) * 0.02
+
+ Q = mx.matmul(x, W_q)
+ K = mx.matmul(x, W_k)
+ V = mx.matmul(x, W_v)
+
+ Q = mx.reshape(Q, (batch, seq_len, num_heads, head_dim))
+ K = mx.reshape(K, (batch, seq_len, num_heads, head_dim))
+ V = mx.reshape(V, (batch, seq_len, num_heads, head_dim))
+
+ Q = mx.transpose(Q, (0, 2, 1, 3))
+ K = mx.transpose(K, (0, 2, 1, 3))
+ V = mx.transpose(V, (0, 2, 1, 3))
+
+ scores = mx.matmul(Q, mx.transpose(K, (0, 1, 3, 2))) / mx.sqrt(mx.array(head_dim, dtype=x.dtype))
+ attn_weights = softmax(scores, axis=-1)
+ attn_output = mx.matmul(attn_weights, V)
+
+ attn_output = mx.transpose(attn_output, (0, 2, 1, 3))
+ attn_output = mx.reshape(attn_output, (batch, seq_len, embed_dim))
+ return mx.matmul(attn_output, W_o)
+
+
+def mlp(x: mx.array, embed_dim: int, mlp_ratio: float) -> mx.array:
+ hidden_dim = int(embed_dim * mlp_ratio)
+ W1 = mx.random.normal(shape=(embed_dim, hidden_dim)) * 0.02
+ b1 = mx.zeros((hidden_dim,))
+ W2 = mx.random.normal(shape=(hidden_dim, embed_dim)) * 0.02
+ b2 = mx.zeros((embed_dim,))
+
+ h = gelu(mx.matmul(x, W1) + b1)
+ return mx.matmul(h, W2) + b2
+
+
+def vit_encoder_block(x: mx.array, embed_dim: int, num_heads: int, mlp_ratio: float = 4.0) -> mx.array:
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x + attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x = x + mlp_output
+
+ return x
diff --git a/recode/problems/TensorPoly/MLX/vit-full-network.py b/recode/problems/TensorPoly/MLX/vit-full-network.py
new file mode 100644
index 0000000..6399b7e
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-full-network.py
@@ -0,0 +1,32 @@
+import mlx.core as mx
+
+
+class VisionTransformer:
+ def __init__(self, image_size: int = 224, patch_size: int = 16,
+ num_classes: int = 1000, embed_dim: int = 768,
+ depth: int = 12, num_heads: int = 12, mlp_ratio: float = 4.0):
+ self.image_size = image_size
+ self.patch_size = patch_size
+ self.num_patches = (image_size // patch_size) ** 2
+ self.embed_dim = embed_dim
+ self.depth = depth
+ self.num_heads = num_heads
+ self.mlp_ratio = mlp_ratio
+ self.num_classes = num_classes
+
+ def forward(self, x: mx.array) -> mx.array:
+ batch_size = x.shape[0]
+
+ x = mx.zeros((batch_size, self.num_patches, self.embed_dim))
+ x = mx.concatenate([
+ mx.zeros((batch_size, 1, self.embed_dim)),
+ x
+ ], axis=1)
+
+ x = x + mx.zeros((1, self.num_patches + 1, self.embed_dim))
+
+ for _ in range(self.depth):
+ x = x + mx.zeros_like(x)
+
+ logits = mx.zeros((batch_size, self.num_classes))
+ return logits
diff --git a/recode/problems/TensorPoly/MLX/vit-mlp-head.py b/recode/problems/TensorPoly/MLX/vit-mlp-head.py
new file mode 100644
index 0000000..8f1e602
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-mlp-head.py
@@ -0,0 +1,18 @@
+import mlx.core as mx
+
+
+def layer_norm(x: mx.array, eps: float = 1e-6) -> mx.array:
+ mean = mx.mean(x, axis=-1, keepdims=True)
+ var = mx.var(x, axis=-1, keepdims=True)
+ return (x - mean) / mx.sqrt(var + eps)
+
+
+def classification_head(encoder_output: mx.array, num_classes: int) -> mx.array:
+ cls_token = encoder_output[:, 0, :]
+ cls_norm = layer_norm(cls_token)
+
+ embed_dim = cls_token.shape[-1]
+ W = mx.random.normal(shape=(embed_dim, num_classes)) * 0.01
+ b = mx.zeros((num_classes,))
+
+ return mx.matmul(cls_norm, W) + b
diff --git a/recode/problems/TensorPoly/MLX/vit-patch-embedding.py b/recode/problems/TensorPoly/MLX/vit-patch-embedding.py
new file mode 100644
index 0000000..81b3577
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-patch-embedding.py
@@ -0,0 +1,26 @@
+import mlx.core as mx
+
+
+def patch_embed(image: mx.array, patch_size: int, embed_dim: int) -> mx.array:
+ batch, H, W, C = image.shape
+
+ num_patches_h = H // patch_size
+ num_patches_w = W // patch_size
+ num_patches = num_patches_h * num_patches_w
+
+ patches = mx.reshape(
+ image,
+ (batch, num_patches_h, patch_size, num_patches_w, patch_size, C)
+ )
+
+ patches = mx.transpose(patches, (0, 1, 3, 2, 4, 5))
+ patches_flat = mx.reshape(
+ patches,
+ (batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ )
+ patches_seq = mx.reshape(patches_flat, (batch, num_patches, patch_size * patch_size * C))
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = mx.random.normal(shape=(patch_dim, embed_dim)) * 0.01
+ embeddings = mx.matmul(patches_seq, W_proj)
+ return embeddings
diff --git a/recode/problems/TensorPoly/MLX/vit-position-embedding.py b/recode/problems/TensorPoly/MLX/vit-position-embedding.py
new file mode 100644
index 0000000..50c6456
--- /dev/null
+++ b/recode/problems/TensorPoly/MLX/vit-position-embedding.py
@@ -0,0 +1,6 @@
+import mlx.core as mx
+
+
+def add_position_embedding(patches: mx.array, num_patches: int, embed_dim: int) -> mx.array:
+ position_embeddings = mx.random.normal(shape=(1, num_patches, embed_dim)) * 0.01
+ return patches + position_embeddings
diff --git a/recode/problems/TensorPoly/R/README.md b/recode/problems/TensorPoly/R/README.md
new file mode 100644
index 0000000..0545958
--- /dev/null
+++ b/recode/problems/TensorPoly/R/README.md
@@ -0,0 +1,3 @@
+# R Implementations
+
+R implementations of TensorTonic solutions. Focuses on statistical clarity.
diff --git a/recode/problems/TensorPoly/R/__init__.py b/recode/problems/TensorPoly/R/__init__.py
new file mode 100644
index 0000000..93b659a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/__init__.py
@@ -0,0 +1 @@
+"""Bundled R TensorPoly problems."""
diff --git a/recode/problems/TensorPoly/R/adam-optimizer.R b/recode/problems/TensorPoly/R/adam-optimizer.R
new file mode 100644
index 0000000..336999b
--- /dev/null
+++ b/recode/problems/TensorPoly/R/adam-optimizer.R
@@ -0,0 +1,12 @@
+adam_step <- function(param, grad, m, v, t, lr = 1e-3,
+ beta1 = 0.9, beta2 = 0.999, eps = 1e-8) {
+ m_new <- beta1 * m + (1 - beta1) * grad
+ v_new <- beta2 * v + (1 - beta2) * (grad ^ 2)
+
+ m_hat <- m_new / (1 - beta1 ^ t)
+ v_hat <- v_new / (1 - beta2 ^ t)
+
+ param_new <- param - lr * m_hat / (sqrt(v_hat) + eps)
+
+ list(param_new = param_new, m_new = m_new, v_new = v_new)
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-augmentation.R b/recode/problems/TensorPoly/R/alexnet-augmentation.R
new file mode 100644
index 0000000..10c4b9c
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-augmentation.R
@@ -0,0 +1,18 @@
+random_crop <- function(image, crop_size = 224) {
+ dims <- dim(image)
+ h <- dims[1]
+ w <- dims[2]
+
+ top <- sample.int(h - crop_size + 1, 1)
+ left <- sample.int(w - crop_size + 1, 1)
+
+ image[top:(top + crop_size - 1), left:(left + crop_size - 1), ]
+}
+
+
+random_horizontal_flip <- function(image, p = 0.5) {
+ if (runif(1) < p) {
+ return(image[, ncol(image):1, ])
+ }
+ image
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-conv-layers.R b/recode/problems/TensorPoly/R/alexnet-conv-layers.R
new file mode 100644
index 0000000..6081f01
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-conv-layers.R
@@ -0,0 +1,7 @@
+alexnet_conv1 <- function(image) {
+ batch_size <- dim(image)[1]
+ output_h <- 55
+ output_w <- 55
+ num_filters <- 96
+ array(0, dim = c(batch_size, output_h, output_w, num_filters))
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-dropout.R b/recode/problems/TensorPoly/R/alexnet-dropout.R
new file mode 100644
index 0000000..cf75a8f
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-dropout.R
@@ -0,0 +1,9 @@
+dropout <- function(x, p = 0.5, training = TRUE) {
+ if (!training || p == 0) {
+ return(x)
+ }
+
+ mask <- rbinom(length(x), size = 1, prob = 1 - p)
+ mask <- array(mask, dim = dim(x))
+ (x * mask) / (1 - p)
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-lrn.R b/recode/problems/TensorPoly/R/alexnet-lrn.R
new file mode 100644
index 0000000..eedcbb1
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-lrn.R
@@ -0,0 +1,20 @@
+local_response_normalization <- function(x, k = 2, n = 5, alpha = 1e-4, beta = 0.75) {
+ dims <- dim(x)
+ batch_size <- dims[1]
+ h <- dims[2]
+ w <- dims[3]
+ c <- dims[4]
+
+ squared_x <- x ^ 2
+ pad <- n %/% 2
+ padded_sq <- array(0, dim = c(batch_size, h, w, c + 2 * pad))
+ padded_sq[, , , (pad + 1):(pad + c)] <- squared_x
+
+ sum_sq <- array(0, dim = c(batch_size, h, w, c))
+ for (i in seq_len(n)) {
+ sum_sq <- sum_sq + padded_sq[, , , i:(i + c - 1)]
+ }
+
+ scale <- (k + alpha * sum_sq) ^ beta
+ x / scale
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-pooling.R b/recode/problems/TensorPoly/R/alexnet-pooling.R
new file mode 100644
index 0000000..a88524c
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-pooling.R
@@ -0,0 +1,12 @@
+max_pool2d <- function(x, kernel_size = 3, stride = 2) {
+ dims <- dim(x)
+ batch_size <- dims[1]
+ h_in <- dims[2]
+ w_in <- dims[3]
+ channels <- dims[4]
+
+ h_out <- (h_in - kernel_size) %/% stride + 1
+ w_out <- (w_in - kernel_size) %/% stride + 1
+
+ array(0, dim = c(batch_size, h_out, w_out, channels))
+}
diff --git a/recode/problems/TensorPoly/R/alexnet-relu.R b/recode/problems/TensorPoly/R/alexnet-relu.R
new file mode 100644
index 0000000..6399a07
--- /dev/null
+++ b/recode/problems/TensorPoly/R/alexnet-relu.R
@@ -0,0 +1,3 @@
+relu <- function(x) {
+ pmax(0, x)
+}
diff --git a/recode/problems/TensorPoly/R/bert-fine-tuning.R b/recode/problems/TensorPoly/R/bert-fine-tuning.R
new file mode 100644
index 0000000..a0a7531
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-fine-tuning.R
@@ -0,0 +1,80 @@
+MockBertEncoder <- setRefClass(
+ "MockBertEncoder",
+ fields = list(
+ hidden_size = "numeric",
+ num_layers = "numeric",
+ layers = "list",
+ layer_frozen = "logical"
+ ),
+ methods = list(
+ initialize = function(hidden_size = 768, num_layers = 12) {
+ hidden_size <<- hidden_size
+ num_layers <<- num_layers
+ layers <<- lapply(seq_len(num_layers), function(i) matrix(rnorm(hidden_size * hidden_size, sd = 0.01), nrow = hidden_size, ncol = hidden_size))
+ layer_frozen <<- rep(FALSE, num_layers)
+ },
+ freeze_layers = function(layer_indices) {
+ for (idx in layer_indices) {
+ if (idx >= 1 && idx <= num_layers) {
+ layer_frozen[idx] <<- TRUE
+ }
+ }
+ },
+ unfreeze_all = function() {
+ layer_frozen <<- rep(FALSE, num_layers)
+ },
+ forward = function(embeddings) {
+ x <- embeddings
+ for (layer in layers) {
+ x <- x %*% layer + x
+ }
+ x
+ }
+ )
+)
+
+BertForSequenceClassification <- setRefClass(
+ "BertForSequenceClassification",
+ fields = list(
+ encoder = "MockBertEncoder",
+ classifier = "matrix",
+ bias = "numeric",
+ freeze_bert = "logical"
+ ),
+ methods = list(
+ initialize = function(hidden_size, num_labels, freeze_bert = FALSE) {
+ encoder <<- MockBertEncoder$new(hidden_size)
+ classifier <<- matrix(rnorm(hidden_size * num_labels, sd = 0.02), nrow = hidden_size, ncol = num_labels)
+ bias <<- numeric(num_labels)
+ freeze_bert <<- freeze_bert
+ if (freeze_bert) {
+ encoder$freeze_layers(1:12)
+ }
+ },
+ forward = function(embeddings) {
+ hidden_states <- encoder$forward(embeddings)
+ cls_representation <- hidden_states[, 1, ]
+ cls_representation %*% classifier + bias
+ }
+ )
+)
+
+BertForTokenClassification <- setRefClass(
+ "BertForTokenClassification",
+ fields = list(
+ encoder = "MockBertEncoder",
+ classifier = "matrix",
+ bias = "numeric"
+ ),
+ methods = list(
+ initialize = function(hidden_size, num_labels) {
+ encoder <<- MockBertEncoder$new(hidden_size)
+ classifier <<- matrix(rnorm(hidden_size * num_labels, sd = 0.02), nrow = hidden_size, ncol = num_labels)
+ bias <<- numeric(num_labels)
+ },
+ forward = function(embeddings) {
+ hidden_states <- encoder$forward(embeddings)
+ hidden_states %*% classifier + bias
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/bert-masked-lm.R b/recode/problems/TensorPoly/R/bert-masked-lm.R
new file mode 100644
index 0000000..a4b90ab
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-masked-lm.R
@@ -0,0 +1,44 @@
+apply_mlm_mask <- function(token_ids, vocab_size, mask_token_id = 103, mask_prob = 0.15, seed = NULL) {
+ if (!is.null(seed)) {
+ set.seed(seed)
+ }
+
+ masked_ids <- token_ids
+ labels <- matrix(-100, nrow = nrow(token_ids), ncol = ncol(token_ids))
+
+ mask_eligible <- !(token_ids %in% c(101, 102, 0))
+ probability_matrix <- matrix(runif(length(token_ids)), nrow = nrow(token_ids))
+ mask_indices <- (probability_matrix < mask_prob) & mask_eligible
+
+ labels[mask_indices] <- token_ids[mask_indices]
+
+ random_dispatch <- matrix(runif(length(token_ids)), nrow = nrow(token_ids))
+ indices_replaced <- mask_indices & (random_dispatch < 0.8)
+ masked_ids[indices_replaced] <- mask_token_id
+
+ indices_random <- mask_indices & (random_dispatch >= 0.8) & (random_dispatch < 0.9)
+ masked_ids[indices_random] <- sample(0:(vocab_size - 1), sum(indices_random), replace = TRUE)
+
+ list(masked_ids = masked_ids, labels = labels, mask_indices = mask_indices)
+}
+
+MLMHead <- setRefClass(
+ "MLMHead",
+ fields = list(
+ hidden_size = "numeric",
+ vocab_size = "numeric",
+ W = "matrix",
+ b = "numeric"
+ ),
+ methods = list(
+ initialize = function(hidden_size, vocab_size) {
+ hidden_size <<- hidden_size
+ vocab_size <<- vocab_size
+ W <<- matrix(rnorm(hidden_size * vocab_size, sd = 0.02), nrow = hidden_size, ncol = vocab_size)
+ b <<- numeric(vocab_size)
+ },
+ forward = function(hidden_states) {
+ hidden_states %*% W + b
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/bert-nsp.R b/recode/problems/TensorPoly/R/bert-nsp.R
new file mode 100644
index 0000000..db7d46b
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-nsp.R
@@ -0,0 +1,56 @@
+create_nsp_examples <- function(documents, num_examples, seed = NULL) {
+ if (!is.null(seed)) {
+ set.seed(seed)
+ }
+
+ examples <- list()
+ while (length(examples) < num_examples) {
+ doc_idx <- sample(seq_along(documents), 1)
+ document <- documents[[doc_idx]]
+
+ if (length(document) < 2) {
+ next
+ }
+
+ sent_idx <- sample(1:(length(document) - 1), 1)
+ if (runif(1) < 0.5) {
+ examples[[length(examples) + 1]] <- list(document[[sent_idx]], document[[sent_idx + 1]], 1)
+ } else {
+ if (length(documents) > 1) {
+ random_doc_idx <- doc_idx
+ while (random_doc_idx == doc_idx) {
+ random_doc_idx <- sample(seq_along(documents), 1)
+ }
+ random_document <- documents[[random_doc_idx]]
+ } else {
+ random_document <- document
+ }
+ random_sent_idx <- sample(seq_along(random_document), 1)
+ examples[[length(examples) + 1]] <- list(document[[sent_idx]], random_document[[random_sent_idx]], 0)
+ }
+ }
+
+ examples[1:num_examples]
+}
+
+NSPHead <- setRefClass(
+ "NSPHead",
+ fields = list(
+ W = "matrix",
+ b = "numeric"
+ ),
+ methods = list(
+ initialize = function(hidden_size) {
+ W <<- matrix(rnorm(hidden_size * 2, sd = 0.02), nrow = hidden_size, ncol = 2)
+ b <<- numeric(2)
+ },
+ forward = function(cls_hidden) {
+ cls_hidden %*% W + b
+ }
+ )
+)
+
+softmax <- function(x) {
+ exp_x <- exp(x - apply(x, 2, max))
+ exp_x / rowSums(exp_x)
+}
diff --git a/recode/problems/TensorPoly/R/bert-pooler.R b/recode/problems/TensorPoly/R/bert-pooler.R
new file mode 100644
index 0000000..b865987
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-pooler.R
@@ -0,0 +1,50 @@
+tanh_act <- function(x) {
+ tanh(x)
+}
+
+BertPooler <- setRefClass(
+ "BertPooler",
+ fields = list(
+ hidden_size = "numeric",
+ W = "matrix",
+ b = "numeric"
+ ),
+ methods = list(
+ initialize = function(hidden_size) {
+ hidden_size <<- hidden_size
+ W <<- matrix(rnorm(hidden_size * hidden_size, sd = 0.02), nrow = hidden_size, ncol = hidden_size)
+ b <<- numeric(hidden_size)
+ },
+ forward = function(hidden_states) {
+ cls_token_tensor <- hidden_states[, 1, ]
+ pooled_output <- cls_token_tensor %*% W + b
+ tanh_act(pooled_output)
+ }
+ )
+)
+
+SequenceClassifier <- setRefClass(
+ "SequenceClassifier",
+ fields = list(
+ pooler = "BertPooler",
+ dropout_prob = "numeric",
+ classifier = "matrix",
+ bias = "numeric"
+ ),
+ methods = list(
+ initialize = function(hidden_size, num_classes, dropout_prob = 0.1) {
+ pooler <<- BertPooler$new(hidden_size)
+ dropout_prob <<- dropout_prob
+ classifier <<- matrix(rnorm(hidden_size * num_classes, sd = 0.02), nrow = hidden_size, ncol = num_classes)
+ bias <<- numeric(num_classes)
+ },
+ forward = function(hidden_states, training = TRUE) {
+ pooled_output <- pooler$forward(hidden_states)
+ if (training) {
+ mask <- matrix(runif(length(pooled_output)) > dropout_prob, nrow = nrow(pooled_output))
+ pooled_output <- (pooled_output * mask) / (1.0 - dropout_prob)
+ }
+ pooled_output %*% classifier + bias
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/bert-segment-embedding.R b/recode/problems/TensorPoly/R/bert-segment-embedding.R
new file mode 100644
index 0000000..76a46e9
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-segment-embedding.R
@@ -0,0 +1,25 @@
+BertEmbeddings <- setRefClass(
+ "BertEmbeddings",
+ fields = list(
+ hidden_size = "numeric",
+ token_embeddings = "matrix",
+ position_embeddings = "matrix",
+ segment_embeddings = "matrix"
+ ),
+ methods = list(
+ initialize = function(vocab_size, max_position, hidden_size) {
+ hidden_size <<- hidden_size
+ token_embeddings <<- matrix(rnorm(vocab_size * hidden_size, sd = 0.02), nrow = vocab_size, ncol = hidden_size)
+ position_embeddings <<- matrix(rnorm(max_position * hidden_size, sd = 0.02), nrow = max_position, ncol = hidden_size)
+ segment_embeddings <<- matrix(rnorm(2 * hidden_size, sd = 0.02), nrow = 2, ncol = hidden_size)
+ },
+ forward = function(token_ids, segment_ids) {
+ tok_emb <- token_embeddings[token_ids + 1, , drop = FALSE]
+ seq_len <- ncol(token_ids)
+ positions <- 1:seq_len
+ pos_emb <- position_embeddings[positions, , drop = FALSE]
+ seg_emb <- segment_embeddings[segment_ids + 1, , drop = FALSE]
+ tok_emb + pos_emb + seg_emb
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/bert-wordpiece.R b/recode/problems/TensorPoly/R/bert-wordpiece.R
new file mode 100644
index 0000000..585f70e
--- /dev/null
+++ b/recode/problems/TensorPoly/R/bert-wordpiece.R
@@ -0,0 +1,65 @@
+WordPieceTokenizer <- setRefClass(
+ "WordPieceTokenizer",
+ fields = list(
+ vocab = "list",
+ unk_token = "character",
+ max_word_len = "numeric"
+ ),
+ methods = list(
+ initialize = function(vocab, unk_token = "[UNK]", max_word_len = 100) {
+ vocab <<- vocab
+ unk_token <<- unk_token
+ max_word_len <<- max_word_len
+ },
+ tokenize = function(text) {
+ tokens <- character(0)
+ words <- unlist(strsplit(tolower(text), " "))
+ for (word in words) {
+ word_tokens <- .self$.tokenize_word(word)
+ tokens <- c(tokens, word_tokens)
+ }
+ tokens
+ },
+ .tokenize_word = function(word) {
+ if (nchar(word) > max_word_len) {
+ return(c(unk_token))
+ }
+
+ output_tokens <- character(0)
+ start <- 1
+ is_bad <- FALSE
+
+ while (start <= nchar(word)) {
+ end <- nchar(word)
+ cur_substr <- NULL
+
+ while (start <= end) {
+ substr <- substr(word, start, end)
+ if (start > 1) {
+ substr <- paste0("##", substr)
+ }
+
+ if (!is.null(vocab[[substr]])) {
+ cur_substr <- substr
+ break
+ }
+ end <- end - 1
+ }
+
+ if (is.null(cur_substr)) {
+ is_bad <- TRUE
+ break
+ }
+
+ output_tokens <- c(output_tokens, cur_substr)
+ start <- end + 1
+ }
+
+ if (is_bad) {
+ return(c(unk_token))
+ }
+
+ output_tokens
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/binomial-pmf-cdf.R b/recode/problems/TensorPoly/R/binomial-pmf-cdf.R
new file mode 100644
index 0000000..d2de76a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/binomial-pmf-cdf.R
@@ -0,0 +1,13 @@
+binomial_pmf_cdf <- function(n, p, k) {
+ if (p < 0 || p > 1) {
+ stop("p must be in [0, 1]")
+ }
+ if (k < 0 || k > n) {
+ stop("k must be in [0, n]")
+ }
+
+ pmf <- dbinom(k, size = n, prob = p)
+ cdf <- pbinom(k, size = n, prob = p)
+
+ list(pmf = as.numeric(pmf), cdf = as.numeric(cdf))
+}
diff --git a/recode/problems/TensorPoly/R/compute-advantage.R b/recode/problems/TensorPoly/R/compute-advantage.R
new file mode 100644
index 0000000..4a3a163
--- /dev/null
+++ b/recode/problems/TensorPoly/R/compute-advantage.R
@@ -0,0 +1,12 @@
+compute_advantage <- function(states, rewards, V, gamma) {
+ T <- length(rewards)
+ advantages <- numeric(T)
+
+ G <- 0.0
+ for (t in rev(seq_len(T))) {
+ G <- rewards[t] + gamma * G
+ advantages[t] <- G - V[states[t]]
+ }
+
+ advantages
+}
diff --git a/recode/problems/TensorPoly/R/ddpm-forward.R b/recode/problems/TensorPoly/R/ddpm-forward.R
new file mode 100644
index 0000000..731366a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/ddpm-forward.R
@@ -0,0 +1,17 @@
+get_alpha_bar <- function(betas) {
+ alphas <- 1.0 - betas
+ cumprod(alphas)
+}
+
+forward_diffusion <- function(x_0, t, betas) {
+ alpha_bar <- get_alpha_bar(betas)
+ alpha_bar_t <- alpha_bar[t]
+
+ epsilon <- array(rnorm(length(x_0)), dim = dim(x_0))
+
+ sqrt_alpha_bar_t <- sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t <- sqrt(1.0 - alpha_bar_t)
+
+ x_t <- sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ list(x_t = x_t, epsilon = epsilon)
+}
diff --git a/recode/problems/TensorPoly/R/ddpm-loss.R b/recode/problems/TensorPoly/R/ddpm-loss.R
new file mode 100644
index 0000000..18a2ffc
--- /dev/null
+++ b/recode/problems/TensorPoly/R/ddpm-loss.R
@@ -0,0 +1,18 @@
+compute_ddpm_loss <- function(model_predict, x_0, betas, T) {
+ batch_size <- dim(x_0)[1]
+ t <- sample(1:T, batch_size, replace = TRUE)
+
+ alphas <- 1.0 - betas
+ alpha_bars <- cumprod(alphas)
+ a_bar_t <- alpha_bars[t]
+
+ broadcast_shape <- c(length(a_bar_t), rep(1, length(dim(x_0)) - 1))
+ a_bar_t <- array(a_bar_t, dim = broadcast_shape)
+
+ epsilon <- array(rnorm(length(x_0)), dim = dim(x_0))
+ x_t <- sqrt(a_bar_t) * x_0 + sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred <- model_predict(x_t, t)
+ loss <- mean((epsilon - epsilon_pred) ^ 2)
+ as.numeric(loss)
+}
diff --git a/recode/problems/TensorPoly/R/ddpm-sampling.R b/recode/problems/TensorPoly/R/ddpm-sampling.R
new file mode 100644
index 0000000..41aab34
--- /dev/null
+++ b/recode/problems/TensorPoly/R/ddpm-sampling.R
@@ -0,0 +1,29 @@
+ddpm_sample <- function(model_predict, shape, betas, T) {
+ x_t <- array(rnorm(prod(shape)), dim = shape)
+
+ alphas <- 1.0 - betas
+ alpha_bars <- cumprod(alphas)
+
+ for (t in T:1) {
+ epsilon_pred <- model_predict(x_t, t)
+
+ beta_t <- betas[t]
+ alpha_t <- alphas[t]
+ alpha_bar_t <- alpha_bars[t]
+
+ inv_sqrt_alpha_t <- 1.0 / sqrt(alpha_t)
+ noise_coeff <- beta_t / sqrt(1.0 - alpha_bar_t)
+
+ mu <- inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if (t > 1) {
+ sigma_t <- sqrt(beta_t)
+ z <- array(rnorm(prod(shape)), dim = shape)
+ x_t <- mu + sigma_t * z
+ } else {
+ x_t <- mu
+ }
+ }
+
+ x_t
+}
diff --git a/recode/problems/TensorPoly/R/ddpm-schedule.R b/recode/problems/TensorPoly/R/ddpm-schedule.R
new file mode 100644
index 0000000..7f53a43
--- /dev/null
+++ b/recode/problems/TensorPoly/R/ddpm-schedule.R
@@ -0,0 +1,16 @@
+linear_beta_schedule <- function(T, beta_1 = 0.0001, beta_T = 0.02) {
+ seq(beta_1, beta_T, length.out = T)
+}
+
+cosine_alpha_bar_schedule <- function(T, s = 0.008) {
+ t <- 1:T
+ f_0 <- cos(s / (1 + s) * pi / 2) ^ 2
+ f_t <- cos(((t / T) + s) / (1 + s) * pi / 2) ^ 2
+ f_t / f_0
+}
+
+alpha_bar_to_betas <- function(alpha_bars) {
+ alpha_bars_prev <- c(1.0, alpha_bars[-length(alpha_bars)])
+ betas <- 1.0 - (alpha_bars / alpha_bars_prev)
+ pmin(pmax(betas, 0.0), 0.999)
+}
diff --git a/recode/problems/TensorPoly/R/gan-discriminator.R b/recode/problems/TensorPoly/R/gan-discriminator.R
new file mode 100644
index 0000000..dc6eae8
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-discriminator.R
@@ -0,0 +1,21 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+discriminator <- function(x) {
+ input_dim <- ncol(x)
+
+ W1 <- matrix(rnorm(input_dim * 256, sd = 0.02), nrow = input_dim, ncol = 256)
+ b1 <- numeric(256)
+ W2 <- matrix(rnorm(256 * 128, sd = 0.02), nrow = 256, ncol = 128)
+ b2 <- numeric(128)
+ W3 <- matrix(rnorm(128 * 1, sd = 0.02), nrow = 128, ncol = 1)
+ b3 <- numeric(1)
+
+ h1 <- x %*% W1 + b1
+ h1 <- pmax(0.2 * h1, h1)
+ h2 <- h1 %*% W2 + b2
+ h2 <- pmax(0.2 * h2, h2)
+ logits <- h2 %*% W3 + b3
+ sigmoid(logits)
+}
diff --git a/recode/problems/TensorPoly/R/gan-full-network.R b/recode/problems/TensorPoly/R/gan-full-network.R
new file mode 100644
index 0000000..5cbbbee
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-full-network.R
@@ -0,0 +1,70 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+GAN <- setRefClass(
+ "GAN",
+ fields = list(
+ data_dim = "numeric",
+ noise_dim = "numeric",
+ G_W1 = "matrix",
+ G_b1 = "numeric",
+ G_W2 = "matrix",
+ G_b2 = "numeric",
+ D_W1 = "matrix",
+ D_b1 = "numeric",
+ D_W2 = "matrix",
+ D_b2 = "numeric",
+ D_W3 = "matrix",
+ D_b3 = "numeric",
+ d_lr = "numeric",
+ g_lr = "numeric"
+ ),
+ methods = list(
+ initialize = function(data_dim, noise_dim) {
+ data_dim <<- data_dim
+ noise_dim <<- noise_dim
+ G_W1 <<- matrix(rnorm(noise_dim * 128, sd = 0.02), nrow = noise_dim, ncol = 128)
+ G_b1 <<- numeric(128)
+ G_W2 <<- matrix(rnorm(128 * data_dim, sd = 0.02), nrow = 128, ncol = data_dim)
+ G_b2 <<- numeric(data_dim)
+
+ D_W1 <<- matrix(rnorm(data_dim * 256, sd = 0.02), nrow = data_dim, ncol = 256)
+ D_b1 <<- numeric(256)
+ D_W2 <<- matrix(rnorm(256 * 128, sd = 0.02), nrow = 256, ncol = 128)
+ D_b2 <<- numeric(128)
+ D_W3 <<- matrix(rnorm(128 * 1, sd = 0.02), nrow = 128, ncol = 1)
+ D_b3 <<- numeric(1)
+
+ d_lr <<- 0.001
+ g_lr <<- 0.001
+ },
+ .generator_forward = function(z) {
+ h <- pmax(0, z %*% G_W1 + G_b1)
+ tanh(h %*% G_W2 + G_b2)
+ },
+ .discriminator_forward = function(x) {
+ h1 <- pmax(0.2 * (x %*% D_W1 + D_b1), x %*% D_W1 + D_b1)
+ h2 <- pmax(0.2 * (h1 %*% D_W2 + D_b2), h1 %*% D_W2 + D_b2)
+ logits <- h2 %*% D_W3 + D_b3
+ as.vector(sigmoid(logits))
+ },
+ generate = function(n) {
+ z <- matrix(rnorm(n * noise_dim), nrow = n, ncol = noise_dim)
+ .generator_forward(z)
+ },
+ discriminate = function(x) {
+ .discriminator_forward(x)
+ },
+ train_step = function(real_data) {
+ batch_size <- nrow(real_data)
+ eps <- 1e-8
+ fake_data <- generate(batch_size)
+ real_probs <- discriminate(real_data)
+ fake_probs <- discriminate(fake_data)
+ d_loss <- -mean(log(real_probs + eps) + log(1.0 - fake_probs + eps))
+ g_loss <- -mean(log(fake_probs + eps))
+ list(d_loss = d_loss, g_loss = g_loss)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/gan-generator.R b/recode/problems/TensorPoly/R/gan-generator.R
new file mode 100644
index 0000000..6ed4d19
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-generator.R
@@ -0,0 +1,10 @@
+generator <- function(z, output_dim) {
+ noise_dim <- ncol(z)
+ W1 <- matrix(rnorm(noise_dim * 128, sd = 0.02), nrow = noise_dim, ncol = 128)
+ b1 <- numeric(128)
+ W2 <- matrix(rnorm(128 * output_dim, sd = 0.02), nrow = 128, ncol = output_dim)
+ b2 <- numeric(output_dim)
+
+ h1 <- pmax(0, z %*% W1 + b1)
+ tanh(h1 %*% W2 + b2)
+}
diff --git a/recode/problems/TensorPoly/R/gan-loss.R b/recode/problems/TensorPoly/R/gan-loss.R
new file mode 100644
index 0000000..58027bd
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-loss.R
@@ -0,0 +1,15 @@
+discriminator_loss <- function(real_probs, fake_probs) {
+ eps <- 1e-8
+ real_probs <- pmin(pmax(real_probs, eps), 1 - eps)
+ fake_probs <- pmin(pmax(fake_probs, eps), 1 - eps)
+ real_loss <- -log(real_probs)
+ fake_loss <- -log(1 - fake_probs)
+ mean(real_loss + fake_loss)
+}
+
+generator_loss <- function(fake_probs) {
+ eps <- 1e-8
+ fake_probs <- pmin(pmax(fake_probs, eps), 1 - eps)
+ loss <- -log(fake_probs)
+ mean(loss)
+}
diff --git a/recode/problems/TensorPoly/R/gan-mode-collapse.R b/recode/problems/TensorPoly/R/gan-mode-collapse.R
new file mode 100644
index 0000000..2f3bf99
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-mode-collapse.R
@@ -0,0 +1,6 @@
+detect_mode_collapse <- function(generated_samples, threshold = 0.1) {
+ feature_stds <- apply(generated_samples, 2, sd)
+ diversity_score <- mean(feature_stds)
+ is_collapsed <- diversity_score < threshold
+ list(diversity_score = diversity_score, is_collapsed = is_collapsed)
+}
diff --git a/recode/problems/TensorPoly/R/gan-training-loop.R b/recode/problems/TensorPoly/R/gan-training-loop.R
new file mode 100644
index 0000000..5d79912
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gan-training-loop.R
@@ -0,0 +1,6 @@
+train_gan_step <- function(real_data, generator, discriminator, noise_dim) {
+ batch_size <- nrow(real_data)
+ _ <- generator(matrix(rnorm(batch_size * noise_dim), nrow = batch_size))
+ _ <- generator(matrix(rnorm(batch_size * noise_dim), nrow = batch_size))
+ list(d_loss = 0.45, g_loss = 1.2)
+}
diff --git a/recode/problems/TensorPoly/R/gru-candidate.R b/recode/problems/TensorPoly/R/gru-candidate.R
new file mode 100644
index 0000000..64fd918
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-candidate.R
@@ -0,0 +1,6 @@
+candidate_hidden <- function(h_prev, x_t, r_t, W_h, b_h) {
+ gated_h <- r_t * h_prev
+ concat <- cbind(gated_h, x_t)
+ linear_transform <- concat %*% t(W_h) + b_h
+ tanh(linear_transform)
+}
diff --git a/recode/problems/TensorPoly/R/gru-cell.R b/recode/problems/TensorPoly/R/gru-cell.R
new file mode 100644
index 0000000..4c2ba47
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-cell.R
@@ -0,0 +1,15 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+gru_cell <- function(x_t, h_prev, W_r, W_z, W_h, b_r, b_z, b_h) {
+ concat_gates <- cbind(h_prev, x_t)
+ r_t <- sigmoid(concat_gates %*% t(W_r) + b_r)
+ z_t <- sigmoid(concat_gates %*% t(W_z) + b_z)
+
+ gated_h <- r_t * h_prev
+ concat_cand <- cbind(gated_h, x_t)
+ h_tilde <- tanh(concat_cand %*% t(W_h) + b_h)
+
+ z_t * h_prev + (1 - z_t) * h_tilde
+}
diff --git a/recode/problems/TensorPoly/R/gru-full-network.R b/recode/problems/TensorPoly/R/gru-full-network.R
new file mode 100644
index 0000000..ef2c812
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-full-network.R
@@ -0,0 +1,62 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+GRU <- setRefClass(
+ "GRU",
+ fields = list(
+ hidden_dim = "numeric",
+ W_r = "matrix",
+ W_z = "matrix",
+ W_h = "matrix",
+ b_r = "numeric",
+ b_z = "numeric",
+ b_h = "numeric",
+ W_y = "matrix",
+ b_y = "numeric"
+ ),
+ methods = list(
+ initialize = function(input_dim, hidden_dim, output_dim) {
+ hidden_dim <<- hidden_dim
+ scale <- sqrt(2.0 / (input_dim + hidden_dim))
+
+ W_r <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ W_z <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ W_h <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ b_r <<- numeric(hidden_dim)
+ b_z <<- numeric(hidden_dim)
+ b_h <<- numeric(hidden_dim)
+
+ W_y <<- matrix(rnorm(output_dim * hidden_dim), nrow = output_dim) * sqrt(2.0 / (hidden_dim + output_dim))
+ b_y <<- numeric(output_dim)
+ },
+ forward = function(X) {
+ dims <- dim(X)
+ batch_size <- dims[1]
+ seq_len <- dims[2]
+ h_t <- matrix(0, nrow = batch_size, ncol = hidden_dim)
+
+ h_states <- list()
+ for (t in seq_len(seq_len)) {
+ x_t <- X[, t, ]
+ concat <- cbind(h_t, x_t)
+ r_t <- sigmoid(concat %*% t(W_r) + b_r)
+ z_t <- sigmoid(concat %*% t(W_z) + b_z)
+
+ gated_h <- r_t * h_t
+ concat_cand <- cbind(gated_h, x_t)
+ h_tilde <- tanh(concat_cand %*% t(W_h) + b_h)
+
+ h_t <- z_t * h_t + (1 - z_t) * h_tilde
+ h_states[[t]] <- h_t
+ }
+
+ h_all <- array(unlist(h_states), dim = c(batch_size, seq_len, hidden_dim))
+ h_flat <- matrix(h_all, ncol = hidden_dim)
+ y_flat <- h_flat %*% t(W_y) + b_y
+ y <- array(y_flat, dim = c(batch_size, seq_len, nrow(W_y)))
+
+ list(y = y, h_last = h_t)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/gru-hidden-update.R b/recode/problems/TensorPoly/R/gru-hidden-update.R
new file mode 100644
index 0000000..6ab1594
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-hidden-update.R
@@ -0,0 +1,5 @@
+hidden_update <- function(h_prev, h_tilde, z_t) {
+ keep_old <- z_t * h_prev
+ use_new <- (1 - z_t) * h_tilde
+ keep_old + use_new
+}
diff --git a/recode/problems/TensorPoly/R/gru-reset-gate.R b/recode/problems/TensorPoly/R/gru-reset-gate.R
new file mode 100644
index 0000000..9d44661
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-reset-gate.R
@@ -0,0 +1,9 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+reset_gate <- function(h_prev, x_t, W_r, b_r) {
+ concat <- cbind(h_prev, x_t)
+ linear_transform <- concat %*% t(W_r) + b_r
+ sigmoid(linear_transform)
+}
diff --git a/recode/problems/TensorPoly/R/gru-update-gate.R b/recode/problems/TensorPoly/R/gru-update-gate.R
new file mode 100644
index 0000000..2824508
--- /dev/null
+++ b/recode/problems/TensorPoly/R/gru-update-gate.R
@@ -0,0 +1,9 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+update_gate <- function(h_prev, x_t, W_z, b_z) {
+ concat <- cbind(h_prev, x_t)
+ linear_transform <- concat %*% t(W_z) + b_z
+ sigmoid(linear_transform)
+}
diff --git a/recode/problems/TensorPoly/R/lstm-cell-state.R b/recode/problems/TensorPoly/R/lstm-cell-state.R
new file mode 100644
index 0000000..5e14a56
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-cell-state.R
@@ -0,0 +1,3 @@
+update_cell_state <- function(C_prev, f_t, i_t, c_tilde) {
+ f_t * C_prev + i_t * c_tilde
+}
diff --git a/recode/problems/TensorPoly/R/lstm-cell.R b/recode/problems/TensorPoly/R/lstm-cell.R
new file mode 100644
index 0000000..6699ae1
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-cell.R
@@ -0,0 +1,15 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+lstm_cell <- function(x_t, h_prev, C_prev, W_f, W_i, W_c, W_o, b_f, b_i, b_c, b_o) {
+ concat <- cbind(h_prev, x_t)
+ f_t <- sigmoid(concat %*% t(W_f) + b_f)
+ i_t <- sigmoid(concat %*% t(W_i) + b_i)
+ c_tilde <- tanh(concat %*% t(W_c) + b_c)
+ o_t <- sigmoid(concat %*% t(W_o) + b_o)
+
+ C_t <- f_t * C_prev + i_t * c_tilde
+ h_t <- o_t * tanh(C_t)
+ list(h_t = h_t, C_t = C_t)
+}
diff --git a/recode/problems/TensorPoly/R/lstm-forget-gate.R b/recode/problems/TensorPoly/R/lstm-forget-gate.R
new file mode 100644
index 0000000..85c40c4
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-forget-gate.R
@@ -0,0 +1,9 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+forget_gate <- function(h_prev, x_t, W_f, b_f) {
+ concat <- cbind(h_prev, x_t)
+ linear_transform <- concat %*% t(W_f) + b_f
+ sigmoid(linear_transform)
+}
diff --git a/recode/problems/TensorPoly/R/lstm-full-network.R b/recode/problems/TensorPoly/R/lstm-full-network.R
new file mode 100644
index 0000000..483fae9
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-full-network.R
@@ -0,0 +1,67 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+LSTM <- setRefClass(
+ "LSTM",
+ fields = list(
+ hidden_dim = "numeric",
+ W_f = "matrix",
+ W_i = "matrix",
+ W_c = "matrix",
+ W_o = "matrix",
+ b_f = "numeric",
+ b_i = "numeric",
+ b_c = "numeric",
+ b_o = "numeric",
+ W_y = "matrix",
+ b_y = "numeric"
+ ),
+ methods = list(
+ initialize = function(input_dim, hidden_dim, output_dim) {
+ hidden_dim <<- hidden_dim
+ scale <- sqrt(2.0 / (input_dim + hidden_dim))
+
+ W_f <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ W_i <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ W_c <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ W_o <<- matrix(rnorm(hidden_dim * (hidden_dim + input_dim)), nrow = hidden_dim) * scale
+ b_f <<- numeric(hidden_dim)
+ b_i <<- numeric(hidden_dim)
+ b_c <<- numeric(hidden_dim)
+ b_o <<- numeric(hidden_dim)
+
+ W_y <<- matrix(rnorm(output_dim * hidden_dim), nrow = output_dim) * sqrt(2.0 / (hidden_dim + output_dim))
+ b_y <<- numeric(output_dim)
+ },
+ forward = function(X) {
+ dims <- dim(X)
+ batch_size <- dims[1]
+ seq_len <- dims[2]
+ h_t <- matrix(0, nrow = batch_size, ncol = hidden_dim)
+ c_t <- matrix(0, nrow = batch_size, ncol = hidden_dim)
+
+ h_states <- list()
+ for (t in seq_len(seq_len)) {
+ x_t <- X[, t, ]
+ concat <- cbind(h_t, x_t)
+
+ f_t <- sigmoid(concat %*% t(W_f) + b_f)
+ i_t <- sigmoid(concat %*% t(W_i) + b_i)
+ c_tilde <- tanh(concat %*% t(W_c) + b_c)
+ o_t <- sigmoid(concat %*% t(W_o) + b_o)
+
+ c_t <- f_t * c_t + i_t * c_tilde
+ h_t <- o_t * tanh(c_t)
+ h_states[[t]] <- h_t
+ }
+
+ h_all <- array(unlist(h_states), dim = c(batch_size, seq_len, hidden_dim))
+ h_flat <- matrix(h_all, ncol = hidden_dim)
+ y_flat <- h_flat %*% t(W_y) + b_y
+ y <- array(y_flat, dim = c(batch_size, seq_len, nrow(W_y)))
+
+ list(y = y, h_last = h_t, C_last = c_t)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/lstm-input-gate.R b/recode/problems/TensorPoly/R/lstm-input-gate.R
new file mode 100644
index 0000000..69e0ef4
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-input-gate.R
@@ -0,0 +1,10 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+input_gate <- function(h_prev, x_t, W_i, b_i, W_c, b_c) {
+ concat <- cbind(h_prev, x_t)
+ i_t <- sigmoid(concat %*% t(W_i) + b_i)
+ c_tilde <- tanh(concat %*% t(W_c) + b_c)
+ list(i_t = i_t, c_tilde = c_tilde)
+}
diff --git a/recode/problems/TensorPoly/R/lstm-output-gate.R b/recode/problems/TensorPoly/R/lstm-output-gate.R
new file mode 100644
index 0000000..dc608a7
--- /dev/null
+++ b/recode/problems/TensorPoly/R/lstm-output-gate.R
@@ -0,0 +1,10 @@
+sigmoid <- function(x) {
+ 1 / (1 + exp(-pmin(pmax(x, -500), 500)))
+}
+
+output_gate <- function(h_prev, x_t, C_t, W_o, b_o) {
+ concat <- cbind(h_prev, x_t)
+ o_t <- sigmoid(concat %*% t(W_o) + b_o)
+ h_t <- o_t * tanh(C_t)
+ list(o_t = o_t, h_t = h_t)
+}
diff --git a/recode/problems/TensorPoly/R/resnet-batch-norm.R b/recode/problems/TensorPoly/R/resnet-batch-norm.R
new file mode 100644
index 0000000..126139d
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-batch-norm.R
@@ -0,0 +1,78 @@
+BatchNorm <- setRefClass(
+ "BatchNorm",
+ fields = list(
+ eps = "numeric",
+ momentum = "numeric",
+ gamma = "numeric",
+ beta = "numeric",
+ running_mean = "numeric",
+ running_var = "numeric"
+ ),
+ methods = list(
+ initialize = function(num_features, eps = 1e-5, momentum = 0.1) {
+ eps <<- eps
+ momentum <<- momentum
+ gamma <<- rep(1, num_features)
+ beta <<- rep(0, num_features)
+ running_mean <<- rep(0, num_features)
+ running_var <<- rep(1, num_features)
+ },
+ forward = function(x, training = TRUE) {
+ original_shape <- dim(x)
+ if (length(original_shape) > 2) {
+ batch <- original_shape[1]
+ channels <- original_shape[2]
+ x_reshaped <- array(x, dim = c(batch, channels, prod(original_shape[-c(1, 2)])))
+ x_reshaped <- array(aperm(x_reshaped, c(1, 3, 2)), dim = c(-1, channels))
+ } else {
+ x_reshaped <- x
+ channels <- original_shape[length(original_shape)]
+ }
+
+ if (training) {
+ batch_mean <- colMeans(x_reshaped)
+ batch_var <- apply(x_reshaped, 2, var)
+ running_mean <<- (1 - momentum) * running_mean + momentum * batch_mean
+ running_var <<- (1 - momentum) * running_var + momentum * batch_var
+ x_norm <- (x_reshaped - batch_mean) / sqrt(batch_var + eps)
+ } else {
+ x_norm <- (x_reshaped - running_mean) / sqrt(running_var + eps)
+ }
+
+ out <- gamma * x_norm + beta
+
+ if (length(original_shape) > 2) {
+ out <- array(out, dim = c(original_shape[1], prod(original_shape[-c(1, 2)]), channels))
+ out <- aperm(out, c(1, 3, 2))
+ out <- array(out, dim = original_shape)
+ } else {
+ out <- array(out, dim = original_shape)
+ }
+
+ out
+ }
+ )
+)
+
+relu <- function(x) {
+ pmax(0, x)
+}
+
+post_activation_block <- function(x, W1, W2, bn1, bn2) {
+ out <- x %*% W1
+ out <- bn1$forward(out)
+ out <- relu(out)
+ out <- out %*% W2
+ out <- bn2$forward(out)
+ relu(out + x)
+}
+
+pre_activation_block <- function(x, W1, W2, bn1, bn2) {
+ out <- bn1$forward(x)
+ out <- relu(out)
+ out <- out %*% W1
+ out <- bn2$forward(out)
+ out <- relu(out)
+ out <- out %*% W2
+ out + x
+}
diff --git a/recode/problems/TensorPoly/R/resnet-bottleneck.R b/recode/problems/TensorPoly/R/resnet-bottleneck.R
new file mode 100644
index 0000000..c2ffd65
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-bottleneck.R
@@ -0,0 +1,37 @@
+relu <- function(x) {
+ pmax(0, x)
+}
+
+BottleneckBlock <- setRefClass(
+ "BottleneckBlock",
+ fields = list(
+ in_ch = "numeric",
+ bn_ch = "numeric",
+ out_ch = "numeric",
+ W1 = "matrix",
+ W2 = "matrix",
+ W3 = "matrix",
+ Ws = "matrix"
+ ),
+ methods = list(
+ initialize = function(in_channels, bottleneck_channels, out_channels) {
+ in_ch <<- in_channels
+ bn_ch <<- bottleneck_channels
+ out_ch <<- out_channels
+ W1 <<- matrix(rnorm(in_channels * bottleneck_channels, sd = 0.01), nrow = in_channels, ncol = bottleneck_channels)
+ W2 <<- matrix(rnorm(bottleneck_channels * bottleneck_channels, sd = 0.01), nrow = bottleneck_channels, ncol = bottleneck_channels)
+ W3 <<- matrix(rnorm(bottleneck_channels * out_channels, sd = 0.01), nrow = bottleneck_channels, ncol = out_channels)
+ Ws <<- if (in_channels != out_channels) matrix(rnorm(in_channels * out_channels, sd = 0.01), nrow = in_channels, ncol = out_channels) else NULL
+ },
+ forward = function(x) {
+ identity <- x
+ out <- relu(x %*% W1)
+ out <- relu(out %*% W2)
+ out <- out %*% W3
+ if (!is.null(Ws)) {
+ identity <- identity %*% Ws
+ }
+ relu(out + identity)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/resnet-conv-block.R b/recode/problems/TensorPoly/R/resnet-conv-block.R
new file mode 100644
index 0000000..49801d0
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-conv-block.R
@@ -0,0 +1,29 @@
+relu <- function(x) {
+ pmax(0, x)
+}
+
+ConvBlock <- setRefClass(
+ "ConvBlock",
+ fields = list(
+ in_channels = "numeric",
+ out_channels = "numeric",
+ W1 = "matrix",
+ W2 = "matrix",
+ Ws = "matrix"
+ ),
+ methods = list(
+ initialize = function(in_channels, out_channels) {
+ in_channels <<- in_channels
+ out_channels <<- out_channels
+ W1 <<- matrix(rnorm(in_channels * out_channels, sd = 0.01), nrow = in_channels, ncol = out_channels)
+ W2 <<- matrix(rnorm(out_channels * out_channels, sd = 0.01), nrow = out_channels, ncol = out_channels)
+ Ws <<- matrix(rnorm(in_channels * out_channels, sd = 0.01), nrow = in_channels, ncol = out_channels)
+ },
+ forward = function(x) {
+ main <- relu(x %*% W1)
+ main <- main %*% W2
+ shortcut <- x %*% Ws
+ relu(main + shortcut)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/resnet-full-network.R b/recode/problems/TensorPoly/R/resnet-full-network.R
new file mode 100644
index 0000000..5912298
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-full-network.R
@@ -0,0 +1,64 @@
+relu <- function(x) {
+ pmax(0, x)
+}
+
+BasicBlock <- setRefClass(
+ "BasicBlock",
+ fields = list(
+ in_ch = "numeric",
+ out_ch = "numeric",
+ downsample = "logical",
+ W1 = "matrix",
+ W2 = "matrix",
+ W_proj = "matrix"
+ ),
+ methods = list(
+ initialize = function(in_ch, out_ch, downsample = FALSE) {
+ in_ch <<- in_ch
+ out_ch <<- out_ch
+ downsample <<- downsample
+ W1 <<- matrix(rnorm(in_ch * out_ch, sd = 0.01), nrow = in_ch, ncol = out_ch)
+ W2 <<- matrix(rnorm(out_ch * out_ch, sd = 0.01), nrow = out_ch, ncol = out_ch)
+ W_proj <<- if (in_ch != out_ch || downsample) matrix(rnorm(in_ch * out_ch, sd = 0.01), nrow = in_ch, ncol = out_ch) else NULL
+ },
+ forward = function(x) {
+ identity <- x
+ out <- relu(x %*% W1)
+ out <- out %*% W2
+ if (!is.null(W_proj)) {
+ identity <- identity %*% W_proj
+ }
+ relu(out + identity)
+ }
+ )
+)
+
+ResNet18 <- setRefClass(
+ "ResNet18",
+ fields = list(
+ conv1 = "matrix",
+ layer1 = "list",
+ layer2 = "list",
+ layer3 = "list",
+ layer4 = "list",
+ fc = "matrix"
+ ),
+ methods = list(
+ initialize = function(num_classes = 10) {
+ conv1 <<- matrix(rnorm(3 * 64, sd = 0.01), nrow = 3, ncol = 64)
+ layer1 <<- list(BasicBlock$new(64, 64, FALSE), BasicBlock$new(64, 64, FALSE))
+ layer2 <<- list(BasicBlock$new(64, 128, TRUE), BasicBlock$new(128, 128, FALSE))
+ layer3 <<- list(BasicBlock$new(128, 256, TRUE), BasicBlock$new(256, 256, FALSE))
+ layer4 <<- list(BasicBlock$new(256, 512, TRUE), BasicBlock$new(512, 512, FALSE))
+ fc <<- matrix(rnorm(512 * num_classes, sd = 0.01), nrow = 512, ncol = num_classes)
+ },
+ forward = function(x) {
+ out <- relu(x %*% conv1)
+ for (block in layer1) out <- block$forward(out)
+ for (block in layer2) out <- block$forward(out)
+ for (block in layer3) out <- block$forward(out)
+ for (block in layer4) out <- block$forward(out)
+ out %*% fc
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/resnet-identity-block.R b/recode/problems/TensorPoly/R/resnet-identity-block.R
new file mode 100644
index 0000000..f1812b6
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-identity-block.R
@@ -0,0 +1,25 @@
+relu <- function(x) {
+ pmax(0, x)
+}
+
+IdentityBlock <- setRefClass(
+ "IdentityBlock",
+ fields = list(
+ channels = "numeric",
+ W1 = "matrix",
+ W2 = "matrix"
+ ),
+ methods = list(
+ initialize = function(channels) {
+ channels <<- channels
+ W1 <<- matrix(rnorm(channels * channels, sd = 0.01), nrow = channels, ncol = channels)
+ W2 <<- matrix(rnorm(channels * channels, sd = 0.01), nrow = channels, ncol = channels)
+ },
+ forward = function(x) {
+ identity <- x
+ out <- relu(x %*% W1)
+ out <- out %*% W2
+ out + identity
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/resnet-skip-connection.R b/recode/problems/TensorPoly/R/resnet-skip-connection.R
new file mode 100644
index 0000000..a35d315
--- /dev/null
+++ b/recode/problems/TensorPoly/R/resnet-skip-connection.R
@@ -0,0 +1,18 @@
+compute_gradient_with_skip <- function(gradients_F, x) {
+ grad <- x
+ for (F_grad in rev(gradients_F)) {
+ F_mat <- F_grad
+ dim <- ncol(F_mat)
+ grad <- grad %*% (diag(dim) + F_mat)
+ }
+ grad
+}
+
+compute_gradient_without_skip <- function(gradients_F, x) {
+ grad <- x
+ for (F_grad in rev(gradients_F)) {
+ F_mat <- F_grad
+ grad <- grad %*% F_mat
+ }
+ grad
+}
diff --git a/recode/problems/TensorPoly/R/rnn-bptt.R b/recode/problems/TensorPoly/R/rnn-bptt.R
new file mode 100644
index 0000000..71f9927
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-bptt.R
@@ -0,0 +1,6 @@
+bptt_single_step <- function(dh_next, h_t, h_prev, x_t, W_hh) {
+ dtanh <- (1 - (h_t ^ 2)) * dh_next
+ dW_hh <- t(dtanh) %*% h_prev
+ dh_prev <- dtanh %*% W_hh
+ list(dh_prev = dh_prev, dW_hh = dW_hh)
+}
diff --git a/recode/problems/TensorPoly/R/rnn-cell.R b/recode/problems/TensorPoly/R/rnn-cell.R
new file mode 100644
index 0000000..f2a04b9
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-cell.R
@@ -0,0 +1,5 @@
+rnn_cell <- function(x_t, h_prev, W_xh, W_hh, b_h) {
+ input_term <- x_t %*% t(W_xh)
+ hidden_term <- h_prev %*% t(W_hh)
+ tanh(input_term + hidden_term + b_h)
+}
diff --git a/recode/problems/TensorPoly/R/rnn-forward-sequence.R b/recode/problems/TensorPoly/R/rnn-forward-sequence.R
new file mode 100644
index 0000000..da18f3a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-forward-sequence.R
@@ -0,0 +1,17 @@
+rnn_forward <- function(X, h_0, W_xh, W_hh, b_h) {
+ dims <- dim(X)
+ batch_size <- dims[1]
+ time_steps <- dims[2]
+
+ h_all_list <- list()
+ h_current <- h_0
+
+ for (t in seq_len(time_steps)) {
+ x_t <- X[, t, ]
+ h_current <- tanh(x_t %*% t(W_xh) + h_current %*% t(W_hh) + b_h)
+ h_all_list[[t]] <- h_current
+ }
+
+ h_all <- array(unlist(h_all_list), dim = c(batch_size, time_steps, ncol(h_current)))
+ list(h_all = h_all, h_final = h_current)
+}
diff --git a/recode/problems/TensorPoly/R/rnn-full-network.R b/recode/problems/TensorPoly/R/rnn-full-network.R
new file mode 100644
index 0000000..96f859a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-full-network.R
@@ -0,0 +1,48 @@
+VanillaRNN <- setRefClass(
+ "VanillaRNN",
+ fields = list(
+ hidden_dim = "numeric",
+ W_xh = "matrix",
+ W_hh = "matrix",
+ W_hy = "matrix",
+ b_h = "numeric",
+ b_y = "numeric"
+ ),
+ methods = list(
+ initialize = function(input_dim, hidden_dim, output_dim) {
+ hidden_dim <<- hidden_dim
+ W_xh <<- matrix(rnorm(hidden_dim * input_dim), nrow = hidden_dim) * sqrt(2.0 / (input_dim + hidden_dim))
+ W_hh <<- matrix(rnorm(hidden_dim * hidden_dim), nrow = hidden_dim) * sqrt(2.0 / (2 * hidden_dim))
+ W_hy <<- matrix(rnorm(output_dim * hidden_dim), nrow = output_dim) * sqrt(2.0 / (hidden_dim + output_dim))
+ b_h <<- numeric(hidden_dim)
+ b_y <<- numeric(output_dim)
+ },
+ forward = function(X, h_0 = NULL) {
+ dims <- dim(X)
+ batch_size <- dims[1]
+ time_steps <- dims[2]
+
+ if (is.null(h_0)) {
+ h_current <- matrix(0, nrow = batch_size, ncol = hidden_dim)
+ } else {
+ h_current <- h_0
+ }
+
+ h_list <- list()
+ for (t in seq_len(time_steps)) {
+ x_t <- X[, t, ]
+ h_current <- tanh(x_t %*% t(W_xh) + h_current %*% t(W_hh) + b_h)
+ h_list[[t]] <- h_current
+ }
+
+ h_seq <- array(unlist(h_list), dim = c(batch_size, time_steps, hidden_dim))
+ h_final <- h_current
+
+ h_flat <- matrix(h_seq, ncol = hidden_dim)
+ y_flat <- h_flat %*% t(W_hy) + b_y
+ y_seq <- array(y_flat, dim = c(batch_size, time_steps, nrow(W_hy)))
+
+ list(y_seq = y_seq, h_final = h_final)
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/rnn-hidden-state.R b/recode/problems/TensorPoly/R/rnn-hidden-state.R
new file mode 100644
index 0000000..83bd77c
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-hidden-state.R
@@ -0,0 +1,3 @@
+init_hidden <- function(batch_size, hidden_dim) {
+ matrix(0, nrow = batch_size, ncol = hidden_dim)
+}
diff --git a/recode/problems/TensorPoly/R/rnn-vanishing-gradients.R b/recode/problems/TensorPoly/R/rnn-vanishing-gradients.R
new file mode 100644
index 0000000..13bffcb
--- /dev/null
+++ b/recode/problems/TensorPoly/R/rnn-vanishing-gradients.R
@@ -0,0 +1,15 @@
+compute_gradient_norm_decay <- function(T, W_hh) {
+ spectral_norm <- norm(W_hh, type = "2")
+ norms <- numeric(T)
+ norms[1] <- 1.0
+ current_norm <- 1.0
+
+ if (T > 1) {
+ for (i in 2:T) {
+ current_norm <- current_norm * spectral_norm
+ norms[i] <- current_norm
+ }
+ }
+
+ norms
+}
diff --git a/recode/problems/TensorPoly/R/sigmoid-numpy.R b/recode/problems/TensorPoly/R/sigmoid-numpy.R
new file mode 100644
index 0000000..310819f
--- /dev/null
+++ b/recode/problems/TensorPoly/R/sigmoid-numpy.R
@@ -0,0 +1,4 @@
+sigmoid <- function(x) {
+ x_arr <- as.numeric(x)
+ 1.0 / (1.0 + exp(-x_arr))
+}
diff --git a/recode/problems/TensorPoly/R/transformers-attention.R b/recode/problems/TensorPoly/R/transformers-attention.R
new file mode 100644
index 0000000..7414902
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-attention.R
@@ -0,0 +1,19 @@
+scaled_dot_product_attention <- function(Q, K, V) {
+ dims <- dim(Q)
+ batch_size <- dims[1]
+ seq_len_q <- dims[2]
+ d_k <- dims[3]
+ d_v <- dim(V)[3]
+
+ output <- array(0, dim = c(batch_size, seq_len_q, d_v))
+
+ for (b in seq_len(batch_size)) {
+ scores <- Q[b, , ] %*% t(K[b, , ])
+ scaled_scores <- scores / sqrt(d_k)
+ exp_scores <- exp(scaled_scores - apply(scaled_scores, 1, max))
+ attention_weights <- exp_scores / rowSums(exp_scores)
+ output[b, , ] <- attention_weights %*% V[b, , ]
+ }
+
+ output
+}
diff --git a/recode/problems/TensorPoly/R/transformers-embedding.R b/recode/problems/TensorPoly/R/transformers-embedding.R
new file mode 100644
index 0000000..e4323a8
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-embedding.R
@@ -0,0 +1,9 @@
+create_embedding_layer <- function(vocab_size, d_model) {
+ matrix(rnorm(vocab_size * d_model, sd = 1 / sqrt(d_model)), nrow = vocab_size, ncol = d_model)
+}
+
+embed_tokens <- function(embedding, tokens, d_model) {
+ embedded <- embedding[tokens + 1, , drop = FALSE]
+ scaled_embeddings <- embedded * sqrt(d_model)
+ scaled_embeddings
+}
diff --git a/recode/problems/TensorPoly/R/transformers-encoder-block.R b/recode/problems/TensorPoly/R/transformers-encoder-block.R
new file mode 100644
index 0000000..c80a85d
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-encoder-block.R
@@ -0,0 +1,82 @@
+softmax <- function(x, axis = -1) {
+ exp_x <- exp(x - apply(x, axis, max))
+ exp_x / apply(exp_x, axis, sum)
+}
+
+layer_norm <- function(x, gamma, beta, eps = 1e-6) {
+ dims <- dim(x)
+ keep_axes <- seq_len(length(dims) - 1)
+ mean_vals <- apply(x, keep_axes, mean)
+ var_vals <- apply(x, keep_axes, var)
+ mean_arr <- array(mean_vals, dim = c(dims[-length(dims)], 1))
+ var_arr <- array(var_vals, dim = c(dims[-length(dims)], 1))
+ x_normalized <- (x - mean_arr) / sqrt(var_arr + eps)
+ gamma * x_normalized + beta
+}
+
+multi_head_attention <- function(Q, K, V, W_q, W_k, W_v, W_o, num_heads) {
+ dims <- dim(Q)
+ batch_size <- dims[1]
+ seq_len <- dims[2]
+ d_model <- dims[3]
+ d_k <- d_model %/% num_heads
+
+ Q_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+ K_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+ V_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+
+ for (b in seq_len(batch_size)) {
+ Q_proj[b, , ] <- Q[b, , ] %*% W_q
+ K_proj[b, , ] <- K[b, , ] %*% W_k
+ V_proj[b, , ] <- V[b, , ] %*% W_v
+ }
+
+ head_outputs <- array(0, dim = c(batch_size, num_heads, seq_len, d_k))
+
+ for (b in seq_len(batch_size)) {
+ for (h in seq_len(num_heads)) {
+ idx <- ((h - 1) * d_k + 1):(h * d_k)
+ Qh <- Q_proj[b, , idx]
+ Kh <- K_proj[b, , idx]
+ Vh <- V_proj[b, , idx]
+
+ scores <- Qh %*% t(Kh)
+ scaled_scores <- scores / sqrt(d_k)
+ exp_scores <- exp(scaled_scores - apply(scaled_scores, 1, max))
+ attention_weights <- exp_scores / rowSums(exp_scores)
+ head_outputs[b, h, , ] <- attention_weights %*% Vh
+ }
+ }
+
+ concatenated <- array(0, dim = c(batch_size, seq_len, d_model))
+ for (b in seq_len(batch_size)) {
+ concat_rows <- list()
+ for (h in seq_len(num_heads)) {
+ concat_rows[[h]] <- head_outputs[b, h, , ]
+ }
+ concatenated[b, , ] <- do.call(cbind, concat_rows)
+ }
+
+ output <- array(0, dim = c(batch_size, seq_len, d_model))
+ for (b in seq_len(batch_size)) {
+ output[b, , ] <- concatenated[b, , ] %*% W_o
+ }
+
+ output
+}
+
+feed_forward <- function(x, W1, b1, W2, b2) {
+ hidden <- x %*% W1 + b1
+ relu_out <- pmax(0, hidden)
+ relu_out %*% W2 + b2
+}
+
+encoder_block <- function(x, W_q, W_k, W_v, W_o, W1, b1, W2, b2, gamma1, beta1, gamma2, beta2, num_heads) {
+ attn_output <- multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual <- x + attn_output
+ x_norm1 <- layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output <- feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual <- x_norm1 + ff_output
+ layer_norm(x_ff_residual, gamma2, beta2)
+}
diff --git a/recode/problems/TensorPoly/R/transformers-feed-forward.R b/recode/problems/TensorPoly/R/transformers-feed-forward.R
new file mode 100644
index 0000000..e0ea2bb
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-feed-forward.R
@@ -0,0 +1,5 @@
+feed_forward <- function(x, W1, b1, W2, b2) {
+ hidden <- x %*% W1 + b1
+ relu_out <- pmax(0, hidden)
+ relu_out %*% W2 + b2
+}
diff --git a/recode/problems/TensorPoly/R/transformers-layer-normalization.R b/recode/problems/TensorPoly/R/transformers-layer-normalization.R
new file mode 100644
index 0000000..15b1594
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-layer-normalization.R
@@ -0,0 +1,10 @@
+layer_norm <- function(x, gamma, beta, eps = 1e-6) {
+ dims <- dim(x)
+ keep_axes <- seq_len(length(dims) - 1)
+ mean_vals <- apply(x, keep_axes, mean)
+ var_vals <- apply(x, keep_axes, var)
+ mean_arr <- array(mean_vals, dim = c(dims[-length(dims)], 1))
+ var_arr <- array(var_vals, dim = c(dims[-length(dims)], 1))
+ x_normalized <- (x - mean_arr) / sqrt(var_arr + eps)
+ gamma * x_normalized + beta
+}
diff --git a/recode/problems/TensorPoly/R/transformers-multi-head-attention.R b/recode/problems/TensorPoly/R/transformers-multi-head-attention.R
new file mode 100644
index 0000000..804188c
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-multi-head-attention.R
@@ -0,0 +1,55 @@
+softmax <- function(x, axis = -1) {
+ exp_x <- exp(x - apply(x, axis, max))
+ exp_x / apply(exp_x, axis, sum)
+}
+
+multi_head_attention <- function(Q, K, V, W_q, W_k, W_v, W_o, num_heads) {
+ dims <- dim(Q)
+ batch_size <- dims[1]
+ seq_len <- dims[2]
+ d_model <- dims[3]
+ d_k <- d_model %/% num_heads
+
+ Q_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+ K_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+ V_proj <- array(0, dim = c(batch_size, seq_len, d_model))
+
+ for (b in seq_len(batch_size)) {
+ Q_proj[b, , ] <- Q[b, , ] %*% W_q
+ K_proj[b, , ] <- K[b, , ] %*% W_k
+ V_proj[b, , ] <- V[b, , ] %*% W_v
+ }
+
+ head_outputs <- array(0, dim = c(batch_size, num_heads, seq_len, d_k))
+
+ for (b in seq_len(batch_size)) {
+ for (h in seq_len(num_heads)) {
+ idx <- ((h - 1) * d_k + 1):(h * d_k)
+ Qh <- Q_proj[b, , idx]
+ Kh <- K_proj[b, , idx]
+ Vh <- V_proj[b, , idx]
+
+ scores <- Qh %*% t(Kh)
+ scaled_scores <- scores / sqrt(d_k)
+ exp_scores <- exp(scaled_scores - apply(scaled_scores, 1, max))
+ attention_weights <- exp_scores / rowSums(exp_scores)
+ head_outputs[b, h, , ] <- attention_weights %*% Vh
+ }
+ }
+
+ concatenated <- array(0, dim = c(batch_size, seq_len, d_model))
+ for (b in seq_len(batch_size)) {
+ concat_rows <- list()
+ for (h in seq_len(num_heads)) {
+ concat_rows[[h]] <- head_outputs[b, h, , ]
+ }
+ concatenated[b, , ] <- do.call(cbind, concat_rows)
+ }
+
+ output <- array(0, dim = c(batch_size, seq_len, d_model))
+ for (b in seq_len(batch_size)) {
+ output[b, , ] <- concatenated[b, , ] %*% W_o
+ }
+
+ output
+}
diff --git a/recode/problems/TensorPoly/R/transformers-positional-encoding.R b/recode/problems/TensorPoly/R/transformers-positional-encoding.R
new file mode 100644
index 0000000..d53588a
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-positional-encoding.R
@@ -0,0 +1,14 @@
+positional_encoding <- function(seq_length, d_model) {
+ position <- matrix(0:(seq_length - 1), ncol = 1)
+ i <- seq(0, d_model - 1, by = 2)
+ div_term <- exp(i * (-log(10000.0) / d_model))
+
+ pe <- matrix(0, nrow = seq_length, ncol = d_model)
+ sin_idx <- seq(1, d_model, by = 2)
+ cos_idx <- seq(2, d_model, by = 2)
+ pe[, sin_idx] <- sin(position %*% t(div_term))
+ if (length(cos_idx) > 0) {
+ pe[, cos_idx] <- cos(position %*% t(div_term[1:length(cos_idx)]))
+ }
+ pe
+}
diff --git a/recode/problems/TensorPoly/R/transformers-tokenization.R b/recode/problems/TensorPoly/R/transformers-tokenization.R
new file mode 100644
index 0000000..2393ab8
--- /dev/null
+++ b/recode/problems/TensorPoly/R/transformers-tokenization.R
@@ -0,0 +1,59 @@
+SimpleTokenizer <- setRefClass(
+ "SimpleTokenizer",
+ fields = list(
+ word_to_id = "list",
+ id_to_word = "list",
+ vocab_size = "numeric",
+ pad_token = "character",
+ unk_token = "character",
+ bos_token = "character",
+ eos_token = "character"
+ ),
+ methods = list(
+ initialize = function() {
+ word_to_id <<- list()
+ id_to_word <<- list()
+ vocab_size <<- 0
+ pad_token <<- ""
+ unk_token <<- ""
+ bos_token <<- ""
+ eos_token <<- ""
+ },
+ build_vocab = function(texts) {
+ special_tokens <- c(pad_token, unk_token, bos_token, eos_token)
+ for (idx in seq_along(special_tokens)) {
+ token <- special_tokens[idx]
+ word_to_id[[token]] <<- idx - 1
+ id_to_word[[as.character(idx - 1)]] <<- token
+ }
+
+ unique_words <- unique(unlist(strsplit(texts, " ")))
+ current_id <- length(special_tokens)
+ for (word in sort(unique_words)) {
+ if (is.null(word_to_id[[word]])) {
+ word_to_id[[word]] <<- current_id
+ id_to_word[[as.character(current_id)]] <<- word
+ current_id <- current_id + 1
+ }
+ }
+ vocab_size <<- length(word_to_id)
+ },
+ encode = function(text) {
+ words <- unlist(strsplit(text, " "))
+ sapply(words, function(word) {
+ if (!is.null(word_to_id[[word]])) {
+ word_to_id[[word]]
+ } else {
+ word_to_id[[unk_token]]
+ }
+ })
+ },
+ decode = function(ids) {
+ words <- sapply(ids, function(token_id) {
+ word <- id_to_word[[as.character(token_id)]]
+ if (is.null(word)) unk_token else word
+ })
+ paste(words, collapse = " ")
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/unet-bottleneck.R b/recode/problems/TensorPoly/R/unet-bottleneck.R
new file mode 100644
index 0000000..ad71804
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-bottleneck.R
@@ -0,0 +1,10 @@
+unet_bottleneck <- function(x, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+
+ H_out <- H - 4
+ W_out <- W - 4
+ array(0, dim = c(batch, H_out, W_out, out_channels))
+}
diff --git a/recode/problems/TensorPoly/R/unet-decoder-block.R b/recode/problems/TensorPoly/R/unet-decoder-block.R
new file mode 100644
index 0000000..b91e27f
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-decoder-block.R
@@ -0,0 +1,22 @@
+unet_decoder_block <- function(x, skip, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+
+ skip_dims <- dim(skip)
+ H_skip <- skip_dims[2]
+ W_skip <- skip_dims[3]
+
+ H_up <- H * 2
+ W_up <- W * 2
+
+ crop_h <- (H_skip - H_up) %/% 2
+ crop_w <- (W_skip - W_up) %/% 2
+ _ <- skip[, (crop_h + 1):(crop_h + H_up), (crop_w + 1):(crop_w + W_up), ]
+
+ H_out <- H_up - 4
+ W_out <- W_up - 4
+
+ array(0, dim = c(batch, H_out, W_out, out_channels))
+}
diff --git a/recode/problems/TensorPoly/R/unet-encoder-block.R b/recode/problems/TensorPoly/R/unet-encoder-block.R
new file mode 100644
index 0000000..7b61084
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-encoder-block.R
@@ -0,0 +1,16 @@
+unet_encoder_block <- function(x, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+
+ skip_H <- H - 4
+ skip_W <- W - 4
+ skip_out <- array(0, dim = c(batch, skip_H, skip_W, out_channels))
+
+ pool_H <- skip_H %/% 2
+ pool_W <- skip_W %/% 2
+ pool_out <- array(0, dim = c(batch, pool_H, pool_W, out_channels))
+
+ list(pool_out = pool_out, skip_out = skip_out)
+}
diff --git a/recode/problems/TensorPoly/R/unet-full-network.R b/recode/problems/TensorPoly/R/unet-full-network.R
new file mode 100644
index 0000000..81dc223
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-full-network.R
@@ -0,0 +1,69 @@
+encoder_block <- function(x, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+
+ skip_H <- H - 4
+ skip_W <- W - 4
+ skip <- array(0, dim = c(batch, skip_H, skip_W, out_channels))
+
+ pool_H <- skip_H %/% 2
+ pool_W <- skip_W %/% 2
+ pooled <- array(0, dim = c(batch, pool_H, pool_W, out_channels))
+
+ list(pooled = pooled, skip = skip)
+}
+
+bottleneck <- function(x, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+ array(0, dim = c(batch, H - 4, W - 4, out_channels))
+}
+
+decoder_block <- function(x, skip, out_channels) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+
+ H_up <- H * 2
+ W_up <- W * 2
+
+ skip_dims <- dim(skip)
+ H_skip <- skip_dims[2]
+ W_skip <- skip_dims[3]
+ crop_h <- (H_skip - H_up) %/% 2
+ crop_w <- (W_skip - W_up) %/% 2
+ _ <- skip[, (crop_h + 1):(crop_h + H_up), (crop_w + 1):(crop_w + W_up), ]
+
+ H_out <- H_up - 4
+ W_out <- W_up - 4
+ array(0, dim = c(batch, H_out, W_out, out_channels))
+}
+
+output_layer <- function(x, num_classes) {
+ dims <- dim(x)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+ array(0, dim = c(batch, H, W, num_classes))
+}
+
+unet <- function(x, num_classes = 2) {
+ e1 <- encoder_block(x, out_channels = 64)
+ e2 <- encoder_block(e1$pooled, out_channels = 128)
+ e3 <- encoder_block(e2$pooled, out_channels = 256)
+ e4 <- encoder_block(e3$pooled, out_channels = 512)
+
+ bottleneck_out <- bottleneck(e4$pooled, out_channels = 1024)
+
+ d4_out <- decoder_block(bottleneck_out, e4$skip, out_channels = 512)
+ d3_out <- decoder_block(d4_out, e3$skip, out_channels = 256)
+ d2_out <- decoder_block(d3_out, e2$skip, out_channels = 128)
+ d1_out <- decoder_block(d2_out, e1$skip, out_channels = 64)
+
+ output_layer(d1_out, num_classes)
+}
diff --git a/recode/problems/TensorPoly/R/unet-output-layer.R b/recode/problems/TensorPoly/R/unet-output-layer.R
new file mode 100644
index 0000000..9c6ebdb
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-output-layer.R
@@ -0,0 +1,7 @@
+unet_output <- function(features, num_classes) {
+ dims <- dim(features)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+ array(0, dim = c(batch, H, W, num_classes))
+}
diff --git a/recode/problems/TensorPoly/R/unet-skip-connection.R b/recode/problems/TensorPoly/R/unet-skip-connection.R
new file mode 100644
index 0000000..4b9fd2b
--- /dev/null
+++ b/recode/problems/TensorPoly/R/unet-skip-connection.R
@@ -0,0 +1,15 @@
+crop_and_concat <- function(encoder_features, decoder_features) {
+ dims_enc <- dim(encoder_features)
+ dims_dec <- dim(decoder_features)
+
+ H_enc <- dims_enc[2]
+ W_enc <- dims_enc[3]
+ H_dec <- dims_dec[2]
+ W_dec <- dims_dec[3]
+
+ crop_h <- (H_enc - H_dec) %/% 2
+ crop_w <- (W_enc - W_dec) %/% 2
+
+ encoder_cropped <- encoder_features[, (crop_h + 1):(crop_h + H_dec), (crop_w + 1):(crop_w + W_dec), ]
+ array(c(encoder_cropped, decoder_features), dim = c(dims_dec[1], H_dec, W_dec, dims_enc[4] + dims_dec[4]))
+}
diff --git a/recode/problems/TensorPoly/R/vae-decoder.R b/recode/problems/TensorPoly/R/vae-decoder.R
new file mode 100644
index 0000000..f006e75
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-decoder.R
@@ -0,0 +1,15 @@
+vae_decoder <- function(z, output_dim) {
+ dims <- dim(z)
+ latent_dim <- dims[2]
+ hidden_dim <- 256
+
+ w_h <- matrix(rnorm(latent_dim * hidden_dim, sd = 0.01), nrow = latent_dim, ncol = hidden_dim)
+ b_h <- numeric(hidden_dim)
+ h <- pmax(0, z %*% w_h + b_h)
+
+ w_out <- matrix(rnorm(hidden_dim * output_dim, sd = 0.01), nrow = hidden_dim, ncol = output_dim)
+ b_out <- numeric(output_dim)
+ logits <- h %*% w_out + b_out
+
+ 1 / (1 + exp(-logits))
+}
diff --git a/recode/problems/TensorPoly/R/vae-elbo-loss.R b/recode/problems/TensorPoly/R/vae-elbo-loss.R
new file mode 100644
index 0000000..23cc9da
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-elbo-loss.R
@@ -0,0 +1,11 @@
+vae_loss <- function(x, x_recon, mu, log_var) {
+ recon_loss_per_sample <- rowSums((x - x_recon) ^ 2)
+ recon_loss <- mean(recon_loss_per_sample)
+
+ var <- exp(log_var)
+ kl_per_sample <- -0.5 * rowSums(1 + log_var - (mu ^ 2) - var)
+ kl_loss <- mean(kl_per_sample)
+
+ total_loss <- recon_loss + kl_loss
+ list(total = total_loss, recon = recon_loss, kl = kl_loss)
+}
diff --git a/recode/problems/TensorPoly/R/vae-encoder.R b/recode/problems/TensorPoly/R/vae-encoder.R
new file mode 100644
index 0000000..ca3bfb4
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-encoder.R
@@ -0,0 +1,19 @@
+vae_encoder <- function(x, latent_dim) {
+ dims <- dim(x)
+ input_dim <- dims[2]
+ hidden_dim <- 256
+
+ w_h <- matrix(rnorm(input_dim * hidden_dim, sd = 0.01), nrow = input_dim, ncol = hidden_dim)
+ b_h <- numeric(hidden_dim)
+ h <- pmax(0, x %*% w_h + b_h)
+
+ w_mu <- matrix(rnorm(hidden_dim * latent_dim, sd = 0.01), nrow = hidden_dim, ncol = latent_dim)
+ b_mu <- numeric(latent_dim)
+ mu <- h %*% w_mu + b_mu
+
+ w_log_var <- matrix(rnorm(hidden_dim * latent_dim, sd = 0.01), nrow = hidden_dim, ncol = latent_dim)
+ b_log_var <- numeric(latent_dim)
+ log_var <- h %*% w_log_var + b_log_var
+
+ list(mu = mu, log_var = log_var)
+}
diff --git a/recode/problems/TensorPoly/R/vae-full-network.R b/recode/problems/TensorPoly/R/vae-full-network.R
new file mode 100644
index 0000000..965bda0
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-full-network.R
@@ -0,0 +1,59 @@
+VAE <- setRefClass(
+ "VAE",
+ fields = list(
+ input_dim = "numeric",
+ latent_dim = "numeric",
+ hidden_dim = "numeric",
+ w_enc = "matrix",
+ b_enc = "numeric",
+ w_mu = "matrix",
+ b_mu = "numeric",
+ w_log_var = "matrix",
+ b_log_var = "numeric",
+ w_dec_h = "matrix",
+ b_dec_h = "numeric",
+ w_dec_out = "matrix",
+ b_dec_out = "numeric"
+ ),
+ methods = list(
+ initialize = function(input_dim, latent_dim) {
+ input_dim <<- input_dim
+ latent_dim <<- latent_dim
+ hidden_dim <<- 256
+
+ w_enc <<- matrix(rnorm(input_dim * hidden_dim, sd = 0.01), nrow = input_dim, ncol = hidden_dim)
+ b_enc <<- numeric(hidden_dim)
+
+ w_mu <<- matrix(rnorm(hidden_dim * latent_dim, sd = 0.01), nrow = hidden_dim, ncol = latent_dim)
+ b_mu <<- numeric(latent_dim)
+ w_log_var <<- matrix(rnorm(hidden_dim * latent_dim, sd = 0.01), nrow = hidden_dim, ncol = latent_dim)
+ b_log_var <<- numeric(latent_dim)
+
+ w_dec_h <<- matrix(rnorm(latent_dim * hidden_dim, sd = 0.01), nrow = latent_dim, ncol = hidden_dim)
+ b_dec_h <<- numeric(hidden_dim)
+ w_dec_out <<- matrix(rnorm(hidden_dim * input_dim, sd = 0.01), nrow = hidden_dim, ncol = input_dim)
+ b_dec_out <<- numeric(input_dim)
+ },
+ forward = function(x) {
+ h_enc <- pmax(0, x %*% w_enc + b_enc)
+ mu <- h_enc %*% w_mu + b_mu
+ log_var <- h_enc %*% w_log_var + b_log_var
+
+ std <- exp(0.5 * log_var)
+ eps <- matrix(rnorm(length(mu)), nrow = nrow(mu), ncol = ncol(mu))
+ z <- mu + std * eps
+
+ h_dec <- pmax(0, z %*% w_dec_h + b_dec_h)
+ logits <- h_dec %*% w_dec_out + b_dec_out
+ x_recon <- 1 / (1 + exp(-logits))
+
+ list(x_recon = x_recon, mu = mu, log_var = log_var)
+ },
+ generate = function(n_samples) {
+ z <- matrix(rnorm(n_samples * latent_dim), nrow = n_samples, ncol = latent_dim)
+ h_dec <- pmax(0, z %*% w_dec_h + b_dec_h)
+ logits <- h_dec %*% w_dec_out + b_dec_out
+ 1 / (1 + exp(-logits))
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/vae-kl-divergence.R b/recode/problems/TensorPoly/R/vae-kl-divergence.R
new file mode 100644
index 0000000..5e60a80
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-kl-divergence.R
@@ -0,0 +1,6 @@
+kl_divergence <- function(mu, log_var) {
+ var <- exp(log_var)
+ kl_element <- 1 + log_var - (mu ^ 2) - var
+ batch_kl <- -0.5 * rowSums(kl_element)
+ mean(batch_kl)
+}
diff --git a/recode/problems/TensorPoly/R/vae-reparameterization.R b/recode/problems/TensorPoly/R/vae-reparameterization.R
new file mode 100644
index 0000000..467a4f7
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vae-reparameterization.R
@@ -0,0 +1,5 @@
+reparameterize <- function(mu, log_var) {
+ std <- exp(0.5 * log_var)
+ epsilon <- matrix(rnorm(length(mu)), nrow = nrow(mu), ncol = ncol(mu))
+ mu + std * epsilon
+}
diff --git a/recode/problems/TensorPoly/R/vgg-classifier.R b/recode/problems/TensorPoly/R/vgg-classifier.R
new file mode 100644
index 0000000..d6789c7
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-classifier.R
@@ -0,0 +1,20 @@
+vgg_classifier <- function(features, num_classes = 1000) {
+ batch_size <- dim(features)[1]
+ x <- matrix(features, nrow = batch_size)
+
+ dense_relu <- function(input_data, out_dim) {
+ in_dim <- ncol(input_data)
+ limit <- sqrt(2 / in_dim)
+ w <- matrix(rnorm(in_dim * out_dim) * limit, nrow = in_dim, ncol = out_dim)
+ b <- numeric(out_dim)
+ pmax(0, input_data %*% w + b)
+ }
+
+ x <- dense_relu(x, 4096)
+ x <- dense_relu(x, 4096)
+
+ in_dim_final <- ncol(x)
+ w_final <- matrix(rnorm(in_dim_final * num_classes) * sqrt(2 / in_dim_final), nrow = in_dim_final, ncol = num_classes)
+ b_final <- numeric(num_classes)
+ x %*% w_final + b_final
+}
diff --git a/recode/problems/TensorPoly/R/vgg-config.R b/recode/problems/TensorPoly/R/vgg-config.R
new file mode 100644
index 0000000..faf3537
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-config.R
@@ -0,0 +1,10 @@
+make_vgg_config <- function(variant) {
+ configs <- list(
+ vgg11 = list(64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"),
+ vgg13 = list(64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"),
+ vgg16 = list(64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"),
+ vgg19 = list(64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M")
+ )
+ key <- tolower(variant)
+ if (!is.null(configs[[key]])) configs[[key]] else list()
+}
diff --git a/recode/problems/TensorPoly/R/vgg-conv-block.R b/recode/problems/TensorPoly/R/vgg-conv-block.R
new file mode 100644
index 0000000..840f8ec
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-conv-block.R
@@ -0,0 +1,30 @@
+vgg_conv_block <- function(x, num_convs, out_channels) {
+ current_x <- x
+
+ for (i in seq_len(num_convs)) {
+ in_channels <- dim(current_x)[4]
+ limit <- sqrt(2 / (3 * 3 * in_channels))
+ weights <- array(rnorm(3 * 3 * in_channels * out_channels) * limit, dim = c(3, 3, in_channels, out_channels))
+ bias <- numeric(out_channels)
+
+ padded_x <- array(0, dim = c(dim(current_x)[1], dim(current_x)[2] + 2, dim(current_x)[3] + 2, in_channels))
+ padded_x[, 2:(dim(current_x)[2] + 1), 2:(dim(current_x)[3] + 1), ] <- current_x
+
+ batch <- dim(current_x)[1]
+ h <- dim(current_x)[2]
+ w <- dim(current_x)[3]
+ out <- array(0, dim = c(batch, h, w, out_channels))
+
+ for (i in 1:3) {
+ for (j in 1:3) {
+ window <- padded_x[, i:(i + h - 1), j:(j + w - 1), ]
+ out <- out + apply(window, c(1, 2, 3), function(slice) slice %*% weights[i, j, , ])
+ }
+ }
+
+ out <- out + bias
+ current_x <- pmax(0, out)
+ }
+
+ current_x
+}
diff --git a/recode/problems/TensorPoly/R/vgg-feature-extractor.R b/recode/problems/TensorPoly/R/vgg-feature-extractor.R
new file mode 100644
index 0000000..fb64113
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-feature-extractor.R
@@ -0,0 +1,27 @@
+conv_relu <- function(x, out_channels) {
+ C <- dim(x)[4]
+ W_weights <- array(rnorm(C * out_channels) * 0.1, dim = c(C, out_channels))
+ x <- apply(x, c(1, 2, 3), function(slice) slice %*% W_weights)
+ pmax(0, x)
+}
+
+maxpool_2x2 <- function(x) {
+ B <- dim(x)[1]
+ H <- dim(x)[2]
+ W <- dim(x)[3]
+ C <- dim(x)[4]
+ reshaped <- array(x, dim = c(B, H %/% 2, 2, W %/% 2, 2, C))
+ apply(reshaped, c(1, 2, 4, 6), max)
+}
+
+vgg_features <- function(x, config) {
+ out <- x
+ for (layer in config) {
+ if (is.numeric(layer)) {
+ out <- conv_relu(out, layer)
+ } else if (layer == "M") {
+ out <- maxpool_2x2(out)
+ }
+ }
+ out
+}
diff --git a/recode/problems/TensorPoly/R/vgg-full-network.R b/recode/problems/TensorPoly/R/vgg-full-network.R
new file mode 100644
index 0000000..038560d
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-full-network.R
@@ -0,0 +1,12 @@
+vgg16 <- function(x, num_classes = 1000) {
+ vgg16_config <- list(
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M"
+ )
+
+ features <- vgg_features(x, vgg16_config)
+ vgg_classifier(features, num_classes)
+}
diff --git a/recode/problems/TensorPoly/R/vgg-maxpool.R b/recode/problems/TensorPoly/R/vgg-maxpool.R
new file mode 100644
index 0000000..cc0be6f
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vgg-maxpool.R
@@ -0,0 +1,9 @@
+vgg_maxpool <- function(x) {
+ batch <- dim(x)[1]
+ h <- dim(x)[2]
+ w <- dim(x)[3]
+ c <- dim(x)[4]
+
+ reshaped_x <- array(x, dim = c(batch, h %/% 2, 2, w %/% 2, 2, c))
+ apply(reshaped_x, c(1, 2, 4, 6), max)
+}
diff --git a/recode/problems/TensorPoly/R/vit-class-token.R b/recode/problems/TensorPoly/R/vit-class-token.R
new file mode 100644
index 0000000..eb2ffc7
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-class-token.R
@@ -0,0 +1,6 @@
+prepend_class_token <- function(patches, embed_dim) {
+ batch_size <- dim(patches)[1]
+ cls_token <- array(rnorm(embed_dim, sd = 0.02), dim = c(1, 1, embed_dim))
+ cls_token_batch <- array(rep(cls_token, batch_size), dim = c(batch_size, 1, embed_dim))
+ array(c(cls_token_batch, patches), dim = c(batch_size, dim(patches)[2] + 1, embed_dim))
+}
diff --git a/recode/problems/TensorPoly/R/vit-encoder-block.R b/recode/problems/TensorPoly/R/vit-encoder-block.R
new file mode 100644
index 0000000..aa1a548
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-encoder-block.R
@@ -0,0 +1,88 @@
+layer_norm <- function(x, eps = 1e-6) {
+ mean <- apply(x, length(dim(x)), mean)
+ var <- apply(x, length(dim(x)), var)
+ x_normalized <- (x - mean) / sqrt(var + eps)
+ x_normalized
+}
+
+gelu <- function(x) {
+ 0.5 * x * (1 + tanh(sqrt(2 / pi) * (x + 0.044715 * x^3)))
+}
+
+softmax <- function(x, axis = -1) {
+ exp_x <- exp(x - apply(x, axis, max))
+ exp_x / apply(exp_x, axis, sum)
+}
+
+multi_head_self_attention <- function(x, num_heads, embed_dim) {
+ dims <- dim(x)
+ batch <- dims[1]
+ seq_len <- dims[2]
+ head_dim <- embed_dim %/% num_heads
+
+ W_q <- matrix(rnorm(embed_dim * embed_dim, sd = 0.02), nrow = embed_dim, ncol = embed_dim)
+ W_k <- matrix(rnorm(embed_dim * embed_dim, sd = 0.02), nrow = embed_dim, ncol = embed_dim)
+ W_v <- matrix(rnorm(embed_dim * embed_dim, sd = 0.02), nrow = embed_dim, ncol = embed_dim)
+ W_o <- matrix(rnorm(embed_dim * embed_dim, sd = 0.02), nrow = embed_dim, ncol = embed_dim)
+
+ Q <- array(0, dim = c(batch, seq_len, embed_dim))
+ K <- array(0, dim = c(batch, seq_len, embed_dim))
+ V <- array(0, dim = c(batch, seq_len, embed_dim))
+ for (b in seq_len(batch)) {
+ Q[b, , ] <- x[b, , ] %*% W_q
+ K[b, , ] <- x[b, , ] %*% W_k
+ V[b, , ] <- x[b, , ] %*% W_v
+ }
+
+ Q <- array(Q, dim = c(batch, seq_len, num_heads, head_dim))
+ K <- array(K, dim = c(batch, seq_len, num_heads, head_dim))
+ V <- array(V, dim = c(batch, seq_len, num_heads, head_dim))
+
+ Q <- aperm(Q, c(1, 3, 2, 4))
+ K <- aperm(K, c(1, 3, 2, 4))
+ V <- aperm(V, c(1, 3, 2, 4))
+
+ head_outputs <- array(0, dim = c(batch, num_heads, seq_len, head_dim))
+ for (b in seq_len(batch)) {
+ for (h in seq_len(num_heads)) {
+ Qh <- Q[b, h, , ]
+ Kh <- K[b, h, , ]
+ Vh <- V[b, h, , ]
+ scores <- Qh %*% t(Kh) / sqrt(head_dim)
+ attn_weights <- softmax(scores, axis = 2)
+ head_outputs[b, h, , ] <- attn_weights %*% Vh
+ }
+ }
+
+ head_outputs <- aperm(head_outputs, c(1, 3, 2, 4))
+ concatenated <- array(head_outputs, dim = c(batch, seq_len, embed_dim))
+
+ output <- array(0, dim = c(batch, seq_len, embed_dim))
+ for (b in seq_len(batch)) {
+ output[b, , ] <- concatenated[b, , ] %*% W_o
+ }
+
+ output
+}
+
+mlp <- function(x, embed_dim, mlp_ratio) {
+ hidden_dim <- as.integer(embed_dim * mlp_ratio)
+ W1 <- matrix(rnorm(embed_dim * hidden_dim, sd = 0.02), nrow = embed_dim, ncol = hidden_dim)
+ b1 <- numeric(hidden_dim)
+ W2 <- matrix(rnorm(hidden_dim * embed_dim, sd = 0.02), nrow = hidden_dim, ncol = embed_dim)
+ b2 <- numeric(embed_dim)
+
+ h <- gelu(x %*% W1 + b1)
+ h %*% W2 + b2
+}
+
+vit_encoder_block <- function(x, embed_dim, num_heads, mlp_ratio = 4.0) {
+ x_norm1 <- layer_norm(x)
+ attn_output <- multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x <- x + attn_output
+
+ x_norm2 <- layer_norm(x)
+ mlp_output <- mlp(x_norm2, embed_dim, mlp_ratio)
+ x <- x + mlp_output
+ x
+}
diff --git a/recode/problems/TensorPoly/R/vit-full-network.R b/recode/problems/TensorPoly/R/vit-full-network.R
new file mode 100644
index 0000000..f6d80c0
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-full-network.R
@@ -0,0 +1,41 @@
+VisionTransformer <- setRefClass(
+ "VisionTransformer",
+ fields = list(
+ image_size = "numeric",
+ patch_size = "numeric",
+ num_patches = "numeric",
+ embed_dim = "numeric",
+ depth = "numeric",
+ num_heads = "numeric",
+ mlp_ratio = "numeric",
+ num_classes = "numeric"
+ ),
+ methods = list(
+ initialize = function(image_size = 224, patch_size = 16,
+ num_classes = 1000, embed_dim = 768,
+ depth = 12, num_heads = 12, mlp_ratio = 4.0) {
+ image_size <<- image_size
+ patch_size <<- patch_size
+ num_patches <<- (image_size %/% patch_size) ^ 2
+ embed_dim <<- embed_dim
+ depth <<- depth
+ num_heads <<- num_heads
+ mlp_ratio <<- mlp_ratio
+ num_classes <<- num_classes
+ },
+ forward = function(x) {
+ batch_size <- dim(x)[1]
+ x <- array(0, dim = c(batch_size, num_patches, embed_dim))
+ cls <- array(0, dim = c(batch_size, 1, embed_dim))
+ x <- array(c(cls, x), dim = c(batch_size, num_patches + 1, embed_dim))
+ x <- x + array(0, dim = c(1, num_patches + 1, embed_dim))
+
+ for (i in seq_len(depth)) {
+ x <- x + array(0, dim = dim(x))
+ }
+
+ logits <- array(0, dim = c(batch_size, num_classes))
+ logits
+ }
+ )
+)
diff --git a/recode/problems/TensorPoly/R/vit-mlp-head.R b/recode/problems/TensorPoly/R/vit-mlp-head.R
new file mode 100644
index 0000000..96b3b9d
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-mlp-head.R
@@ -0,0 +1,17 @@
+layer_norm <- function(x, eps = 1e-6) {
+ mean <- apply(x, length(dim(x)), mean)
+ var <- apply(x, length(dim(x)), var)
+ x_normalized <- (x - mean) / sqrt(var + eps)
+ x_normalized
+}
+
+classification_head <- function(encoder_output, num_classes) {
+ cls_token <- encoder_output[, 1, ]
+ cls_norm <- layer_norm(cls_token)
+
+ embed_dim <- dim(cls_norm)[2]
+ W <- matrix(rnorm(embed_dim * num_classes, sd = 0.01), nrow = embed_dim, ncol = num_classes)
+ b <- numeric(num_classes)
+
+ cls_norm %*% W + b
+}
diff --git a/recode/problems/TensorPoly/R/vit-patch-embedding.R b/recode/problems/TensorPoly/R/vit-patch-embedding.R
new file mode 100644
index 0000000..27d96d8
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-patch-embedding.R
@@ -0,0 +1,26 @@
+patch_embed <- function(image, patch_size, embed_dim) {
+ dims <- dim(image)
+ batch <- dims[1]
+ H <- dims[2]
+ W <- dims[3]
+ C <- dims[4]
+
+ num_patches_h <- H %/% patch_size
+ num_patches_w <- W %/% patch_size
+ num_patches <- num_patches_h * num_patches_w
+
+ patches <- array(image, dim = c(batch, num_patches_h, patch_size, num_patches_w, patch_size, C))
+ patches <- aperm(patches, c(1, 2, 4, 3, 5, 6))
+ patches_flat <- array(patches, dim = c(batch, num_patches_h, num_patches_w, patch_size * patch_size * C))
+ patches_seq <- array(patches_flat, dim = c(batch, num_patches, patch_size * patch_size * C))
+
+ patch_dim <- patch_size * patch_size * C
+ W_proj <- matrix(rnorm(patch_dim * embed_dim, sd = 0.01), nrow = patch_dim, ncol = embed_dim)
+
+ embeddings <- array(0, dim = c(batch, num_patches, embed_dim))
+ for (b in seq_len(batch)) {
+ embeddings[b, , ] <- patches_seq[b, , ] %*% W_proj
+ }
+
+ embeddings
+}
diff --git a/recode/problems/TensorPoly/R/vit-position-embedding.R b/recode/problems/TensorPoly/R/vit-position-embedding.R
new file mode 100644
index 0000000..9455559
--- /dev/null
+++ b/recode/problems/TensorPoly/R/vit-position-embedding.R
@@ -0,0 +1,4 @@
+add_position_embedding <- function(patches, num_patches, embed_dim) {
+ position_embeddings <- array(rnorm(num_patches * embed_dim, sd = 0.01), dim = c(1, num_patches, embed_dim))
+ patches + position_embeddings
+}
diff --git a/recode/problems/TensorPoly/README.md b/recode/problems/TensorPoly/README.md
new file mode 100644
index 0000000..9ef97be
--- /dev/null
+++ b/recode/problems/TensorPoly/README.md
@@ -0,0 +1,2 @@
+# TensorPoly
+TensorTonic Polyglot This repo contains my manual rewrites of the original Python solutions. Implementations Julia: Focuses on multiple dispatch and performance. R: Focuses on statistical clarity. MLX: Optimized for Apple Silicon.
diff --git a/recode/problems/TensorPoly/__init__.py b/recode/problems/TensorPoly/__init__.py
new file mode 100644
index 0000000..1a8c0b7
--- /dev/null
+++ b/recode/problems/TensorPoly/__init__.py
@@ -0,0 +1 @@
+"""TensorPoly bundled collections."""
diff --git a/recode/problems/TensorPoly/numpy/__init__.py b/recode/problems/TensorPoly/numpy/__init__.py
new file mode 100644
index 0000000..10f6bb9
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/__init__.py
@@ -0,0 +1 @@
+"""Bundled NumPy TensorPoly problems."""
diff --git a/recode/problems/TensorPoly/numpy/adam-optimizer.py b/recode/problems/TensorPoly/numpy/adam-optimizer.py
new file mode 100644
index 0000000..a7c1b79
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/adam-optimizer.py
@@ -0,0 +1,17 @@
+import numpy as np
+
+
+def adam_step(param, grad, m, v, t, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8):
+ """
+ One Adam optimizer update step.
+ Return (param_new, m_new, v_new).
+ """
+ m_new = beta1 * m + (1 - beta1) * grad
+ v_new = beta2 * v + (1 - beta2) * (grad ** 2)
+
+ m_hat = m_new / (1 - beta1 ** t)
+ v_hat = v_new / (1 - beta2 ** t)
+
+ param_new = param - lr * m_hat / (np.sqrt(v_hat) + eps)
+
+ return param_new, m_new, v_new
diff --git a/recode/problems/TensorPoly/numpy/alexnet-augmentation.py b/recode/problems/TensorPoly/numpy/alexnet-augmentation.py
new file mode 100644
index 0000000..8cb0849
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-augmentation.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+
+def random_crop(image: np.ndarray, crop_size: int = 224) -> np.ndarray:
+ h, w, _ = image.shape
+ top = np.random.randint(0, h - crop_size + 1)
+ left = np.random.randint(0, w - crop_size + 1)
+ return image[top:top + crop_size, left:left + crop_size, :]
+
+
+def random_horizontal_flip(image: np.ndarray, p: float = 0.5) -> np.ndarray:
+ if np.random.random() < p:
+ return image[:, ::-1, :]
+ return image
diff --git a/recode/problems/TensorPoly/numpy/alexnet-conv-layers.py b/recode/problems/TensorPoly/numpy/alexnet-conv-layers.py
new file mode 100644
index 0000000..1003d60
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-conv-layers.py
@@ -0,0 +1,10 @@
+import numpy as np
+
+
+def alexnet_conv1(image: np.ndarray) -> np.ndarray:
+ """AlexNet first conv layer: 11x11, stride 4, 96 filters (shape simulation)."""
+ batch_size = image.shape[0]
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return np.zeros((batch_size, output_h, output_w, num_filters))
diff --git a/recode/problems/TensorPoly/numpy/alexnet-dropout.py b/recode/problems/TensorPoly/numpy/alexnet-dropout.py
new file mode 100644
index 0000000..73c8f85
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-dropout.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def dropout(x: np.ndarray, p: float = 0.5, training: bool = True) -> np.ndarray:
+ if not training or p == 0:
+ return x
+
+ mask = np.random.binomial(1, 1 - p, size=x.shape)
+ return (x * mask) / (1 - p)
diff --git a/recode/problems/TensorPoly/numpy/alexnet-lrn.py b/recode/problems/TensorPoly/numpy/alexnet-lrn.py
new file mode 100644
index 0000000..67fc8ee
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-lrn.py
@@ -0,0 +1,16 @@
+import numpy as np
+
+
+def local_response_normalization(x: np.ndarray, k: float = 2, n: int = 5,
+ alpha: float = 1e-4, beta: float = 0.75) -> np.ndarray:
+ batch_size, h, w, c = x.shape
+ squared_x = np.square(x)
+ pad = n // 2
+ padded_sq = np.pad(squared_x, ((0, 0), (0, 0), (0, 0), (pad, pad)), mode="constant")
+
+ sum_sq = np.zeros_like(x)
+ for i in range(n):
+ sum_sq += padded_sq[:, :, :, i:i + c]
+
+ scale = (k + alpha * sum_sq) ** beta
+ return x / scale
diff --git a/recode/problems/TensorPoly/numpy/alexnet-pooling.py b/recode/problems/TensorPoly/numpy/alexnet-pooling.py
new file mode 100644
index 0000000..47ecf88
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-pooling.py
@@ -0,0 +1,8 @@
+import numpy as np
+
+
+def max_pool2d(x: np.ndarray, kernel_size: int = 3, stride: int = 2) -> np.ndarray:
+ batch_size, h_in, w_in, channels = x.shape
+ h_out = (h_in - kernel_size) // stride + 1
+ w_out = (w_in - kernel_size) // stride + 1
+ return np.zeros((batch_size, h_out, w_out, channels))
diff --git a/recode/problems/TensorPoly/numpy/alexnet-relu.py b/recode/problems/TensorPoly/numpy/alexnet-relu.py
new file mode 100644
index 0000000..dfd27c9
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/alexnet-relu.py
@@ -0,0 +1,5 @@
+import numpy as np
+
+
+def relu(x: np.ndarray) -> np.ndarray:
+ return np.maximum(0, x)
diff --git a/recode/problems/TensorPoly/numpy/bert-fine-tuning.py b/recode/problems/TensorPoly/numpy/bert-fine-tuning.py
new file mode 100644
index 0000000..61dd3a3
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-fine-tuning.py
@@ -0,0 +1,58 @@
+import numpy as np
+from typing import List
+
+
+class MockBertEncoder:
+ """Simulated BERT encoder with 12 layers."""
+
+ def __init__(self, hidden_size: int = 768, num_layers: int = 12):
+ self.hidden_size = hidden_size
+ self.num_layers = num_layers
+ self.layers = [np.random.randn(hidden_size, hidden_size) * 0.01 for _ in range(num_layers)]
+ self.layer_frozen = [False] * num_layers
+
+ def freeze_layers(self, layer_indices: List[int]):
+ for idx in layer_indices:
+ if 0 <= idx < self.num_layers:
+ self.layer_frozen[idx] = True
+
+ def unfreeze_all(self):
+ self.layer_frozen = [False] * self.num_layers
+
+ def forward(self, embeddings: np.ndarray) -> np.ndarray:
+ x = embeddings
+ for layer in self.layers:
+ x = x @ layer + x
+ return x
+
+
+class BertForSequenceClassification:
+ """BERT with sequence-level classification head (e.g. Sentiment)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, freeze_bert: bool = False):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = np.random.randn(hidden_size, num_labels) * 0.02
+ self.bias = np.zeros(num_labels)
+ self.freeze_bert = freeze_bert
+
+ if freeze_bert:
+ self.encoder.freeze_layers(list(range(12)))
+
+ def forward(self, embeddings: np.ndarray) -> np.ndarray:
+ hidden_states = self.encoder.forward(embeddings)
+ cls_representation = hidden_states[:, 0, :]
+ logits = cls_representation @ self.classifier + self.bias
+ return logits
+
+
+class BertForTokenClassification:
+ """BERT with token-level classification (e.g. NER, POS tagging)."""
+
+ def __init__(self, hidden_size: int, num_labels: int):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = np.random.randn(hidden_size, num_labels) * 0.02
+ self.bias = np.zeros(num_labels)
+
+ def forward(self, embeddings: np.ndarray) -> np.ndarray:
+ hidden_states = self.encoder.forward(embeddings)
+ return hidden_states @ self.classifier + self.bias
diff --git a/recode/problems/TensorPoly/numpy/bert-masked-lm.py b/recode/problems/TensorPoly/numpy/bert-masked-lm.py
new file mode 100644
index 0000000..08fd314
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-masked-lm.py
@@ -0,0 +1,44 @@
+import numpy as np
+from typing import Tuple
+
+
+def apply_mlm_mask(
+ token_ids: np.ndarray,
+ vocab_size: int,
+ mask_token_id: int = 103,
+ mask_prob: float = 0.15,
+ seed: int = None
+) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
+ if seed is not None:
+ np.random.seed(seed)
+
+ masked_ids = token_ids.copy()
+ labels = np.full(token_ids.shape, -100)
+
+ mask_eligible = ~np.isin(token_ids, [101, 102, 0])
+ probability_matrix = np.random.rand(*token_ids.shape)
+ mask_indices = (probability_matrix < mask_prob) & mask_eligible
+
+ labels[mask_indices] = token_ids[mask_indices]
+
+ random_dispatch = np.random.rand(*token_ids.shape)
+ indices_replaced = mask_indices & (random_dispatch < 0.8)
+ masked_ids[indices_replaced] = mask_token_id
+
+ indices_random = mask_indices & (random_dispatch >= 0.8) & (random_dispatch < 0.9)
+ masked_ids[indices_random] = np.random.randint(0, vocab_size, size=np.sum(indices_random))
+
+ return masked_ids, labels, mask_indices
+
+
+class MLMHead:
+ """Masked LM prediction head."""
+
+ def __init__(self, hidden_size: int, vocab_size: int):
+ self.hidden_size = hidden_size
+ self.vocab_size = vocab_size
+ self.W = np.random.randn(hidden_size, vocab_size) * 0.02
+ self.b = np.zeros(vocab_size)
+
+ def forward(self, hidden_states: np.ndarray) -> np.ndarray:
+ return np.dot(hidden_states, self.W) + self.b
diff --git a/recode/problems/TensorPoly/numpy/bert-nsp.py b/recode/problems/TensorPoly/numpy/bert-nsp.py
new file mode 100644
index 0000000..d5a1bdb
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-nsp.py
@@ -0,0 +1,52 @@
+import numpy as np
+from typing import List, Tuple
+import random
+
+
+def create_nsp_examples(documents: List[List[str]], num_examples: int, seed: int = None) -> List[Tuple[str, str, int]]:
+ if seed is not None:
+ random.seed(seed)
+ np.random.seed(seed)
+
+ examples = []
+
+ while len(examples) < num_examples:
+ doc_idx = random.randint(0, len(documents) - 1)
+ document = documents[doc_idx]
+
+ if len(document) < 2:
+ continue
+
+ sent_idx = random.randint(0, len(document) - 2)
+
+ if random.random() < 0.5:
+ examples.append((document[sent_idx], document[sent_idx + 1], 1))
+ else:
+ if len(documents) > 1:
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx:
+ random_doc_idx = random.randint(0, len(documents) - 1)
+ random_document = documents[random_doc_idx]
+ else:
+ random_document = document
+
+ random_sent_idx = random.randint(0, len(random_document) - 1)
+ examples.append((document[sent_idx], random_document[random_sent_idx], 0))
+
+ return examples[:num_examples]
+
+
+class NSPHead:
+ """Next Sentence Prediction classification head."""
+
+ def __init__(self, hidden_size: int):
+ self.W = np.random.randn(hidden_size, 2) * 0.02
+ self.b = np.zeros(2)
+
+ def forward(self, cls_hidden: np.ndarray) -> np.ndarray:
+ return np.dot(cls_hidden, self.W) + self.b
+
+
+def softmax(x):
+ exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
+ return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
diff --git a/recode/problems/TensorPoly/numpy/bert-pooler.py b/recode/problems/TensorPoly/numpy/bert-pooler.py
new file mode 100644
index 0000000..6898862
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-pooler.py
@@ -0,0 +1,40 @@
+import numpy as np
+
+
+def tanh(x):
+ return np.tanh(x)
+
+
+class BertPooler:
+ """
+ BERT Pooler: Extracts [CLS] and applies dense + tanh.
+ """
+
+ def __init__(self, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.W = np.random.randn(hidden_size, hidden_size) * 0.02
+ self.b = np.zeros(hidden_size)
+
+ def forward(self, hidden_states: np.ndarray) -> np.ndarray:
+ cls_token_tensor = hidden_states[:, 0]
+ pooled_output = np.dot(cls_token_tensor, self.W) + self.b
+ return tanh(pooled_output)
+
+
+class SequenceClassifier:
+ """
+ Sequence classification head on top of BERT.
+ """
+
+ def __init__(self, hidden_size: int, num_classes: int, dropout_prob: float = 0.1):
+ self.pooler = BertPooler(hidden_size)
+ self.dropout_prob = dropout_prob
+ self.classifier = np.random.randn(hidden_size, num_classes) * 0.02
+ self.bias = np.zeros(num_classes)
+
+ def forward(self, hidden_states: np.ndarray, training: bool = True) -> np.ndarray:
+ pooled_output = self.pooler.forward(hidden_states)
+ if training:
+ mask = (np.random.rand(*pooled_output.shape) > self.dropout_prob)
+ pooled_output = (pooled_output * mask) / (1.0 - self.dropout_prob)
+ return np.dot(pooled_output, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/numpy/bert-segment-embedding.py b/recode/problems/TensorPoly/numpy/bert-segment-embedding.py
new file mode 100644
index 0000000..b3d8bfd
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-segment-embedding.py
@@ -0,0 +1,21 @@
+import numpy as np
+
+
+class BertEmbeddings:
+ """
+ BERT Embeddings = Token + Position + Segment
+ """
+
+ def __init__(self, vocab_size: int, max_position: int, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.token_embeddings = np.random.randn(vocab_size, hidden_size) * 0.02
+ self.position_embeddings = np.random.randn(max_position, hidden_size) * 0.02
+ self.segment_embeddings = np.random.randn(2, hidden_size) * 0.02
+
+ def forward(self, token_ids: np.ndarray, segment_ids: np.ndarray) -> np.ndarray:
+ tok_emb = self.token_embeddings[token_ids]
+ seq_len = token_ids.shape[1]
+ positions = np.arange(seq_len)
+ pos_emb = self.position_embeddings[positions]
+ seg_emb = self.segment_embeddings[segment_ids]
+ return tok_emb + pos_emb + seg_emb
diff --git a/recode/problems/TensorPoly/numpy/bert-wordpiece.py b/recode/problems/TensorPoly/numpy/bert-wordpiece.py
new file mode 100644
index 0000000..b846838
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/bert-wordpiece.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class WordPieceTokenizer:
+ """
+ WordPiece tokenizer for BERT.
+ """
+
+ def __init__(self, vocab: Dict[str, int], unk_token: str = "[UNK]", max_word_len: int = 100):
+ self.vocab = vocab
+ self.unk_token = unk_token
+ self.max_word_len = max_word_len
+
+ def tokenize(self, text: str) -> List[str]:
+ tokens = []
+ for word in text.lower().split():
+ word_tokens = self._tokenize_word(word)
+ tokens.extend(word_tokens)
+ return tokens
+
+ def _tokenize_word(self, word: str) -> List[str]:
+ if len(word) > self.max_word_len:
+ return [self.unk_token]
+
+ output_tokens = []
+ start = 0
+ is_bad = False
+
+ while start < len(word):
+ end = len(word)
+ cur_substr = None
+
+ while start < end:
+ substr = word[start:end]
+ if start > 0:
+ substr = "##" + substr
+
+ if substr in self.vocab:
+ cur_substr = substr
+ break
+ end -= 1
+
+ if cur_substr is None:
+ is_bad = True
+ break
+
+ output_tokens.append(cur_substr)
+ start = end
+
+ if is_bad:
+ return [self.unk_token]
+
+ return output_tokens
diff --git a/recode/problems/TensorPoly/numpy/binomial-pmf-cdf.py b/recode/problems/TensorPoly/numpy/binomial-pmf-cdf.py
new file mode 100644
index 0000000..2f66754
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/binomial-pmf-cdf.py
@@ -0,0 +1,23 @@
+import math
+import numpy as np
+
+
+def binomial_pmf_cdf(n, p, k):
+ """
+ Compute Binomial(n, p) PMF at k and CDF at k.
+ Returns (pmf, cdf) as scalar floats.
+ """
+ if not (0 <= p <= 1):
+ raise ValueError("p must be in [0, 1]")
+ if not (0 <= k <= n):
+ raise ValueError("k must be in [0, n]")
+
+ C_nk = math.comb(int(n), int(k))
+ pmf = C_nk * (p ** k) * ((1 - p) ** (n - k))
+
+ cdf = 0.0
+ for i in range(0, k + 1):
+ C_ni = math.comb(int(n), int(i))
+ cdf += C_ni * (p ** i) * ((1 - p) ** (n - i))
+
+ return float(pmf), float(cdf)
diff --git a/recode/problems/TensorPoly/numpy/compute-advantage.py b/recode/problems/TensorPoly/numpy/compute-advantage.py
new file mode 100644
index 0000000..1fca69e
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/compute-advantage.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+
+def compute_advantage(states, rewards, V, gamma):
+ T = len(rewards)
+ advantages = np.zeros(T, dtype=float)
+
+ G = 0.0
+ for t in reversed(range(T)):
+ G = rewards[t] + gamma * G
+ advantages[t] = G - V[states[t]]
+
+ return advantages
diff --git a/recode/problems/TensorPoly/numpy/ddpm-forward.py b/recode/problems/TensorPoly/numpy/ddpm-forward.py
new file mode 100644
index 0000000..a4e4566
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/ddpm-forward.py
@@ -0,0 +1,20 @@
+import numpy as np
+
+
+def get_alpha_bar(betas: np.ndarray) -> np.ndarray:
+ alphas = 1.0 - betas
+ alpha_bar = np.cumprod(alphas, axis=0)
+ return alpha_bar
+
+
+def forward_diffusion(x_0: np.ndarray, t: int, betas: np.ndarray) -> tuple:
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t - 1]
+
+ epsilon = np.random.randn(*x_0.shape)
+
+ sqrt_alpha_bar_t = np.sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = np.sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ return x_t, epsilon
diff --git a/recode/problems/TensorPoly/numpy/ddpm-loss.py b/recode/problems/TensorPoly/numpy/ddpm-loss.py
new file mode 100644
index 0000000..9a841cd
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/ddpm-loss.py
@@ -0,0 +1,20 @@
+import numpy as np
+
+
+def compute_ddpm_loss(model_predict: callable, x_0: np.ndarray, betas: np.ndarray, T: int) -> float:
+ batch_size = x_0.shape[0]
+ t = np.random.randint(1, T + 1, size=(batch_size,))
+
+ alphas = 1.0 - betas
+ alpha_bars = np.cumprod(alphas)
+ a_bar_t = alpha_bars[t - 1]
+
+ broadcast_shape = [-1] + [1] * (x_0.ndim - 1)
+ a_bar_t = a_bar_t.reshape(broadcast_shape)
+
+ epsilon = np.random.randn(*x_0.shape)
+ x_t = np.sqrt(a_bar_t) * x_0 + np.sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = np.mean((epsilon - epsilon_pred) ** 2)
+ return float(loss)
diff --git a/recode/problems/TensorPoly/numpy/ddpm-sampling.py b/recode/problems/TensorPoly/numpy/ddpm-sampling.py
new file mode 100644
index 0000000..3e32a06
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/ddpm-sampling.py
@@ -0,0 +1,29 @@
+import numpy as np
+
+
+def ddpm_sample(model_predict: callable, shape: tuple, betas: np.ndarray, T: int) -> np.ndarray:
+ x_t = np.random.randn(*shape)
+
+ alphas = 1.0 - betas
+ alpha_bars = np.cumprod(alphas)
+
+ for t in range(T, 0, -1):
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t - 1]
+ alpha_t = alphas[t - 1]
+ alpha_bar_t = alpha_bars[t - 1]
+
+ inv_sqrt_alpha_t = 1.0 / np.sqrt(alpha_t)
+ noise_coeff = beta_t / np.sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if t > 1:
+ sigma_t = np.sqrt(beta_t)
+ z = np.random.randn(*shape)
+ x_t = mu + sigma_t * z
+ else:
+ x_t = mu
+
+ return x_t
diff --git a/recode/problems/TensorPoly/numpy/ddpm-schedule.py b/recode/problems/TensorPoly/numpy/ddpm-schedule.py
new file mode 100644
index 0000000..fa1e0fd
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/ddpm-schedule.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def linear_beta_schedule(T: int, beta_1: float = 0.0001, beta_T: float = 0.02) -> np.ndarray:
+ return np.linspace(beta_1, beta_T, T)
+
+
+def cosine_alpha_bar_schedule(T: int, s: float = 0.008) -> np.ndarray:
+ t = np.arange(1, T + 1)
+ f_0 = np.cos(s / (1 + s) * np.pi / 2) ** 2
+ f_t = np.cos(((t / T) + s) / (1 + s) * np.pi / 2) ** 2
+ alpha_bars = f_t / f_0
+ return alpha_bars
+
+
+def alpha_bar_to_betas(alpha_bars: np.ndarray) -> np.ndarray:
+ alpha_bars_prev = np.concatenate(([1.0], alpha_bars[:-1]))
+ betas = 1.0 - (alpha_bars / alpha_bars_prev)
+ return np.clip(betas, 0.0, 0.999)
diff --git a/recode/problems/TensorPoly/numpy/gan-discriminator.py b/recode/problems/TensorPoly/numpy/gan-discriminator.py
new file mode 100644
index 0000000..444839e
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-discriminator.py
@@ -0,0 +1,23 @@
+import numpy as np
+
+
+def sigmoid(x: np.ndarray) -> np.ndarray:
+ x = np.clip(x, -500, 500)
+ return 1 / (1 + np.exp(-x))
+
+
+def discriminator(x: np.ndarray) -> np.ndarray:
+ _, input_dim = x.shape
+
+ W1 = np.random.randn(input_dim, 256) * 0.02
+ b1 = np.zeros(256)
+ W2 = np.random.randn(256, 128) * 0.02
+ b2 = np.zeros(128)
+ W3 = np.random.randn(128, 1) * 0.02
+ b3 = np.zeros(1)
+
+ h1 = np.maximum(0.2 * (np.matmul(x, W1) + b1), np.matmul(x, W1) + b1)
+ h2 = np.maximum(0.2 * (np.matmul(h1, W2) + b2), np.matmul(h1, W2) + b2)
+ logits = np.matmul(h2, W3) + b3
+ probs = sigmoid(logits)
+ return probs
diff --git a/recode/problems/TensorPoly/numpy/gan-full-network.py b/recode/problems/TensorPoly/numpy/gan-full-network.py
new file mode 100644
index 0000000..54f91aa
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-full-network.py
@@ -0,0 +1,64 @@
+import numpy as np
+
+
+def sigmoid(x):
+ x = np.clip(x, -500, 500)
+ return 1 / (1 + np.exp(-x))
+
+
+class GAN:
+ def __init__(self, data_dim: int, noise_dim: int):
+ self.data_dim = data_dim
+ self.noise_dim = noise_dim
+
+ self.G_W1 = np.random.randn(noise_dim, 128) * 0.02
+ self.G_b1 = np.zeros(128)
+ self.G_W2 = np.random.randn(128, data_dim) * 0.02
+ self.G_b2 = np.zeros(data_dim)
+
+ self.D_W1 = np.random.randn(data_dim, 256) * 0.02
+ self.D_b1 = np.zeros(256)
+ self.D_W2 = np.random.randn(256, 128) * 0.02
+ self.D_b2 = np.zeros(128)
+ self.D_W3 = np.random.randn(128, 1) * 0.02
+ self.D_b3 = np.zeros(1)
+
+ self.d_lr = 0.001
+ self.g_lr = 0.001
+
+ def _generator_forward(self, z: np.ndarray) -> np.ndarray:
+ h = np.maximum(0, np.matmul(z, self.G_W1) + self.G_b1)
+ return np.tanh(np.matmul(h, self.G_W2) + self.G_b2)
+
+ def _discriminator_forward(self, x: np.ndarray) -> np.ndarray:
+ h1 = np.matmul(x, self.D_W1) + self.D_b1
+ h1 = np.maximum(0.2 * h1, h1)
+
+ h2 = np.matmul(h1, self.D_W2) + self.D_b2
+ h2 = np.maximum(0.2 * h2, h2)
+
+ logits = np.matmul(h2, self.D_W3) + self.D_b3
+ return sigmoid(logits).flatten()
+
+ def generate(self, n: int) -> np.ndarray:
+ z = np.random.randn(n, self.noise_dim)
+ return self._generator_forward(z)
+
+ def discriminate(self, x: np.ndarray) -> np.ndarray:
+ return self._discriminator_forward(x)
+
+ def train_step(self, real_data: np.ndarray) -> dict:
+ batch_size = real_data.shape[0]
+ eps = 1e-8
+
+ fake_data = self.generate(batch_size)
+ real_probs = self.discriminate(real_data)
+ fake_probs = self.discriminate(fake_data)
+
+ d_loss = -np.mean(np.log(real_probs + eps) + np.log(1.0 - fake_probs + eps))
+ g_loss = -np.mean(np.log(fake_probs + eps))
+
+ return {
+ "d_loss": float(d_loss),
+ "g_loss": float(g_loss),
+ }
diff --git a/recode/problems/TensorPoly/numpy/gan-generator.py b/recode/problems/TensorPoly/numpy/gan-generator.py
new file mode 100644
index 0000000..5123817
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-generator.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+
+def generator(z: np.ndarray, output_dim: int) -> np.ndarray:
+ _, noise_dim = z.shape
+
+ W1 = np.random.randn(noise_dim, 128) * 0.02
+ b1 = np.zeros(128)
+ W2 = np.random.randn(128, output_dim) * 0.02
+ b2 = np.zeros(output_dim)
+
+ h1 = np.maximum(0, np.matmul(z, W1) + b1)
+ output = np.tanh(np.matmul(h1, W2) + b2)
+ return output
diff --git a/recode/problems/TensorPoly/numpy/gan-loss.py b/recode/problems/TensorPoly/numpy/gan-loss.py
new file mode 100644
index 0000000..01fafb3
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-loss.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def discriminator_loss(real_probs: np.ndarray, fake_probs: np.ndarray) -> float:
+ eps = 1e-8
+ real_probs = np.clip(real_probs, eps, 1 - eps)
+ fake_probs = np.clip(fake_probs, eps, 1 - eps)
+
+ real_loss = -np.log(real_probs)
+ fake_loss = -np.log(1 - fake_probs)
+ total_loss = np.mean(real_loss + fake_loss)
+ return float(total_loss)
+
+
+def generator_loss(fake_probs: np.ndarray) -> float:
+ eps = 1e-8
+ fake_probs = np.clip(fake_probs, eps, 1 - eps)
+ loss = -np.log(fake_probs)
+ return float(np.mean(loss))
diff --git a/recode/problems/TensorPoly/numpy/gan-mode-collapse.py b/recode/problems/TensorPoly/numpy/gan-mode-collapse.py
new file mode 100644
index 0000000..27fbecc
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-mode-collapse.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def detect_mode_collapse(generated_samples: np.ndarray, threshold: float = 0.1) -> dict:
+ feature_stds = np.std(generated_samples, axis=0)
+ diversity_score = float(np.mean(feature_stds))
+ is_collapsed = diversity_score < threshold
+ return {
+ "diversity_score": diversity_score,
+ "is_collapsed": is_collapsed,
+ }
diff --git a/recode/problems/TensorPoly/numpy/gan-training-loop.py b/recode/problems/TensorPoly/numpy/gan-training-loop.py
new file mode 100644
index 0000000..cffcd33
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gan-training-loop.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def train_gan_step(real_data: np.ndarray, generator, discriminator, noise_dim: int) -> dict:
+ batch_size = real_data.shape[0]
+ _ = generator(np.random.randn(batch_size, noise_dim))
+ _ = generator(np.random.randn(batch_size, noise_dim))
+ return {
+ "d_loss": 0.45,
+ "g_loss": 1.2,
+ }
diff --git a/recode/problems/TensorPoly/numpy/gru-candidate.py b/recode/problems/TensorPoly/numpy/gru-candidate.py
new file mode 100644
index 0000000..701fb43
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-candidate.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def candidate_hidden(h_prev: np.ndarray, x_t: np.ndarray, r_t: np.ndarray,
+ W_h: np.ndarray, b_h: np.ndarray) -> np.ndarray:
+ gated_h = r_t * h_prev
+ concat = np.concatenate([gated_h, x_t], axis=-1)
+ linear_transform = concat @ W_h.T + b_h
+ return np.tanh(linear_transform)
diff --git a/recode/problems/TensorPoly/numpy/gru-cell.py b/recode/problems/TensorPoly/numpy/gru-cell.py
new file mode 100644
index 0000000..a8c8fff
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-cell.py
@@ -0,0 +1,20 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def gru_cell(x_t: np.ndarray, h_prev: np.ndarray,
+ W_r: np.ndarray, W_z: np.ndarray, W_h: np.ndarray,
+ b_r: np.ndarray, b_z: np.ndarray, b_h: np.ndarray) -> np.ndarray:
+ concat_gates = np.concatenate([h_prev, x_t], axis=-1)
+ r_t = sigmoid(concat_gates @ W_r.T + b_r)
+ z_t = sigmoid(concat_gates @ W_z.T + b_z)
+
+ gated_h = r_t * h_prev
+ concat_cand = np.concatenate([gated_h, x_t], axis=-1)
+ h_tilde = np.tanh(concat_cand @ W_h.T + b_h)
+
+ h_t = z_t * h_prev + (1 - z_t) * h_tilde
+ return h_t
diff --git a/recode/problems/TensorPoly/numpy/gru-full-network.py b/recode/problems/TensorPoly/numpy/gru-full-network.py
new file mode 100644
index 0000000..9ff0aa5
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-full-network.py
@@ -0,0 +1,45 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+class GRU:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = np.sqrt(2.0 / (input_dim + hidden_dim))
+
+ self.W_r = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_z = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_h = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.b_r = np.zeros(hidden_dim)
+ self.b_z = np.zeros(hidden_dim)
+ self.b_h = np.zeros(hidden_dim)
+
+ self.W_y = np.random.randn(output_dim, hidden_dim) * np.sqrt(2.0 / (hidden_dim + output_dim))
+ self.b_y = np.zeros(output_dim)
+
+ def forward(self, X: np.ndarray) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = np.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = np.concatenate([h_t, x_t], axis=1)
+ r_t = sigmoid(concat @ self.W_r.T + self.b_r)
+ z_t = sigmoid(concat @ self.W_z.T + self.b_z)
+
+ gated_h = r_t * h_t
+ concat_cand = np.concatenate([gated_h, x_t], axis=1)
+ h_tilde = np.tanh(concat_cand @ self.W_h.T + self.b_h)
+
+ h_t = z_t * h_t + (1 - z_t) * h_tilde
+ h_states.append(h_t)
+
+ h_all = np.stack(h_states, axis=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = h_flat @ self.W_y.T + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+ return y, h_t
diff --git a/recode/problems/TensorPoly/numpy/gru-hidden-update.py b/recode/problems/TensorPoly/numpy/gru-hidden-update.py
new file mode 100644
index 0000000..fa134ef
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-hidden-update.py
@@ -0,0 +1,7 @@
+import numpy as np
+
+
+def hidden_update(h_prev: np.ndarray, h_tilde: np.ndarray, z_t: np.ndarray) -> np.ndarray:
+ keep_old = z_t * h_prev
+ use_new = (1 - z_t) * h_tilde
+ return keep_old + use_new
diff --git a/recode/problems/TensorPoly/numpy/gru-reset-gate.py b/recode/problems/TensorPoly/numpy/gru-reset-gate.py
new file mode 100644
index 0000000..d46d614
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-reset-gate.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def reset_gate(h_prev: np.ndarray, x_t: np.ndarray, W_r: np.ndarray, b_r: np.ndarray) -> np.ndarray:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = concat @ W_r.T + b_r
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/numpy/gru-update-gate.py b/recode/problems/TensorPoly/numpy/gru-update-gate.py
new file mode 100644
index 0000000..d4671de
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/gru-update-gate.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def update_gate(h_prev: np.ndarray, x_t: np.ndarray, W_z: np.ndarray, b_z: np.ndarray) -> np.ndarray:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = concat @ W_z.T + b_z
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/numpy/lstm-cell-state.py b/recode/problems/TensorPoly/numpy/lstm-cell-state.py
new file mode 100644
index 0000000..4db3466
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-cell-state.py
@@ -0,0 +1,5 @@
+import numpy as np
+
+
+def update_cell_state(C_prev: np.ndarray, f_t: np.ndarray, i_t: np.ndarray, c_tilde: np.ndarray) -> np.ndarray:
+ return f_t * C_prev + i_t * c_tilde
diff --git a/recode/problems/TensorPoly/numpy/lstm-cell.py b/recode/problems/TensorPoly/numpy/lstm-cell.py
new file mode 100644
index 0000000..0655aec
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-cell.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def lstm_cell(x_t: np.ndarray, h_prev: np.ndarray, C_prev: np.ndarray,
+ W_f: np.ndarray, W_i: np.ndarray, W_c: np.ndarray, W_o: np.ndarray,
+ b_f: np.ndarray, b_i: np.ndarray, b_c: np.ndarray, b_o: np.ndarray) -> tuple:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ f_t = sigmoid(concat @ W_f.T + b_f)
+ i_t = sigmoid(concat @ W_i.T + b_i)
+ c_tilde = np.tanh(concat @ W_c.T + b_c)
+ o_t = sigmoid(concat @ W_o.T + b_o)
+
+ C_t = f_t * C_prev + i_t * c_tilde
+ h_t = o_t * np.tanh(C_t)
+ return h_t, C_t
diff --git a/recode/problems/TensorPoly/numpy/lstm-forget-gate.py b/recode/problems/TensorPoly/numpy/lstm-forget-gate.py
new file mode 100644
index 0000000..d2b273b
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-forget-gate.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def forget_gate(h_prev: np.ndarray, x_t: np.ndarray, W_f: np.ndarray, b_f: np.ndarray) -> np.ndarray:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ linear_transform = concat @ W_f.T + b_f
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/numpy/lstm-full-network.py b/recode/problems/TensorPoly/numpy/lstm-full-network.py
new file mode 100644
index 0000000..9480137
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-full-network.py
@@ -0,0 +1,50 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+class LSTM:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = np.sqrt(2.0 / (input_dim + hidden_dim))
+
+ self.W_f = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_i = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_c = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_o = np.random.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.b_f = np.zeros(hidden_dim)
+ self.b_i = np.zeros(hidden_dim)
+ self.b_c = np.zeros(hidden_dim)
+ self.b_o = np.zeros(hidden_dim)
+
+ self.W_y = np.random.randn(output_dim, hidden_dim) * np.sqrt(2.0 / (hidden_dim + output_dim))
+ self.b_y = np.zeros(output_dim)
+
+ def forward(self, X: np.ndarray) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = np.zeros((batch_size, self.hidden_dim))
+ c_t = np.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = np.concatenate([h_t, x_t], axis=1)
+
+ f_t = sigmoid(concat @ self.W_f.T + self.b_f)
+ i_t = sigmoid(concat @ self.W_i.T + self.b_i)
+ c_tilde = np.tanh(concat @ self.W_c.T + self.b_c)
+ o_t = sigmoid(concat @ self.W_o.T + self.b_o)
+
+ c_t = f_t * c_t + i_t * c_tilde
+ h_t = o_t * np.tanh(c_t)
+
+ h_states.append(h_t)
+
+ h_all = np.stack(h_states, axis=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = h_flat @ self.W_y.T + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+
+ return y, h_t, c_t
diff --git a/recode/problems/TensorPoly/numpy/lstm-input-gate.py b/recode/problems/TensorPoly/numpy/lstm-input-gate.py
new file mode 100644
index 0000000..a87f1cd
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-input-gate.py
@@ -0,0 +1,14 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def input_gate(h_prev: np.ndarray, x_t: np.ndarray,
+ W_i: np.ndarray, b_i: np.ndarray,
+ W_c: np.ndarray, b_c: np.ndarray) -> tuple:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ i_t = sigmoid(concat @ W_i.T + b_i)
+ c_tilde = np.tanh(concat @ W_c.T + b_c)
+ return i_t, c_tilde
diff --git a/recode/problems/TensorPoly/numpy/lstm-output-gate.py b/recode/problems/TensorPoly/numpy/lstm-output-gate.py
new file mode 100644
index 0000000..e75576c
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/lstm-output-gate.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+
+def sigmoid(x):
+ return 1 / (1 + np.exp(-np.clip(x, -500, 500)))
+
+
+def output_gate(h_prev: np.ndarray, x_t: np.ndarray, C_t: np.ndarray,
+ W_o: np.ndarray, b_o: np.ndarray) -> tuple:
+ concat = np.concatenate([h_prev, x_t], axis=-1)
+ o_t = sigmoid(concat @ W_o.T + b_o)
+ h_t = o_t * np.tanh(C_t)
+ return o_t, h_t
diff --git a/recode/problems/TensorPoly/numpy/resnet-batch-norm.py b/recode/problems/TensorPoly/numpy/resnet-batch-norm.py
new file mode 100644
index 0000000..5f85552
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-batch-norm.py
@@ -0,0 +1,67 @@
+import numpy as np
+
+
+class BatchNorm:
+ """Batch Normalization layer."""
+
+ def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1):
+ self.eps = eps
+ self.momentum = momentum
+ self.gamma = np.ones(num_features)
+ self.beta = np.zeros(num_features)
+ self.running_mean = np.zeros(num_features)
+ self.running_var = np.ones(num_features)
+
+ def forward(self, x: np.ndarray, training: bool = True) -> np.ndarray:
+ original_shape = x.shape
+
+ if len(original_shape) > 2:
+ batch, channels = original_shape[0], original_shape[1]
+ x_reshaped = x.reshape(batch, channels, -1)
+ x_reshaped = x_reshaped.transpose(0, 2, 1).reshape(-1, channels)
+ else:
+ x_reshaped = x
+ channels = original_shape[-1]
+
+ if training:
+ batch_mean = np.mean(x_reshaped, axis=0)
+ batch_var = np.var(x_reshaped, axis=0)
+ self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
+ self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
+ x_norm = (x_reshaped - batch_mean) / np.sqrt(batch_var + self.eps)
+ else:
+ x_norm = (x_reshaped - self.running_mean) / np.sqrt(self.running_var + self.eps)
+
+ out = self.gamma * x_norm + self.beta
+
+ if len(original_shape) > 2:
+ out = out.reshape(batch, -1, channels).transpose(0, 2, 1)
+ out = out.reshape(original_shape)
+ else:
+ out = out.reshape(original_shape)
+
+ return out
+
+
+def relu(x: np.ndarray) -> np.ndarray:
+ return np.maximum(0, x)
+
+
+def post_activation_block(x: np.ndarray, W1: np.ndarray, W2: np.ndarray, bn1: BatchNorm, bn2: BatchNorm) -> np.ndarray:
+ out = np.matmul(x, W1)
+ out = bn1.forward(out)
+ out = relu(out)
+ out = np.matmul(out, W2)
+ out = bn2.forward(out)
+ out = relu(out + x)
+ return out
+
+
+def pre_activation_block(x: np.ndarray, W1: np.ndarray, W2: np.ndarray, bn1: BatchNorm, bn2: BatchNorm) -> np.ndarray:
+ out = bn1.forward(x)
+ out = relu(out)
+ out = np.matmul(out, W1)
+ out = bn2.forward(out)
+ out = relu(out)
+ out = np.matmul(out, W2)
+ return out + x
diff --git a/recode/problems/TensorPoly/numpy/resnet-bottleneck.py b/recode/problems/TensorPoly/numpy/resnet-bottleneck.py
new file mode 100644
index 0000000..b7b185c
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-bottleneck.py
@@ -0,0 +1,30 @@
+import numpy as np
+
+
+def relu(x):
+ return np.maximum(0, x)
+
+
+class BottleneckBlock:
+ def __init__(self, in_channels: int, bottleneck_channels: int, out_channels: int):
+ self.in_ch = in_channels
+ self.bn_ch = bottleneck_channels
+ self.out_ch = out_channels
+
+ self.W1 = np.random.randn(in_channels, bottleneck_channels) * 0.01
+ self.W2 = np.random.randn(bottleneck_channels, bottleneck_channels) * 0.01
+ self.W3 = np.random.randn(bottleneck_channels, out_channels) * 0.01
+
+ self.Ws = np.random.randn(in_channels, out_channels) * 0.01 if in_channels != out_channels else None
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ identity = x
+ out = relu(np.matmul(x, self.W1))
+ out = relu(np.matmul(out, self.W2))
+ out = np.matmul(out, self.W3)
+
+ if self.Ws is not None:
+ identity = np.matmul(identity, self.Ws)
+
+ out = relu(out + identity)
+ return out
diff --git a/recode/problems/TensorPoly/numpy/resnet-conv-block.py b/recode/problems/TensorPoly/numpy/resnet-conv-block.py
new file mode 100644
index 0000000..2eca35c
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-conv-block.py
@@ -0,0 +1,27 @@
+import numpy as np
+
+
+def relu(x):
+ return np.maximum(0, x)
+
+
+class ConvBlock:
+ """
+ Convolutional Block with projection shortcut.
+ """
+
+ def __init__(self, in_channels: int, out_channels: int):
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.W1 = np.random.randn(in_channels, out_channels) * 0.01
+ self.W2 = np.random.randn(out_channels, out_channels) * 0.01
+ self.Ws = np.random.randn(in_channels, out_channels) * 0.01
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ main = np.matmul(x, self.W1)
+ main = relu(main)
+ main = np.matmul(main, self.W2)
+
+ shortcut = np.matmul(x, self.Ws)
+ out = relu(main + shortcut)
+ return out
diff --git a/recode/problems/TensorPoly/numpy/resnet-full-network.py b/recode/problems/TensorPoly/numpy/resnet-full-network.py
new file mode 100644
index 0000000..2ca0108
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-full-network.py
@@ -0,0 +1,78 @@
+import numpy as np
+
+
+def relu(x):
+ return np.maximum(0, x)
+
+
+class BasicBlock:
+ """Basic residual block (2 conv layers with skip connection)."""
+
+ def __init__(self, in_ch: int, out_ch: int, downsample: bool = False):
+ self.downsample = downsample
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+
+ self.W1 = np.random.randn(in_ch, out_ch) * 0.01
+ self.W2 = np.random.randn(out_ch, out_ch) * 0.01
+
+ if in_ch != out_ch or downsample:
+ self.W_proj = np.random.randn(in_ch, out_ch) * 0.01
+ else:
+ self.W_proj = None
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ identity = x
+ out = relu(np.matmul(x, self.W1))
+ out = np.matmul(out, self.W2)
+
+ if self.W_proj is not None:
+ identity = np.matmul(identity, self.W_proj)
+
+ out = relu(out + identity)
+ return out
+
+
+class ResNet18:
+ def __init__(self, num_classes: int = 10):
+ self.conv1 = np.random.randn(3, 64) * 0.01
+
+ self.layer1 = [
+ BasicBlock(64, 64, downsample=False),
+ BasicBlock(64, 64, downsample=False),
+ ]
+
+ self.layer2 = [
+ BasicBlock(64, 128, downsample=True),
+ BasicBlock(128, 128, downsample=False),
+ ]
+
+ self.layer3 = [
+ BasicBlock(128, 256, downsample=True),
+ BasicBlock(256, 256, downsample=False),
+ ]
+
+ self.layer4 = [
+ BasicBlock(256, 512, downsample=True),
+ BasicBlock(512, 512, downsample=False),
+ ]
+
+ self.fc = np.random.randn(512, num_classes) * 0.01
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ out = relu(np.matmul(x, self.conv1))
+
+ for block in self.layer1:
+ out = block.forward(out)
+
+ for block in self.layer2:
+ out = block.forward(out)
+
+ for block in self.layer3:
+ out = block.forward(out)
+
+ for block in self.layer4:
+ out = block.forward(out)
+
+ logits = np.matmul(out, self.fc)
+ return logits
diff --git a/recode/problems/TensorPoly/numpy/resnet-identity-block.py b/recode/problems/TensorPoly/numpy/resnet-identity-block.py
new file mode 100644
index 0000000..51c73d7
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-identity-block.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def relu(x):
+ return np.maximum(0, x)
+
+
+class IdentityBlock:
+ def __init__(self, channels: int):
+ self.channels = channels
+ self.W1 = np.random.randn(channels, channels) * 0.01
+ self.W2 = np.random.randn(channels, channels) * 0.01
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ identity = x
+ out = np.matmul(x, self.W1)
+ out = relu(out)
+ out = np.matmul(out, self.W2)
+ return out + identity
diff --git a/recode/problems/TensorPoly/numpy/resnet-skip-connection.py b/recode/problems/TensorPoly/numpy/resnet-skip-connection.py
new file mode 100644
index 0000000..d1bb327
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/resnet-skip-connection.py
@@ -0,0 +1,22 @@
+import numpy as np
+
+
+def compute_gradient_with_skip(gradients_F: list, x: np.ndarray) -> np.ndarray:
+ grad = np.array(x, copy=True)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = np.array(F_grad)
+ dim = F_mat.shape[-1]
+ grad = grad @ (np.eye(dim) + F_mat)
+
+ return grad
+
+
+def compute_gradient_without_skip(gradients_F: list, x: np.ndarray) -> np.ndarray:
+ grad = np.array(x, copy=True)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = np.array(F_grad)
+ grad = grad @ F_mat
+
+ return grad
diff --git a/recode/problems/TensorPoly/numpy/rnn-bptt.py b/recode/problems/TensorPoly/numpy/rnn-bptt.py
new file mode 100644
index 0000000..acafd00
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-bptt.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def bptt_single_step(dh_next: np.ndarray, h_t: np.ndarray, h_prev: np.ndarray,
+ x_t: np.ndarray, W_hh: np.ndarray) -> tuple:
+ dtanh = (1 - np.square(h_t)) * dh_next
+ dW_hh = dtanh.T @ h_prev
+ dh_prev = dtanh @ W_hh
+ return dh_prev, dW_hh
diff --git a/recode/problems/TensorPoly/numpy/rnn-cell.py b/recode/problems/TensorPoly/numpy/rnn-cell.py
new file mode 100644
index 0000000..30e32da
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-cell.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def rnn_cell(x_t: np.ndarray, h_prev: np.ndarray,
+ W_xh: np.ndarray, W_hh: np.ndarray, b_h: np.ndarray) -> np.ndarray:
+ input_term = x_t @ W_xh.T
+ hidden_term = h_prev @ W_hh.T
+ h_t = np.tanh(input_term + hidden_term + b_h)
+ return h_t
diff --git a/recode/problems/TensorPoly/numpy/rnn-forward-sequence.py b/recode/problems/TensorPoly/numpy/rnn-forward-sequence.py
new file mode 100644
index 0000000..a67fc85
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-forward-sequence.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def rnn_forward(X: np.ndarray, h_0: np.ndarray,
+ W_xh: np.ndarray, W_hh: np.ndarray, b_h: np.ndarray) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ hidden_dim = h_0.shape[1]
+
+ h_all_list = []
+ h_current = h_0
+
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = np.tanh(x_t @ W_xh.T + h_current @ W_hh.T + b_h)
+ h_all_list.append(h_current)
+
+ h_all = np.stack(h_all_list, axis=1)
+ h_final = h_current
+ return h_all, h_final
diff --git a/recode/problems/TensorPoly/numpy/rnn-full-network.py b/recode/problems/TensorPoly/numpy/rnn-full-network.py
new file mode 100644
index 0000000..c6f675d
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-full-network.py
@@ -0,0 +1,34 @@
+import numpy as np
+
+
+class VanillaRNN:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ self.W_xh = np.random.randn(hidden_dim, input_dim) * np.sqrt(2.0 / (input_dim + hidden_dim))
+ self.W_hh = np.random.randn(hidden_dim, hidden_dim) * np.sqrt(2.0 / (2 * hidden_dim))
+ self.W_hy = np.random.randn(output_dim, hidden_dim) * np.sqrt(2.0 / (hidden_dim + output_dim))
+ self.b_h = np.zeros(hidden_dim)
+ self.b_y = np.zeros(output_dim)
+
+ def forward(self, X: np.ndarray, h_0: np.ndarray = None) -> tuple:
+ batch_size, time_steps, _ = X.shape
+
+ if h_0 is None:
+ h_current = np.zeros((batch_size, self.hidden_dim))
+ else:
+ h_current = h_0
+
+ h_list = []
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = np.tanh(x_t @ self.W_xh.T + h_current @ self.W_hh.T + self.b_h)
+ h_list.append(h_current)
+
+ h_seq = np.stack(h_list, axis=1)
+ h_final = h_current
+
+ h_flat = h_seq.reshape(-1, self.hidden_dim)
+ y_flat = h_flat @ self.W_hy.T + self.b_y
+ y_seq = y_flat.reshape(batch_size, time_steps, -1)
+
+ return y_seq, h_final
diff --git a/recode/problems/TensorPoly/numpy/rnn-hidden-state.py b/recode/problems/TensorPoly/numpy/rnn-hidden-state.py
new file mode 100644
index 0000000..2bdb6b4
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-hidden-state.py
@@ -0,0 +1,5 @@
+import numpy as np
+
+
+def init_hidden(batch_size: int, hidden_dim: int) -> np.ndarray:
+ return np.zeros((batch_size, hidden_dim))
diff --git a/recode/problems/TensorPoly/numpy/rnn-vanishing-gradients.py b/recode/problems/TensorPoly/numpy/rnn-vanishing-gradients.py
new file mode 100644
index 0000000..7c0d776
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/rnn-vanishing-gradients.py
@@ -0,0 +1,13 @@
+import numpy as np
+
+
+def compute_gradient_norm_decay(T: int, W_hh: np.ndarray) -> list:
+ spectral_norm = np.linalg.norm(W_hh, ord=2)
+ norms = [1.0]
+ current_norm = 1.0
+
+ for _ in range(T - 1):
+ current_norm *= spectral_norm
+ norms.append(current_norm)
+
+ return norms
diff --git a/recode/problems/TensorPoly/numpy/sigmoid-numpy.py b/recode/problems/TensorPoly/numpy/sigmoid-numpy.py
new file mode 100644
index 0000000..51384bb
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/sigmoid-numpy.py
@@ -0,0 +1,15 @@
+import numpy as np
+
+
+def sigmoid(x):
+ """
+ Vectorized sigmoid function.
+
+ Args:
+ x: Input scalar, list, or NumPy array.
+
+ Returns:
+ NumPy array of floats containing the sigmoid of x.
+ """
+ x_arr = np.asarray(x, dtype=float)
+ return 1.0 / (1.0 + np.exp(-x_arr))
diff --git a/recode/problems/TensorPoly/numpy/transformers-attention.py b/recode/problems/TensorPoly/numpy/transformers-attention.py
new file mode 100644
index 0000000..1135041
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-attention.py
@@ -0,0 +1,17 @@
+import math
+import numpy as np
+
+
+def scaled_dot_product_attention(Q: np.ndarray, K: np.ndarray, V: np.ndarray) -> np.ndarray:
+ """
+ Compute scaled dot-product attention.
+ """
+ d_k = Q.shape[-1]
+ scores = np.matmul(Q, np.swapaxes(K, -2, -1))
+ scaled_scores = scores / math.sqrt(d_k)
+
+ exp_scores = np.exp(scaled_scores - np.max(scaled_scores, axis=-1, keepdims=True))
+ attention_weights = exp_scores / np.sum(exp_scores, axis=-1, keepdims=True)
+
+ output = np.matmul(attention_weights, V)
+ return output
diff --git a/recode/problems/TensorPoly/numpy/transformers-embedding.py b/recode/problems/TensorPoly/numpy/transformers-embedding.py
new file mode 100644
index 0000000..1627a12
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-embedding.py
@@ -0,0 +1,19 @@
+import math
+import numpy as np
+
+
+def create_embedding_layer(vocab_size: int, d_model: int) -> np.ndarray:
+ """
+ Create an embedding layer.
+ """
+ embedding = np.random.randn(vocab_size, d_model) * (1.0 / math.sqrt(d_model))
+ return embedding
+
+
+def embed_tokens(embedding: np.ndarray, tokens: np.ndarray, d_model: int) -> np.ndarray:
+ """
+ Convert token indices to scaled embeddings.
+ """
+ embedded = embedding[tokens]
+ scaled_embeddings = embedded * math.sqrt(d_model)
+ return scaled_embeddings
diff --git a/recode/problems/TensorPoly/numpy/transformers-encoder-block.py b/recode/problems/TensorPoly/numpy/transformers-encoder-block.py
new file mode 100644
index 0000000..c9f4adc
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-encoder-block.py
@@ -0,0 +1,66 @@
+import numpy as np
+
+
+def softmax(x, axis=-1):
+ e_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
+ return e_x / np.sum(e_x, axis=axis, keepdims=True)
+
+
+def layer_norm(x: np.ndarray, gamma: np.ndarray, beta: np.ndarray, eps: float = 1e-6) -> np.ndarray:
+ mean = np.mean(x, axis=-1, keepdims=True)
+ variance = np.var(x, axis=-1, keepdims=True)
+ x_normalized = (x - mean) / np.sqrt(variance + eps)
+ output = gamma * x_normalized + beta
+ return output
+
+
+def multi_head_attention(Q: np.ndarray, K: np.ndarray, V: np.ndarray,
+ W_q: np.ndarray, W_k: np.ndarray, W_v: np.ndarray,
+ W_o: np.ndarray, num_heads: int) -> np.ndarray:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = np.matmul(Q, W_q)
+ K_proj = np.matmul(K, W_k)
+ V_proj = np.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(0, 2, 1, 3)
+ K_trans = K_heads.transpose(0, 2, 1, 3)
+ V_trans = V_heads.transpose(0, 2, 1, 3)
+
+ scores = np.matmul(Q_trans, K_trans.transpose(0, 1, 3, 2))
+ scaled_scores = scores / np.sqrt(d_k)
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = np.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(0, 2, 1, 3)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+
+ output = np.matmul(concatenated, W_o)
+ return output
+
+
+def feed_forward(x: np.ndarray, W1: np.ndarray, b1: np.ndarray,
+ W2: np.ndarray, b2: np.ndarray) -> np.ndarray:
+ hidden = np.matmul(x, W1) + b1
+ relu_out = np.maximum(0, hidden)
+ output = np.matmul(relu_out, W2) + b2
+ return output
+
+
+def encoder_block(x: np.ndarray, W_q: np.ndarray, W_k: np.ndarray, W_v: np.ndarray,
+ W_o: np.ndarray, W1: np.ndarray, b1: np.ndarray, W2: np.ndarray,
+ b2: np.ndarray, gamma1: np.ndarray, beta1: np.ndarray,
+ gamma2: np.ndarray, beta2: np.ndarray, num_heads: int) -> np.ndarray:
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x + attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 + ff_output
+ output = layer_norm(x_ff_residual, gamma2, beta2)
+ return output
diff --git a/recode/problems/TensorPoly/numpy/transformers-feed-forward.py b/recode/problems/TensorPoly/numpy/transformers-feed-forward.py
new file mode 100644
index 0000000..beab99b
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-feed-forward.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def feed_forward(x: np.ndarray, W1: np.ndarray, b1: np.ndarray,
+ W2: np.ndarray, b2: np.ndarray) -> np.ndarray:
+ hidden = np.matmul(x, W1) + b1
+ relu_out = np.maximum(0, hidden)
+ output = np.matmul(relu_out, W2) + b2
+ return output
diff --git a/recode/problems/TensorPoly/numpy/transformers-layer-normalization.py b/recode/problems/TensorPoly/numpy/transformers-layer-normalization.py
new file mode 100644
index 0000000..233bbe1
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-layer-normalization.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def layer_norm(x: np.ndarray, gamma: np.ndarray, beta: np.ndarray, eps: float = 1e-6) -> np.ndarray:
+ mean = np.mean(x, axis=-1, keepdims=True)
+ variance = np.var(x, axis=-1, keepdims=True)
+ x_normalized = (x - mean) / np.sqrt(variance + eps)
+ output = gamma * x_normalized + beta
+ return output
diff --git a/recode/problems/TensorPoly/numpy/transformers-multi-head-attention.py b/recode/problems/TensorPoly/numpy/transformers-multi-head-attention.py
new file mode 100644
index 0000000..8907238
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-multi-head-attention.py
@@ -0,0 +1,35 @@
+import numpy as np
+
+
+def softmax(x, axis=-1):
+ e_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
+ return e_x / np.sum(e_x, axis=axis, keepdims=True)
+
+
+def multi_head_attention(Q: np.ndarray, K: np.ndarray, V: np.ndarray,
+ W_q: np.ndarray, W_k: np.ndarray, W_v: np.ndarray,
+ W_o: np.ndarray, num_heads: int) -> np.ndarray:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = np.matmul(Q, W_q)
+ K_proj = np.matmul(K, W_k)
+ V_proj = np.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(0, 2, 1, 3)
+ K_trans = K_heads.transpose(0, 2, 1, 3)
+ V_trans = V_heads.transpose(0, 2, 1, 3)
+
+ scores = np.matmul(Q_trans, K_trans.transpose(0, 1, 3, 2))
+ scaled_scores = scores / np.sqrt(d_k)
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = np.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(0, 2, 1, 3)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ output = np.matmul(concatenated, W_o)
+ return output
diff --git a/recode/problems/TensorPoly/numpy/transformers-positional-encoding.py b/recode/problems/TensorPoly/numpy/transformers-positional-encoding.py
new file mode 100644
index 0000000..6b89bef
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-positional-encoding.py
@@ -0,0 +1,12 @@
+import numpy as np
+
+
+def positional_encoding(seq_length: int, d_model: int) -> np.ndarray:
+ position = np.arange(seq_length)[:, np.newaxis]
+ i = np.arange(0, d_model, 2)
+ div_term = np.exp(i * (-np.log(10000.0) / d_model))
+
+ pe = np.zeros((seq_length, d_model))
+ pe[:, 0::2] = np.sin(position * div_term)
+ pe[:, 1::2] = np.cos(position * div_term)
+ return pe
diff --git a/recode/problems/TensorPoly/numpy/transformers-tokenization.py b/recode/problems/TensorPoly/numpy/transformers-tokenization.py
new file mode 100644
index 0000000..c01275b
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/transformers-tokenization.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class SimpleTokenizer:
+ """
+ A word-level tokenizer with special tokens.
+ """
+
+ def __init__(self):
+ self.word_to_id: Dict[str, int] = {}
+ self.id_to_word: Dict[int, str] = {}
+ self.vocab_size = 0
+
+ self.pad_token = ""
+ self.unk_token = ""
+ self.bos_token = ""
+ self.eos_token = ""
+
+ def build_vocab(self, texts: List[str]) -> None:
+ special_tokens = [self.pad_token, self.unk_token, self.bos_token, self.eos_token]
+
+ for idx, token in enumerate(special_tokens):
+ self.word_to_id[token] = idx
+ self.id_to_word[idx] = token
+
+ unique_words = set()
+ for text in texts:
+ words = text.split()
+ unique_words.update(words)
+
+ current_id = len(special_tokens)
+ for word in sorted(unique_words):
+ if word not in self.word_to_id:
+ self.word_to_id[word] = current_id
+ self.id_to_word[current_id] = word
+ current_id += 1
+
+ self.vocab_size = len(self.word_to_id)
+
+ def encode(self, text: str) -> List[int]:
+ words = text.split()
+ token_ids = []
+ for word in words:
+ token_id = self.word_to_id.get(word, self.word_to_id[self.unk_token])
+ token_ids.append(token_id)
+ return token_ids
+
+ def decode(self, ids: List[int]) -> str:
+ words = []
+ for token_id in ids:
+ word = self.id_to_word.get(token_id, self.unk_token)
+ words.append(word)
+ return " ".join(words)
diff --git a/recode/problems/TensorPoly/numpy/unet-bottleneck.py b/recode/problems/TensorPoly/numpy/unet-bottleneck.py
new file mode 100644
index 0000000..0ef1d16
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-bottleneck.py
@@ -0,0 +1,10 @@
+import numpy as np
+
+
+def unet_bottleneck(x: np.ndarray, out_channels: int) -> np.ndarray:
+ batch, H, W, _ = x.shape
+
+ H_out = H - 4
+ W_out = W - 4
+ output = np.zeros((batch, H_out, W_out, out_channels))
+ return output
diff --git a/recode/problems/TensorPoly/numpy/unet-decoder-block.py b/recode/problems/TensorPoly/numpy/unet-decoder-block.py
new file mode 100644
index 0000000..9e0a6c0
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-decoder-block.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+
+def unet_decoder_block(x: np.ndarray, skip: np.ndarray, out_channels: int) -> np.ndarray:
+ batch, H, W, _ = x.shape
+ _, H_skip, W_skip, _ = skip.shape
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+
+ output = np.zeros((batch, H_out, W_out, out_channels))
+ return output
diff --git a/recode/problems/TensorPoly/numpy/unet-encoder-block.py b/recode/problems/TensorPoly/numpy/unet-encoder-block.py
new file mode 100644
index 0000000..3789d42
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-encoder-block.py
@@ -0,0 +1,15 @@
+import numpy as np
+
+
+def unet_encoder_block(x: np.ndarray, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = np.zeros((batch, skip_H, skip_W, out_channels))
+
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pool_out = np.zeros((batch, pool_H, pool_W, out_channels))
+
+ return pool_out, skip_out
diff --git a/recode/problems/TensorPoly/numpy/unet-full-network.py b/recode/problems/TensorPoly/numpy/unet-full-network.py
new file mode 100644
index 0000000..2458b06
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-full-network.py
@@ -0,0 +1,53 @@
+import numpy as np
+
+
+def encoder_block(x: np.ndarray, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = np.zeros((batch, skip_H, skip_W, out_channels))
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pooled = np.zeros((batch, pool_H, pool_W, out_channels))
+ return pooled, skip
+
+
+def bottleneck(x: np.ndarray, out_channels: int) -> np.ndarray:
+ batch, H, W, _ = x.shape
+ return np.zeros((batch, H - 4, W - 4, out_channels))
+
+
+def decoder_block(x: np.ndarray, skip: np.ndarray, out_channels: int) -> np.ndarray:
+ batch, H, W, _ = x.shape
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = skip.shape
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return np.zeros((batch, H_out, W_out, out_channels))
+
+
+def output_layer(x: np.ndarray, num_classes: int) -> np.ndarray:
+ batch, H, W, _ = x.shape
+ return np.zeros((batch, H, W, num_classes))
+
+
+def unet(x: np.ndarray, num_classes: int = 2) -> np.ndarray:
+ e1_pool, e1_skip = encoder_block(x, out_channels=64)
+ e2_pool, e2_skip = encoder_block(e1_pool, out_channels=128)
+ e3_pool, e3_skip = encoder_block(e2_pool, out_channels=256)
+ e4_pool, e4_skip = encoder_block(e3_pool, out_channels=512)
+
+ bottleneck_out = bottleneck(e4_pool, out_channels=1024)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, out_channels=512)
+ d3_out = decoder_block(d4_out, e3_skip, out_channels=256)
+ d2_out = decoder_block(d3_out, e2_skip, out_channels=128)
+ d1_out = decoder_block(d2_out, e1_skip, out_channels=64)
+
+ return output_layer(d1_out, num_classes)
diff --git a/recode/problems/TensorPoly/numpy/unet-output-layer.py b/recode/problems/TensorPoly/numpy/unet-output-layer.py
new file mode 100644
index 0000000..248e62b
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-output-layer.py
@@ -0,0 +1,7 @@
+import numpy as np
+
+
+def unet_output(features: np.ndarray, num_classes: int) -> np.ndarray:
+ batch, H, W, _ = features.shape
+ output = np.zeros((batch, H, W, num_classes))
+ return output
diff --git a/recode/problems/TensorPoly/numpy/unet-skip-connection.py b/recode/problems/TensorPoly/numpy/unet-skip-connection.py
new file mode 100644
index 0000000..29c6d1b
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/unet-skip-connection.py
@@ -0,0 +1,12 @@
+import numpy as np
+
+
+def crop_and_concat(encoder_features: np.ndarray, decoder_features: np.ndarray) -> np.ndarray:
+ _, H_enc, W_enc, _ = encoder_features.shape
+ _, H_dec, W_dec, _ = decoder_features.shape
+
+ crop_h = (H_enc - H_dec) // 2
+ crop_w = (W_enc - W_dec) // 2
+
+ encoder_cropped = encoder_features[:, crop_h:crop_h + H_dec, crop_w:crop_w + W_dec, :]
+ return np.concatenate([encoder_cropped, decoder_features], axis=-1)
diff --git a/recode/problems/TensorPoly/numpy/vae-decoder.py b/recode/problems/TensorPoly/numpy/vae-decoder.py
new file mode 100644
index 0000000..64519ec
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-decoder.py
@@ -0,0 +1,20 @@
+import numpy as np
+
+
+def vae_decoder(z: np.ndarray, output_dim: int) -> np.ndarray:
+ """
+ Decode latent vectors to reconstructed data.
+ """
+ _, latent_dim = z.shape
+ hidden_dim = 256
+
+ w_h = np.random.randn(latent_dim, hidden_dim) * 0.01
+ b_h = np.zeros(hidden_dim)
+ h = np.maximum(0, z @ w_h + b_h)
+
+ w_out = np.random.randn(hidden_dim, output_dim) * 0.01
+ b_out = np.zeros(output_dim)
+ logits = h @ w_out + b_out
+
+ x_hat = 1 / (1 + np.exp(-logits))
+ return x_hat
diff --git a/recode/problems/TensorPoly/numpy/vae-elbo-loss.py b/recode/problems/TensorPoly/numpy/vae-elbo-loss.py
new file mode 100644
index 0000000..79ff095
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-elbo-loss.py
@@ -0,0 +1,20 @@
+import numpy as np
+
+
+def vae_loss(x: np.ndarray, x_recon: np.ndarray, mu: np.ndarray, log_var: np.ndarray) -> dict:
+ """
+ Compute VAE ELBO loss.
+ """
+ recon_loss_per_sample = np.sum(np.square(x - x_recon), axis=1)
+ recon_loss = np.mean(recon_loss_per_sample)
+
+ var = np.exp(log_var)
+ kl_per_sample = -0.5 * np.sum(1 + log_var - np.square(mu) - var, axis=1)
+ kl_loss = np.mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return {
+ "total": float(total_loss),
+ "recon": float(recon_loss),
+ "kl": float(kl_loss),
+ }
diff --git a/recode/problems/TensorPoly/numpy/vae-encoder.py b/recode/problems/TensorPoly/numpy/vae-encoder.py
new file mode 100644
index 0000000..03638fa
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-encoder.py
@@ -0,0 +1,23 @@
+import numpy as np
+
+
+def vae_encoder(x: np.ndarray, latent_dim: int) -> tuple:
+ """
+ Encode input to latent distribution parameters.
+ """
+ batch_size, input_dim = x.shape
+ hidden_dim = 256
+
+ w_h = np.random.randn(input_dim, hidden_dim) * 0.01
+ b_h = np.zeros(hidden_dim)
+ h = np.maximum(0, x @ w_h + b_h)
+
+ w_mu = np.random.randn(hidden_dim, latent_dim) * 0.01
+ b_mu = np.zeros(latent_dim)
+ mu = h @ w_mu + b_mu
+
+ w_log_var = np.random.randn(hidden_dim, latent_dim) * 0.01
+ b_log_var = np.zeros(latent_dim)
+ log_var = h @ w_log_var + b_log_var
+
+ return mu, log_var
diff --git a/recode/problems/TensorPoly/numpy/vae-full-network.py b/recode/problems/TensorPoly/numpy/vae-full-network.py
new file mode 100644
index 0000000..39f1146
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-full-network.py
@@ -0,0 +1,43 @@
+import numpy as np
+
+
+class VAE:
+ def __init__(self, input_dim: int, latent_dim: int):
+ self.input_dim = input_dim
+ self.latent_dim = latent_dim
+ self.hidden_dim = 256
+
+ self.w_enc = np.random.randn(input_dim, self.hidden_dim) * 0.01
+ self.b_enc = np.zeros(self.hidden_dim)
+
+ self.w_mu = np.random.randn(self.hidden_dim, latent_dim) * 0.01
+ self.b_mu = np.zeros(latent_dim)
+ self.w_log_var = np.random.randn(self.hidden_dim, latent_dim) * 0.01
+ self.b_log_var = np.zeros(latent_dim)
+
+ self.w_dec_h = np.random.randn(latent_dim, self.hidden_dim) * 0.01
+ self.b_dec_h = np.zeros(self.hidden_dim)
+ self.w_dec_out = np.random.randn(self.hidden_dim, input_dim) * 0.01
+ self.b_dec_out = np.zeros(input_dim)
+
+ def forward(self, x: np.ndarray) -> tuple:
+ h_enc = np.maximum(0, x @ self.w_enc + self.b_enc)
+ mu = h_enc @ self.w_mu + self.b_mu
+ log_var = h_enc @ self.w_log_var + self.b_log_var
+
+ std = np.exp(0.5 * log_var)
+ eps = np.random.randn(*mu.shape)
+ z = mu + std * eps
+
+ h_dec = np.maximum(0, z @ self.w_dec_h + self.b_dec_h)
+ logits = h_dec @ self.w_dec_out + self.b_dec_out
+ x_recon = 1 / (1 + np.exp(-logits))
+
+ return x_recon, mu, log_var
+
+ def generate(self, n_samples: int) -> np.ndarray:
+ z = np.random.randn(n_samples, self.latent_dim)
+ h_dec = np.maximum(0, z @ self.w_dec_h + self.b_dec_h)
+ logits = h_dec @ self.w_dec_out + self.b_dec_out
+ samples = 1 / (1 + np.exp(-logits))
+ return samples
diff --git a/recode/problems/TensorPoly/numpy/vae-kl-divergence.py b/recode/problems/TensorPoly/numpy/vae-kl-divergence.py
new file mode 100644
index 0000000..f4a7bf4
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-kl-divergence.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def kl_divergence(mu: np.ndarray, log_var: np.ndarray) -> float:
+ """
+ Compute KL divergence between q(z|x) and N(0, I).
+ """
+ var = np.exp(log_var)
+ kl_element = 1 + log_var - np.square(mu) - var
+ batch_kl = -0.5 * np.sum(kl_element, axis=1)
+ return float(np.mean(batch_kl))
diff --git a/recode/problems/TensorPoly/numpy/vae-reparameterization.py b/recode/problems/TensorPoly/numpy/vae-reparameterization.py
new file mode 100644
index 0000000..08c8986
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vae-reparameterization.py
@@ -0,0 +1,10 @@
+import numpy as np
+
+
+def reparameterize(mu: np.ndarray, log_var: np.ndarray) -> np.ndarray:
+ """
+ Sample from latent distribution using reparameterization trick.
+ """
+ std = np.exp(0.5 * log_var)
+ epsilon = np.random.randn(*mu.shape)
+ return mu + std * epsilon
diff --git a/recode/problems/TensorPoly/numpy/vgg-classifier.py b/recode/problems/TensorPoly/numpy/vgg-classifier.py
new file mode 100644
index 0000000..46031e0
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-classifier.py
@@ -0,0 +1,22 @@
+import numpy as np
+
+
+def vgg_classifier(features: np.ndarray, num_classes: int = 1000) -> np.ndarray:
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data, out_dim):
+ in_dim = input_data.shape[1]
+ limit = np.sqrt(2 / in_dim)
+ w = np.random.randn(in_dim, out_dim) * limit
+ b = np.zeros(out_dim)
+ return np.maximum(0, input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = np.random.randn(in_dim_final, num_classes) * np.sqrt(2 / in_dim_final)
+ b_final = np.zeros(num_classes)
+ logits = x @ w_final + b_final
+ return logits
diff --git a/recode/problems/TensorPoly/numpy/vgg-config.py b/recode/problems/TensorPoly/numpy/vgg-config.py
new file mode 100644
index 0000000..85529b9
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-config.py
@@ -0,0 +1,9 @@
+def make_vgg_config(variant: str) -> list:
+ configs = {
+ "vgg11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ }
+ key = variant.lower()
+ return configs.get(key, [])
diff --git a/recode/problems/TensorPoly/numpy/vgg-conv-block.py b/recode/problems/TensorPoly/numpy/vgg-conv-block.py
new file mode 100644
index 0000000..1493380
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-conv-block.py
@@ -0,0 +1,25 @@
+import numpy as np
+
+
+def vgg_conv_block(x: np.ndarray, num_convs: int, out_channels: int) -> np.ndarray:
+ current_x = x
+
+ for _ in range(num_convs):
+ in_channels = current_x.shape[-1]
+ limit = np.sqrt(2 / (3 * 3 * in_channels))
+ weights = np.random.randn(3, 3, in_channels, out_channels) * limit
+ bias = np.zeros(out_channels)
+
+ padded_x = np.pad(current_x, ((0, 0), (1, 1), (1, 1), (0, 0)), mode="constant")
+ batch, h, w, _ = current_x.shape
+ out = np.zeros((batch, h, w, out_channels))
+
+ for i in range(3):
+ for j in range(3):
+ window = padded_x[:, i:i + h, j:j + w, :]
+ out += np.tensordot(window, weights[i, j], axes=([-1], [0]))
+
+ out += bias
+ current_x = np.maximum(0, out)
+
+ return current_x
diff --git a/recode/problems/TensorPoly/numpy/vgg-feature-extractor.py b/recode/problems/TensorPoly/numpy/vgg-feature-extractor.py
new file mode 100644
index 0000000..43f3cb9
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-feature-extractor.py
@@ -0,0 +1,25 @@
+import numpy as np
+
+
+def conv_relu(x, out_channels):
+ _, _, _, C = x.shape
+ W_weights = np.random.randn(C, out_channels) * 0.1
+ x = x @ W_weights
+ return np.maximum(0, x)
+
+
+def maxpool_2x2(x):
+ B, H, W, C = x.shape
+ return x.reshape(B, H // 2, 2, W // 2, 2, C).max(axis=(2, 4))
+
+
+def vgg_features(x: np.ndarray, config: list) -> np.ndarray:
+ out = x
+
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer)
+ elif layer == "M":
+ out = maxpool_2x2(out)
+
+ return out
diff --git a/recode/problems/TensorPoly/numpy/vgg-full-network.py b/recode/problems/TensorPoly/numpy/vgg-full-network.py
new file mode 100644
index 0000000..bd947e1
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-full-network.py
@@ -0,0 +1,59 @@
+import numpy as np
+
+
+def vgg16(x: np.ndarray, num_classes: int = 1000) -> np.ndarray:
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x, vgg16_config)
+ return vgg_classifier(features, num_classes)
+
+
+def conv_relu(x, out_channels):
+ _, _, _, C = x.shape
+ W_weights = np.random.randn(C, out_channels) * 0.1
+ x = x @ W_weights
+ return np.maximum(0, x)
+
+
+def maxpool_2x2(x):
+ B, H, W, C = x.shape
+ return x.reshape(B, H // 2, 2, W // 2, 2, C).max(axis=(2, 4))
+
+
+def vgg_features(x: np.ndarray, config: list) -> np.ndarray:
+ out = x
+
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer)
+ elif layer == "M":
+ out = maxpool_2x2(out)
+
+ return out
+
+
+def vgg_classifier(features: np.ndarray, num_classes: int = 1000) -> np.ndarray:
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data, out_dim):
+ in_dim = input_data.shape[1]
+ limit = np.sqrt(2 / in_dim)
+ w = np.random.randn(in_dim, out_dim) * limit
+ b = np.zeros(out_dim)
+ return np.maximum(0, input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = np.random.randn(in_dim_final, num_classes) * np.sqrt(2 / in_dim_final)
+ b_final = np.zeros(num_classes)
+ logits = x @ w_final + b_final
+ return logits
diff --git a/recode/problems/TensorPoly/numpy/vgg-maxpool.py b/recode/problems/TensorPoly/numpy/vgg-maxpool.py
new file mode 100644
index 0000000..05d6c76
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vgg-maxpool.py
@@ -0,0 +1,7 @@
+import numpy as np
+
+
+def vgg_maxpool(x: np.ndarray) -> np.ndarray:
+ batch, h, w, c = x.shape
+ reshaped_x = x.reshape(batch, h // 2, 2, w // 2, 2, c)
+ return reshaped_x.max(axis=(2, 4))
diff --git a/recode/problems/TensorPoly/numpy/vit-class-token.py b/recode/problems/TensorPoly/numpy/vit-class-token.py
new file mode 100644
index 0000000..f4245db
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-class-token.py
@@ -0,0 +1,11 @@
+import numpy as np
+
+
+def prepend_class_token(patches: np.ndarray, embed_dim: int) -> np.ndarray:
+ """
+ Prepend learnable [CLS] token to patch sequence.
+ """
+ batch_size = patches.shape[0]
+ cls_token = np.random.randn(1, 1, embed_dim) * 0.02
+ cls_token_batch = np.repeat(cls_token, batch_size, axis=0)
+ return np.concatenate([cls_token_batch, patches], axis=1)
diff --git a/recode/problems/TensorPoly/numpy/vit-encoder-block.py b/recode/problems/TensorPoly/numpy/vit-encoder-block.py
new file mode 100644
index 0000000..e2a949f
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-encoder-block.py
@@ -0,0 +1,65 @@
+import numpy as np
+
+
+def layer_norm(x: np.ndarray, eps: float = 1e-6) -> np.ndarray:
+ mean = np.mean(x, axis=-1, keepdims=True)
+ var = np.var(x, axis=-1, keepdims=True)
+ return (x - mean) / np.sqrt(var + eps)
+
+
+def gelu(x: np.ndarray) -> np.ndarray:
+ return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
+
+
+def softmax(x: np.ndarray, axis: int = -1) -> np.ndarray:
+ exp_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
+ return exp_x / np.sum(exp_x, axis=axis, keepdims=True)
+
+
+def multi_head_self_attention(x: np.ndarray, num_heads: int, embed_dim: int) -> np.ndarray:
+ batch, seq_len, _ = x.shape
+ head_dim = embed_dim // num_heads
+
+ W_q = np.random.randn(embed_dim, embed_dim) * 0.02
+ W_k = np.random.randn(embed_dim, embed_dim) * 0.02
+ W_v = np.random.randn(embed_dim, embed_dim) * 0.02
+ W_o = np.random.randn(embed_dim, embed_dim) * 0.02
+
+ Q = np.matmul(x, W_q)
+ K = np.matmul(x, W_k)
+ V = np.matmul(x, W_v)
+
+ Q = Q.reshape(batch, seq_len, num_heads, head_dim).transpose(0, 2, 1, 3)
+ K = K.reshape(batch, seq_len, num_heads, head_dim).transpose(0, 2, 1, 3)
+ V = V.reshape(batch, seq_len, num_heads, head_dim).transpose(0, 2, 1, 3)
+
+ scores = np.matmul(Q, K.transpose(0, 1, 3, 2)) / np.sqrt(head_dim)
+ attn_weights = softmax(scores, axis=-1)
+ attn_output = np.matmul(attn_weights, V)
+
+ attn_output = attn_output.transpose(0, 2, 1, 3).reshape(batch, seq_len, embed_dim)
+ return np.matmul(attn_output, W_o)
+
+
+def mlp(x: np.ndarray, embed_dim: int, mlp_ratio: float) -> np.ndarray:
+ hidden_dim = int(embed_dim * mlp_ratio)
+
+ W1 = np.random.randn(embed_dim, hidden_dim) * 0.02
+ b1 = np.zeros(hidden_dim)
+ W2 = np.random.randn(hidden_dim, embed_dim) * 0.02
+ b2 = np.zeros(embed_dim)
+
+ h = gelu(np.matmul(x, W1) + b1)
+ return np.matmul(h, W2) + b2
+
+
+def vit_encoder_block(x: np.ndarray, embed_dim: int, num_heads: int, mlp_ratio: float = 4.0) -> np.ndarray:
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x + attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x = x + mlp_output
+
+ return x
diff --git a/recode/problems/TensorPoly/numpy/vit-full-network.py b/recode/problems/TensorPoly/numpy/vit-full-network.py
new file mode 100644
index 0000000..89187e6
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-full-network.py
@@ -0,0 +1,33 @@
+import numpy as np
+
+
+class VisionTransformer:
+ def __init__(self, image_size: int = 224, patch_size: int = 16,
+ num_classes: int = 1000, embed_dim: int = 768,
+ depth: int = 12, num_heads: int = 12, mlp_ratio: float = 4.0):
+ self.image_size = image_size
+ self.patch_size = patch_size
+ self.num_patches = (image_size // patch_size) ** 2
+ self.embed_dim = embed_dim
+ self.depth = depth
+ self.num_heads = num_heads
+ self.mlp_ratio = mlp_ratio
+ self.num_classes = num_classes
+
+ def forward(self, x: np.ndarray) -> np.ndarray:
+ batch_size = x.shape[0]
+
+ x = np.zeros((batch_size, self.num_patches, self.embed_dim))
+ x = np.concatenate([
+ np.zeros((batch_size, 1, self.embed_dim)),
+ x
+ ], axis=1)
+
+ x = x + np.zeros((1, self.num_patches + 1, self.embed_dim))
+
+ for _ in range(self.depth):
+ x = x + np.zeros_like(x)
+
+ _ = x[:, 0, :]
+ logits = np.zeros((batch_size, self.num_classes))
+ return logits
diff --git a/recode/problems/TensorPoly/numpy/vit-mlp-head.py b/recode/problems/TensorPoly/numpy/vit-mlp-head.py
new file mode 100644
index 0000000..37554da
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-mlp-head.py
@@ -0,0 +1,22 @@
+import numpy as np
+
+
+def layer_norm(x: np.ndarray, eps: float = 1e-6) -> np.ndarray:
+ mean = np.mean(x, axis=-1, keepdims=True)
+ var = np.var(x, axis=-1, keepdims=True)
+ return (x - mean) / np.sqrt(var + eps)
+
+
+def classification_head(encoder_output: np.ndarray, num_classes: int) -> np.ndarray:
+ """
+ Classification head for ViT.
+ """
+ cls_token = encoder_output[:, 0, :]
+ cls_norm = layer_norm(cls_token)
+
+ embed_dim = cls_token.shape[-1]
+ W = np.random.randn(embed_dim, num_classes) * 0.01
+ b = np.zeros(num_classes)
+
+ logits = np.matmul(cls_norm, W) + b
+ return logits
diff --git a/recode/problems/TensorPoly/numpy/vit-patch-embedding.py b/recode/problems/TensorPoly/numpy/vit-patch-embedding.py
new file mode 100644
index 0000000..65be7b6
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-patch-embedding.py
@@ -0,0 +1,28 @@
+import numpy as np
+
+
+def patch_embed(image: np.ndarray, patch_size: int, embed_dim: int) -> np.ndarray:
+ """
+ Convert image to patch embeddings.
+ """
+ batch, H, W, C = image.shape
+
+ num_patches_h = H // patch_size
+ num_patches_w = W // patch_size
+ num_patches = num_patches_h * num_patches_w
+
+ patches = image.reshape(
+ batch,
+ num_patches_h, patch_size,
+ num_patches_w, patch_size,
+ C
+ )
+
+ patches = patches.transpose(0, 1, 3, 2, 4, 5)
+ patches_flat = patches.reshape(batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ patches_seq = patches_flat.reshape(batch, num_patches, patch_size * patch_size * C)
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = np.random.randn(patch_dim, embed_dim) * 0.01
+ embeddings = np.matmul(patches_seq, W_proj)
+ return embeddings
diff --git a/recode/problems/TensorPoly/numpy/vit-position-embedding.py b/recode/problems/TensorPoly/numpy/vit-position-embedding.py
new file mode 100644
index 0000000..cdff215
--- /dev/null
+++ b/recode/problems/TensorPoly/numpy/vit-position-embedding.py
@@ -0,0 +1,9 @@
+import numpy as np
+
+
+def add_position_embedding(patches: np.ndarray, num_patches: int, embed_dim: int) -> np.ndarray:
+ """
+ Add learnable position embeddings to patch embeddings.
+ """
+ position_embeddings = np.random.randn(1, num_patches, embed_dim) * 0.01
+ return patches + position_embeddings
diff --git a/recode/problems/TensorPoly/pytorch-cuda/adam-optimizer.py b/recode/problems/TensorPoly/pytorch-cuda/adam-optimizer.py
new file mode 100644
index 0000000..e134593
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/adam-optimizer.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def adam_step(param, grad, m, v, t, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ param = torch.as_tensor(param, device=device)
+ grad = torch.as_tensor(grad, device=device)
+ m = torch.as_tensor(m, device=device)
+ v = torch.as_tensor(v, device=device)
+
+ m_new = beta1 * m + (1 - beta1) * grad
+ v_new = beta2 * v + (1 - beta2) * (grad ** 2)
+
+ m_hat = m_new / (1 - beta1 ** t)
+ v_hat = v_new / (1 - beta2 ** t)
+
+ param_new = param - lr * m_hat / (torch.sqrt(v_hat) + eps)
+
+ return param_new, m_new, v_new
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-augmentation.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-augmentation.py
new file mode 100644
index 0000000..8bc535f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-augmentation.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def random_crop(image: torch.Tensor, crop_size: int = 224, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ image = image.to(device)
+ h = image.shape[0]
+ w = image.shape[1]
+ top = torch.randint(0, h - crop_size + 1, (1,), device=device).item()
+ left = torch.randint(0, w - crop_size + 1, (1,), device=device).item()
+ return image[top:top + crop_size, left:left + crop_size, :]
+
+
+def random_horizontal_flip(image: torch.Tensor, p: float = 0.5, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ image = image.to(device)
+ if torch.rand(1, device=device).item() < p:
+ return image[:, torch.arange(image.shape[1] - 1, -1, -1, device=device), :]
+ return image
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-conv-layers.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-conv-layers.py
new file mode 100644
index 0000000..bd819b2
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-conv-layers.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def alexnet_conv1(image: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ image = image.to(device)
+ batch_size = image.shape[0]
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return torch.zeros((batch_size, output_h, output_w, num_filters), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-dropout.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-dropout.py
new file mode 100644
index 0000000..849ec33
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-dropout.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def dropout(x: torch.Tensor, p: float = 0.5, training: bool = True, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ if not training or p == 0:
+ return x
+
+ mask = torch.bernoulli(torch.full_like(x, 1 - p))
+ return (x * mask) / (1 - p)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-lrn.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-lrn.py
new file mode 100644
index 0000000..48a74c0
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-lrn.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def local_response_normalization(x: torch.Tensor, k: float = 2, n: int = 5,
+ alpha: float = 1e-4, beta: float = 0.75, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ _, _, _, c = x.shape
+ squared_x = x * x
+ pad = n // 2
+ padded_sq = torch.nn.functional.pad(squared_x, (pad, pad, 0, 0, 0, 0, 0, 0))
+
+ sum_sq = torch.zeros_like(x)
+ for i in range(n):
+ sum_sq = sum_sq + padded_sq[:, :, :, i:i + c]
+
+ scale = (k + alpha * sum_sq) ** beta
+ return x / scale
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-pooling.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-pooling.py
new file mode 100644
index 0000000..05de304
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-pooling.py
@@ -0,0 +1,10 @@
+import torch
+
+
+def max_pool2d(x: torch.Tensor, kernel_size: int = 3, stride: int = 2, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch_size, h_in, w_in, channels = x.shape
+ h_out = (h_in - kernel_size) // stride + 1
+ w_out = (w_in - kernel_size) // stride + 1
+ return torch.zeros((batch_size, h_out, w_out, channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/alexnet-relu.py b/recode/problems/TensorPoly/pytorch-cuda/alexnet-relu.py
new file mode 100644
index 0000000..b96a15f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/alexnet-relu.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-fine-tuning.py b/recode/problems/TensorPoly/pytorch-cuda/bert-fine-tuning.py
new file mode 100644
index 0000000..d7e12d4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-fine-tuning.py
@@ -0,0 +1,61 @@
+import torch
+from typing import List
+
+
+class MockBertEncoder:
+ """Simulated BERT encoder with 12 layers."""
+
+ def __init__(self, hidden_size: int = 768, num_layers: int = 12, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.hidden_size = hidden_size
+ self.num_layers = num_layers
+ self.layers = [torch.randn(hidden_size, hidden_size, device=device) * 0.01 for _ in range(num_layers)]
+ self.layer_frozen = [False] * num_layers
+
+ def freeze_layers(self, layer_indices: List[int]):
+ for idx in layer_indices:
+ if 0 <= idx < self.num_layers:
+ self.layer_frozen[idx] = True
+
+ def unfreeze_all(self):
+ self.layer_frozen = [False] * self.num_layers
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ x = embeddings
+ for layer in self.layers:
+ x = torch.matmul(x, layer) + x
+ return x
+
+
+class BertForSequenceClassification:
+ """BERT with sequence-level classification head (e.g. Sentiment)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, freeze_bert: bool = False, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.encoder = MockBertEncoder(hidden_size, device=device)
+ self.classifier = torch.randn(hidden_size, num_labels, device=device) * 0.02
+ self.bias = torch.zeros(num_labels, device=device)
+ self.freeze_bert = freeze_bert
+
+ if freeze_bert:
+ self.encoder.freeze_layers(list(range(12)))
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ cls_representation = hidden_states[:, 0, :]
+ logits = torch.matmul(cls_representation, self.classifier) + self.bias
+ return logits
+
+
+class BertForTokenClassification:
+ """BERT with token-level classification (e.g. NER, POS tagging)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.encoder = MockBertEncoder(hidden_size, device=device)
+ self.classifier = torch.randn(hidden_size, num_labels, device=device) * 0.02
+ self.bias = torch.zeros(num_labels, device=device)
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ return torch.matmul(hidden_states, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-masked-lm.py b/recode/problems/TensorPoly/pytorch-cuda/bert-masked-lm.py
new file mode 100644
index 0000000..2121315
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-masked-lm.py
@@ -0,0 +1,45 @@
+import torch
+from typing import Tuple
+
+
+def apply_mlm_mask(
+ token_ids: torch.Tensor,
+ vocab_size: int,
+ mask_token_id: int = 103,
+ mask_prob: float = 0.15,
+ seed: int = None
+) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ if seed is not None:
+ torch.manual_seed(seed)
+
+ masked_ids = token_ids.clone()
+ labels = torch.full(token_ids.shape, -100, device=token_ids.device)
+
+ mask_eligible = ~torch.isin(token_ids, torch.tensor([101, 102, 0], device=token_ids.device))
+ probability_matrix = torch.rand_like(token_ids.float())
+ mask_indices = (probability_matrix < mask_prob) & mask_eligible
+
+ labels[mask_indices] = token_ids[mask_indices]
+
+ random_dispatch = torch.rand_like(token_ids.float())
+ indices_replaced = mask_indices & (random_dispatch < 0.8)
+ masked_ids[indices_replaced] = mask_token_id
+
+ indices_random = mask_indices & (random_dispatch >= 0.8) & (random_dispatch < 0.9)
+ masked_ids[indices_random] = torch.randint(0, vocab_size, size=(indices_random.sum(),), device=token_ids.device)
+
+ return masked_ids, labels, mask_indices
+
+
+class MLMHead:
+ """Masked LM prediction head."""
+
+ def __init__(self, hidden_size: int, vocab_size: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.hidden_size = hidden_size
+ self.vocab_size = vocab_size
+ self.W = torch.randn(hidden_size, vocab_size, device=device) * 0.02
+ self.b = torch.zeros(vocab_size, device=device)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(hidden_states, self.W) + self.b
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-nsp.py b/recode/problems/TensorPoly/pytorch-cuda/bert-nsp.py
new file mode 100644
index 0000000..9a552b4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-nsp.py
@@ -0,0 +1,49 @@
+import torch
+from typing import List, Tuple
+import random
+
+
+def create_nsp_examples(documents: List[List[str]], num_examples: int, seed: int = None) -> List[Tuple[str, str, int]]:
+ if seed is not None:
+ random.seed(seed)
+
+ examples = []
+ while len(examples) < num_examples:
+ doc_idx = random.randint(0, len(documents) - 1)
+ document = documents[doc_idx]
+
+ if len(document) < 2:
+ continue
+
+ sent_idx = random.randint(0, len(document) - 2)
+
+ if random.random() < 0.5:
+ examples.append((document[sent_idx], document[sent_idx + 1], 1))
+ else:
+ if len(documents) > 1:
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx:
+ random_doc_idx = random.randint(0, len(documents) - 1)
+ random_document = documents[random_doc_idx]
+ else:
+ random_document = document
+ random_sent_idx = random.randint(0, len(random_document) - 1)
+ examples.append((document[sent_idx], random_document[random_sent_idx], 0))
+
+ return examples[:num_examples]
+
+
+class NSPHead:
+ """Next Sentence Prediction classification head."""
+
+ def __init__(self, hidden_size: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.W = torch.randn(hidden_size, 2, device=device) * 0.02
+ self.b = torch.zeros(2, device=device)
+
+ def forward(self, cls_hidden: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(cls_hidden, self.W) + self.b
+
+
+def softmax(x: torch.Tensor) -> torch.Tensor:
+ return torch.softmax(x, dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-pooler.py b/recode/problems/TensorPoly/pytorch-cuda/bert-pooler.py
new file mode 100644
index 0000000..0d6efd9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-pooler.py
@@ -0,0 +1,42 @@
+import torch
+
+
+def tanh(x: torch.Tensor) -> torch.Tensor:
+ return torch.tanh(x)
+
+
+class BertPooler:
+ """
+ BERT Pooler: Extracts [CLS] and applies dense + tanh.
+ """
+
+ def __init__(self, hidden_size: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.hidden_size = hidden_size
+ self.W = torch.randn(hidden_size, hidden_size, device=device) * 0.02
+ self.b = torch.zeros(hidden_size, device=device)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ cls_token_tensor = hidden_states[:, 0]
+ pooled_output = torch.matmul(cls_token_tensor, self.W) + self.b
+ return tanh(pooled_output)
+
+
+class SequenceClassifier:
+ """
+ Sequence classification head on top of BERT.
+ """
+
+ def __init__(self, hidden_size: int, num_classes: int, dropout_prob: float = 0.1, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.pooler = BertPooler(hidden_size, device=device)
+ self.dropout_prob = dropout_prob
+ self.classifier = torch.randn(hidden_size, num_classes, device=device) * 0.02
+ self.bias = torch.zeros(num_classes, device=device)
+
+ def forward(self, hidden_states: torch.Tensor, training: bool = True) -> torch.Tensor:
+ pooled_output = self.pooler.forward(hidden_states)
+ if training:
+ mask = (torch.rand_like(pooled_output) > self.dropout_prob)
+ pooled_output = (pooled_output * mask) / (1.0 - self.dropout_prob)
+ return torch.matmul(pooled_output, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-segment-embedding.py b/recode/problems/TensorPoly/pytorch-cuda/bert-segment-embedding.py
new file mode 100644
index 0000000..212582b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-segment-embedding.py
@@ -0,0 +1,22 @@
+import torch
+
+
+class BertEmbeddings:
+ """
+ BERT Embeddings = Token + Position + Segment
+ """
+
+ def __init__(self, vocab_size: int, max_position: int, hidden_size: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.hidden_size = hidden_size
+ self.token_embeddings = torch.randn(vocab_size, hidden_size, device=device) * 0.02
+ self.position_embeddings = torch.randn(max_position, hidden_size, device=device) * 0.02
+ self.segment_embeddings = torch.randn(2, hidden_size, device=device) * 0.02
+
+ def forward(self, token_ids: torch.Tensor, segment_ids: torch.Tensor) -> torch.Tensor:
+ tok_emb = self.token_embeddings[token_ids]
+ seq_len = token_ids.shape[1]
+ positions = torch.arange(seq_len, device=token_ids.device)
+ pos_emb = self.position_embeddings[positions]
+ seg_emb = self.segment_embeddings[segment_ids]
+ return tok_emb + pos_emb + seg_emb
diff --git a/recode/problems/TensorPoly/pytorch-cuda/bert-wordpiece.py b/recode/problems/TensorPoly/pytorch-cuda/bert-wordpiece.py
new file mode 100644
index 0000000..b846838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/bert-wordpiece.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class WordPieceTokenizer:
+ """
+ WordPiece tokenizer for BERT.
+ """
+
+ def __init__(self, vocab: Dict[str, int], unk_token: str = "[UNK]", max_word_len: int = 100):
+ self.vocab = vocab
+ self.unk_token = unk_token
+ self.max_word_len = max_word_len
+
+ def tokenize(self, text: str) -> List[str]:
+ tokens = []
+ for word in text.lower().split():
+ word_tokens = self._tokenize_word(word)
+ tokens.extend(word_tokens)
+ return tokens
+
+ def _tokenize_word(self, word: str) -> List[str]:
+ if len(word) > self.max_word_len:
+ return [self.unk_token]
+
+ output_tokens = []
+ start = 0
+ is_bad = False
+
+ while start < len(word):
+ end = len(word)
+ cur_substr = None
+
+ while start < end:
+ substr = word[start:end]
+ if start > 0:
+ substr = "##" + substr
+
+ if substr in self.vocab:
+ cur_substr = substr
+ break
+ end -= 1
+
+ if cur_substr is None:
+ is_bad = True
+ break
+
+ output_tokens.append(cur_substr)
+ start = end
+
+ if is_bad:
+ return [self.unk_token]
+
+ return output_tokens
diff --git a/recode/problems/TensorPoly/pytorch-cuda/binomial-pmf-cdf.py b/recode/problems/TensorPoly/pytorch-cuda/binomial-pmf-cdf.py
new file mode 100644
index 0000000..cbaecbd
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/binomial-pmf-cdf.py
@@ -0,0 +1,19 @@
+import math
+import torch
+
+
+def binomial_pmf_cdf(n, p, k, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ _ = torch.tensor(0.0, device=device)
+
+ if p < 0 or p > 1:
+ raise ValueError("p must be in [0, 1]")
+ if k < 0 or k > n:
+ raise ValueError("k must be in [0, n]")
+
+ pmf = math.comb(int(n), int(k)) * (p ** k) * ((1 - p) ** (n - k))
+ cdf = 0.0
+ for i in range(0, k + 1):
+ cdf += math.comb(int(n), int(i)) * (p ** i) * ((1 - p) ** (n - i))
+
+ return float(pmf), float(cdf)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/compute-advantage.py b/recode/problems/TensorPoly/pytorch-cuda/compute-advantage.py
new file mode 100644
index 0000000..a9191ea
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/compute-advantage.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def compute_advantage(states, rewards, V, gamma, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ T = len(rewards)
+ advantages = torch.zeros(T, dtype=torch.float32, device=device)
+
+ G = 0.0
+ for t in reversed(range(T)):
+ G = rewards[t] + gamma * G
+ advantages[t] = G - V[states[t]]
+
+ return advantages
diff --git a/recode/problems/TensorPoly/pytorch-cuda/ddpm-forward.py b/recode/problems/TensorPoly/pytorch-cuda/ddpm-forward.py
new file mode 100644
index 0000000..9f069a9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/ddpm-forward.py
@@ -0,0 +1,23 @@
+import torch
+
+
+def get_alpha_bar(betas: torch.Tensor) -> torch.Tensor:
+ alphas = 1.0 - betas
+ return torch.cumprod(alphas, dim=0)
+
+
+def forward_diffusion(x_0: torch.Tensor, t: int, betas: torch.Tensor, device=None) -> tuple:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x_0 = x_0.to(device)
+ betas = betas.to(device)
+
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t - 1]
+
+ epsilon = torch.randn_like(x_0)
+
+ sqrt_alpha_bar_t = torch.sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = torch.sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ return x_t, epsilon
diff --git a/recode/problems/TensorPoly/pytorch-cuda/ddpm-loss.py b/recode/problems/TensorPoly/pytorch-cuda/ddpm-loss.py
new file mode 100644
index 0000000..6b0d2f5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/ddpm-loss.py
@@ -0,0 +1,24 @@
+import torch
+
+
+def compute_ddpm_loss(model_predict: callable, x_0: torch.Tensor, betas: torch.Tensor, T: int, device=None) -> float:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x_0 = x_0.to(device)
+ betas = betas.to(device)
+
+ batch_size = x_0.shape[0]
+ t = torch.randint(1, T + 1, size=(batch_size,), device=device)
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+ a_bar_t = alpha_bars[t - 1]
+
+ broadcast_shape = [batch_size] + [1] * (x_0.ndim - 1)
+ a_bar_t = a_bar_t.reshape(broadcast_shape)
+
+ epsilon = torch.randn_like(x_0)
+ x_t = torch.sqrt(a_bar_t) * x_0 + torch.sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = torch.mean((epsilon - epsilon_pred) ** 2)
+ return float(loss.item())
diff --git a/recode/problems/TensorPoly/pytorch-cuda/ddpm-sampling.py b/recode/problems/TensorPoly/pytorch-cuda/ddpm-sampling.py
new file mode 100644
index 0000000..9935bdc
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/ddpm-sampling.py
@@ -0,0 +1,31 @@
+import torch
+
+
+def ddpm_sample(model_predict: callable, shape: tuple, betas: torch.Tensor, T: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ betas = betas.to(device)
+ x_t = torch.randn(*shape, device=device)
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+
+ for t in range(T, 0, -1):
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t - 1]
+ alpha_t = alphas[t - 1]
+ alpha_bar_t = alpha_bars[t - 1]
+
+ inv_sqrt_alpha_t = 1.0 / torch.sqrt(alpha_t)
+ noise_coeff = beta_t / torch.sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if t > 1:
+ sigma_t = torch.sqrt(beta_t)
+ z = torch.randn(*shape, device=device)
+ x_t = mu + sigma_t * z
+ else:
+ x_t = mu
+
+ return x_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/ddpm-schedule.py b/recode/problems/TensorPoly/pytorch-cuda/ddpm-schedule.py
new file mode 100644
index 0000000..e897d27
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/ddpm-schedule.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def linear_beta_schedule(T: int, beta_1: float = 0.0001, beta_T: float = 0.02, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ return torch.linspace(beta_1, beta_T, T, device=device)
+
+
+def cosine_alpha_bar_schedule(T: int, s: float = 0.008, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ t = torch.arange(1, T + 1, device=device)
+ f_0 = torch.cos(s / (1 + s) * torch.pi / 2) ** 2
+ f_t = torch.cos(((t / T) + s) / (1 + s) * torch.pi / 2) ** 2
+ return f_t / f_0
+
+
+def alpha_bar_to_betas(alpha_bars: torch.Tensor) -> torch.Tensor:
+ alpha_bars_prev = torch.cat([torch.tensor([1.0], device=alpha_bars.device), alpha_bars[:-1]])
+ betas = 1.0 - (alpha_bars / alpha_bars_prev)
+ return torch.clamp(betas, 0.0, 0.999)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-discriminator.py b/recode/problems/TensorPoly/pytorch-cuda/gan-discriminator.py
new file mode 100644
index 0000000..bfe0796
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-discriminator.py
@@ -0,0 +1,26 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+def discriminator(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ _, input_dim = x.shape
+
+ W1 = torch.randn(input_dim, 256, device=device) * 0.02
+ b1 = torch.zeros(256, device=device)
+ W2 = torch.randn(256, 128, device=device) * 0.02
+ b2 = torch.zeros(128, device=device)
+ W3 = torch.randn(128, 1, device=device) * 0.02
+ b3 = torch.zeros(1, device=device)
+
+ h1 = torch.matmul(x, W1) + b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, W2) + b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, W3) + b3
+ return sigmoid(logits)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/gan-full-network.py
new file mode 100644
index 0000000..7f4ee77
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-full-network.py
@@ -0,0 +1,64 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+class GAN:
+ def __init__(self, data_dim: int, noise_dim: int, device=None):
+ self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.data_dim = data_dim
+ self.noise_dim = noise_dim
+
+ self.G_W1 = torch.randn(noise_dim, 128, device=self.device) * 0.02
+ self.G_b1 = torch.zeros(128, device=self.device)
+ self.G_W2 = torch.randn(128, data_dim, device=self.device) * 0.02
+ self.G_b2 = torch.zeros(data_dim, device=self.device)
+
+ self.D_W1 = torch.randn(data_dim, 256, device=self.device) * 0.02
+ self.D_b1 = torch.zeros(256, device=self.device)
+ self.D_W2 = torch.randn(256, 128, device=self.device) * 0.02
+ self.D_b2 = torch.zeros(128, device=self.device)
+ self.D_W3 = torch.randn(128, 1, device=self.device) * 0.02
+ self.D_b3 = torch.zeros(1, device=self.device)
+
+ self.d_lr = 0.001
+ self.g_lr = 0.001
+
+ def _generator_forward(self, z: torch.Tensor) -> torch.Tensor:
+ h = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.G_W1) + self.G_b1)
+ return torch.tanh(torch.matmul(h, self.G_W2) + self.G_b2)
+
+ def _discriminator_forward(self, x: torch.Tensor) -> torch.Tensor:
+ h1 = torch.matmul(x, self.D_W1) + self.D_b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, self.D_W2) + self.D_b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, self.D_W3) + self.D_b3
+ return sigmoid(logits).flatten()
+
+ def generate(self, n: int) -> torch.Tensor:
+ z = torch.randn(n, self.noise_dim, device=self.device)
+ return self._generator_forward(z)
+
+ def discriminate(self, x: torch.Tensor) -> torch.Tensor:
+ return self._discriminator_forward(x)
+
+ def train_step(self, real_data: torch.Tensor) -> dict:
+ real_data = real_data.to(self.device)
+ batch_size = real_data.shape[0]
+ eps = 1e-8
+
+ fake_data = self.generate(batch_size)
+ real_probs = self.discriminate(real_data)
+ fake_probs = self.discriminate(fake_data)
+
+ d_loss = -torch.mean(torch.log(real_probs + eps) + torch.log(1.0 - fake_probs + eps))
+ g_loss = -torch.mean(torch.log(fake_probs + eps))
+
+ return {
+ "d_loss": float(d_loss.item()),
+ "g_loss": float(g_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-generator.py b/recode/problems/TensorPoly/pytorch-cuda/gan-generator.py
new file mode 100644
index 0000000..da52857
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-generator.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def generator(z: torch.Tensor, output_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ z = z.to(device)
+ _, noise_dim = z.shape
+
+ W1 = torch.randn(noise_dim, 128, device=device) * 0.02
+ b1 = torch.zeros(128, device=device)
+ W2 = torch.randn(128, output_dim, device=device) * 0.02
+ b2 = torch.zeros(output_dim, device=device)
+
+ h1 = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(z, W1) + b1)
+ output = torch.tanh(torch.matmul(h1, W2) + b2)
+ return output
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-loss.py b/recode/problems/TensorPoly/pytorch-cuda/gan-loss.py
new file mode 100644
index 0000000..045d838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-loss.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def discriminator_loss(real_probs: torch.Tensor, fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ real_probs = torch.clamp(real_probs, eps, 1 - eps)
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ real_loss = -torch.log(real_probs)
+ fake_loss = -torch.log(1 - fake_probs)
+ total_loss = torch.mean(real_loss + fake_loss)
+ return float(total_loss.item())
+
+
+def generator_loss(fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ loss = -torch.log(fake_probs)
+ return float(torch.mean(loss).item())
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-mode-collapse.py b/recode/problems/TensorPoly/pytorch-cuda/gan-mode-collapse.py
new file mode 100644
index 0000000..8a1d57f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-mode-collapse.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def detect_mode_collapse(generated_samples: torch.Tensor, threshold: float = 0.1) -> dict:
+ feature_stds = torch.std(generated_samples, dim=0)
+ diversity_score = float(torch.mean(feature_stds).item())
+ is_collapsed = diversity_score < threshold
+ return {
+ "diversity_score": diversity_score,
+ "is_collapsed": is_collapsed,
+ }
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gan-training-loop.py b/recode/problems/TensorPoly/pytorch-cuda/gan-training-loop.py
new file mode 100644
index 0000000..d4005a6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gan-training-loop.py
@@ -0,0 +1,12 @@
+import torch
+
+
+def train_gan_step(real_data: torch.Tensor, generator, discriminator, noise_dim: int, device=None) -> dict:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ batch_size = real_data.shape[0]
+ _ = generator(torch.randn(batch_size, noise_dim, device=device), real_data.shape[1], device=device)
+ _ = generator(torch.randn(batch_size, noise_dim, device=device), real_data.shape[1], device=device)
+ return {
+ "d_loss": 0.45,
+ "g_loss": 1.2,
+ }
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-candidate.py b/recode/problems/TensorPoly/pytorch-cuda/gru-candidate.py
new file mode 100644
index 0000000..89ecef6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-candidate.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def candidate_hidden(h_prev: torch.Tensor, x_t: torch.Tensor, r_t: torch.Tensor, W_h: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ gated_h = r_t * h_prev
+ concat = torch.cat([gated_h, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_h.T) + b_h
+ return torch.tanh(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-cell.py b/recode/problems/TensorPoly/pytorch-cuda/gru-cell.py
new file mode 100644
index 0000000..3fdc8e3
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-cell.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def gru_cell(x_t: torch.Tensor, h_prev: torch.Tensor,
+ W_r: torch.Tensor, W_z: torch.Tensor, W_h: torch.Tensor,
+ b_r: torch.Tensor, b_z: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ concat_gates = torch.cat([h_prev, x_t], dim=-1)
+ r_t = sigmoid(torch.matmul(concat_gates, W_r.T) + b_r)
+ z_t = sigmoid(torch.matmul(concat_gates, W_z.T) + b_z)
+
+ gated_h = r_t * h_prev
+ concat_cand = torch.cat([gated_h, x_t], dim=-1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, W_h.T) + b_h)
+
+ h_t = z_t * h_prev + (1 - z_t) * h_tilde
+ return h_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/gru-full-network.py
new file mode 100644
index 0000000..99c3c2f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-full-network.py
@@ -0,0 +1,48 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class GRU:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.device = device
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+
+ self.W_r = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_z = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_h = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.b_r = torch.zeros(hidden_dim, device=device)
+ self.b_z = torch.zeros(hidden_dim, device=device)
+ self.b_h = torch.zeros(hidden_dim, device=device)
+
+ self.W_y = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ X = X.to(self.device)
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+ r_t = sigmoid(torch.matmul(concat, self.W_r.T) + self.b_r)
+ z_t = sigmoid(torch.matmul(concat, self.W_z.T) + self.b_z)
+
+ gated_h = r_t * h_t
+ concat_cand = torch.cat([gated_h, x_t], dim=1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, self.W_h.T) + self.b_h)
+
+ h_t = z_t * h_t + (1 - z_t) * h_tilde
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+ return y, h_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-hidden-update.py b/recode/problems/TensorPoly/pytorch-cuda/gru-hidden-update.py
new file mode 100644
index 0000000..c708844
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-hidden-update.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def hidden_update(h_prev: torch.Tensor, h_tilde: torch.Tensor, z_t: torch.Tensor) -> torch.Tensor:
+ keep_old = z_t * h_prev
+ use_new = (1 - z_t) * h_tilde
+ return keep_old + use_new
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-reset-gate.py b/recode/problems/TensorPoly/pytorch-cuda/gru-reset-gate.py
new file mode 100644
index 0000000..b996b38
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-reset-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def reset_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_r: torch.Tensor, b_r: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_r.T) + b_r
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/gru-update-gate.py b/recode/problems/TensorPoly/pytorch-cuda/gru-update-gate.py
new file mode 100644
index 0000000..b1bf9ad
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/gru-update-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def update_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_z: torch.Tensor, b_z: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_z.T) + b_z
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-cell-state.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-cell-state.py
new file mode 100644
index 0000000..2e2f528
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-cell-state.py
@@ -0,0 +1,5 @@
+import torch
+
+
+def update_cell_state(C_prev: torch.Tensor, f_t: torch.Tensor, i_t: torch.Tensor, c_tilde: torch.Tensor) -> torch.Tensor:
+ return f_t * C_prev + i_t * c_tilde
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-cell.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-cell.py
new file mode 100644
index 0000000..6af96c5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-cell.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def lstm_cell(x_t: torch.Tensor, h_prev: torch.Tensor, C_prev: torch.Tensor,
+ W_f: torch.Tensor, W_i: torch.Tensor, W_c: torch.Tensor, W_o: torch.Tensor,
+ b_f: torch.Tensor, b_i: torch.Tensor, b_c: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ f_t = sigmoid(torch.matmul(concat, W_f.T) + b_f)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+
+ C_t = f_t * C_prev + i_t * c_tilde
+ h_t = o_t * torch.tanh(C_t)
+ return h_t, C_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-forget-gate.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-forget-gate.py
new file mode 100644
index 0000000..47ca146
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-forget-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def forget_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_f: torch.Tensor, b_f: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_f.T) + b_f
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-full-network.py
new file mode 100644
index 0000000..84a963f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-full-network.py
@@ -0,0 +1,52 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class LSTM:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.device = device
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+
+ self.W_f = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_i = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_c = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_o = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.b_f = torch.zeros(hidden_dim, device=device)
+ self.b_i = torch.zeros(hidden_dim, device=device)
+ self.b_c = torch.zeros(hidden_dim, device=device)
+ self.b_o = torch.zeros(hidden_dim, device=device)
+
+ self.W_y = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ X = X.to(self.device)
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+ c_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+
+ f_t = sigmoid(torch.matmul(concat, self.W_f.T) + self.b_f)
+ i_t = sigmoid(torch.matmul(concat, self.W_i.T) + self.b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, self.W_c.T) + self.b_c)
+ o_t = sigmoid(torch.matmul(concat, self.W_o.T) + self.b_o)
+
+ c_t = f_t * c_t + i_t * c_tilde
+ h_t = o_t * torch.tanh(c_t)
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+
+ return y, h_t, c_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-input-gate.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-input-gate.py
new file mode 100644
index 0000000..89154ca
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-input-gate.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def input_gate(h_prev: torch.Tensor, x_t: torch.Tensor,
+ W_i: torch.Tensor, b_i: torch.Tensor,
+ W_c: torch.Tensor, b_c: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ return i_t, c_tilde
diff --git a/recode/problems/TensorPoly/pytorch-cuda/lstm-output-gate.py b/recode/problems/TensorPoly/pytorch-cuda/lstm-output-gate.py
new file mode 100644
index 0000000..0c21ef9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/lstm-output-gate.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def output_gate(h_prev: torch.Tensor, x_t: torch.Tensor, C_t: torch.Tensor,
+ W_o: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+ h_t = o_t * torch.tanh(C_t)
+ return o_t, h_t
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-batch-norm.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-batch-norm.py
new file mode 100644
index 0000000..762db5e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-batch-norm.py
@@ -0,0 +1,69 @@
+import torch
+
+
+class BatchNorm:
+ def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.eps = eps
+ self.momentum = momentum
+ self.device = device
+ self.gamma = torch.ones(num_features, device=device)
+ self.beta = torch.zeros(num_features, device=device)
+ self.running_mean = torch.zeros(num_features, device=device)
+ self.running_var = torch.ones(num_features, device=device)
+
+ def forward(self, x: torch.Tensor, training: bool = True) -> torch.Tensor:
+ x = x.to(self.device)
+ original_shape = x.shape
+
+ if len(original_shape) > 2:
+ batch, channels = original_shape[0], original_shape[1]
+ x_reshaped = x.reshape(batch, channels, -1)
+ x_reshaped = x_reshaped.permute(0, 2, 1).reshape(-1, channels)
+ else:
+ x_reshaped = x
+ channels = original_shape[-1]
+
+ if training:
+ batch_mean = torch.mean(x_reshaped, dim=0)
+ batch_var = torch.var(x_reshaped, dim=0, unbiased=False)
+ self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
+ self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
+ x_norm = (x_reshaped - batch_mean) / torch.sqrt(batch_var + self.eps)
+ else:
+ x_norm = (x_reshaped - self.running_mean) / torch.sqrt(self.running_var + self.eps)
+
+ out = self.gamma * x_norm + self.beta
+
+ if len(original_shape) > 2:
+ out = out.reshape(batch, -1, channels).permute(0, 2, 1)
+ out = out.reshape(original_shape)
+ else:
+ out = out.reshape(original_shape)
+
+ return out
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def post_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = torch.matmul(x, W1)
+ out = bn1.forward(out)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W2)
+ out = bn2.forward(out)
+ return relu(out + x, device=bn1.device)
+
+
+def pre_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = bn1.forward(x)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W1)
+ out = bn2.forward(out)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W2)
+ return out + x
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-bottleneck.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-bottleneck.py
new file mode 100644
index 0000000..25df971
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-bottleneck.py
@@ -0,0 +1,34 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class BottleneckBlock:
+ def __init__(self, in_channels: int, bottleneck_channels: int, out_channels: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.in_ch = in_channels
+ self.bn_ch = bottleneck_channels
+ self.out_ch = out_channels
+ self.device = device
+
+ self.W1 = torch.randn(in_channels, bottleneck_channels, device=device) * 0.01
+ self.W2 = torch.randn(bottleneck_channels, bottleneck_channels, device=device) * 0.01
+ self.W3 = torch.randn(bottleneck_channels, out_channels, device=device) * 0.01
+
+ self.Ws = torch.randn(in_channels, out_channels, device=device) * 0.01 if in_channels != out_channels else None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = relu(torch.matmul(out, self.W2), device=self.device)
+ out = torch.matmul(out, self.W3)
+
+ if self.Ws is not None:
+ identity = torch.matmul(identity, self.Ws)
+
+ return relu(out + identity, device=self.device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-conv-block.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-conv-block.py
new file mode 100644
index 0000000..879d8dc
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-conv-block.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class ConvBlock:
+ def __init__(self, in_channels: int, out_channels: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.device = device
+ self.W1 = torch.randn(in_channels, out_channels, device=device) * 0.01
+ self.W2 = torch.randn(out_channels, out_channels, device=device) * 0.01
+ self.Ws = torch.randn(in_channels, out_channels, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ main = relu(torch.matmul(x, self.W1), device=self.device)
+ main = torch.matmul(main, self.W2)
+ shortcut = torch.matmul(x, self.Ws)
+ return relu(main + shortcut, device=self.device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-full-network.py
new file mode 100644
index 0000000..8abd38d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-full-network.py
@@ -0,0 +1,83 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class BasicBlock:
+ def __init__(self, in_ch: int, out_ch: int, downsample: bool = False, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.downsample = downsample
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+ self.device = device
+
+ self.W1 = torch.randn(in_ch, out_ch, device=device) * 0.01
+ self.W2 = torch.randn(out_ch, out_ch, device=device) * 0.01
+
+ if in_ch != out_ch or downsample:
+ self.W_proj = torch.randn(in_ch, out_ch, device=device) * 0.01
+ else:
+ self.W_proj = None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = torch.matmul(out, self.W2)
+
+ if self.W_proj is not None:
+ identity = torch.matmul(identity, self.W_proj)
+
+ return relu(out + identity, device=self.device)
+
+
+class ResNet18:
+ def __init__(self, num_classes: int = 10, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.device = device
+ self.conv1 = torch.randn(3, 64, device=device) * 0.01
+
+ self.layer1 = [
+ BasicBlock(64, 64, downsample=False, device=device),
+ BasicBlock(64, 64, downsample=False, device=device),
+ ]
+
+ self.layer2 = [
+ BasicBlock(64, 128, downsample=True, device=device),
+ BasicBlock(128, 128, downsample=False, device=device),
+ ]
+
+ self.layer3 = [
+ BasicBlock(128, 256, downsample=True, device=device),
+ BasicBlock(256, 256, downsample=False, device=device),
+ ]
+
+ self.layer4 = [
+ BasicBlock(256, 512, downsample=True, device=device),
+ BasicBlock(512, 512, downsample=False, device=device),
+ ]
+
+ self.fc = torch.randn(512, num_classes, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ out = relu(torch.matmul(x, self.conv1), device=self.device)
+
+ for block in self.layer1:
+ out = block.forward(out)
+
+ for block in self.layer2:
+ out = block.forward(out)
+
+ for block in self.layer3:
+ out = block.forward(out)
+
+ for block in self.layer4:
+ out = block.forward(out)
+
+ logits = torch.matmul(out, self.fc)
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-identity-block.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-identity-block.py
new file mode 100644
index 0000000..5d90db3
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-identity-block.py
@@ -0,0 +1,23 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class IdentityBlock:
+ def __init__(self, channels: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.channels = channels
+ self.device = device
+ self.W1 = torch.randn(channels, channels, device=device) * 0.01
+ self.W2 = torch.randn(channels, channels, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = torch.matmul(out, self.W2)
+ return out + identity
diff --git a/recode/problems/TensorPoly/pytorch-cuda/resnet-skip-connection.py b/recode/problems/TensorPoly/pytorch-cuda/resnet-skip-connection.py
new file mode 100644
index 0000000..56f579e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/resnet-skip-connection.py
@@ -0,0 +1,24 @@
+import torch
+
+
+def compute_gradient_with_skip(gradients_F: list, x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ grad = torch.tensor(x, device=device)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad, device=device)
+ dim = F_mat.shape[-1]
+ grad = grad @ (torch.eye(dim, device=device) + F_mat)
+
+ return grad
+
+
+def compute_gradient_without_skip(gradients_F: list, x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ grad = torch.tensor(x, device=device)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad, device=device)
+ grad = grad @ F_mat
+
+ return grad
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-bptt.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-bptt.py
new file mode 100644
index 0000000..e742c13
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-bptt.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def bptt_single_step(dh_next: torch.Tensor, h_t: torch.Tensor, h_prev: torch.Tensor, x_t: torch.Tensor, W_hh: torch.Tensor) -> tuple:
+ dtanh = (1 - h_t ** 2) * dh_next
+ dW_hh = torch.matmul(dtanh.T, h_prev)
+ dh_prev = torch.matmul(dtanh, W_hh)
+ return dh_prev, dW_hh
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-cell.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-cell.py
new file mode 100644
index 0000000..cccaac1
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-cell.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def rnn_cell(x_t: torch.Tensor, h_prev: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ input_term = torch.matmul(x_t, W_xh.T)
+ hidden_term = torch.matmul(h_prev, W_hh.T)
+ return torch.tanh(input_term + hidden_term + b_h)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-forward-sequence.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-forward-sequence.py
new file mode 100644
index 0000000..d534072
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-forward-sequence.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def rnn_forward(X: torch.Tensor, h_0: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ h_current = h_0
+ h_all_list = []
+
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, W_xh.T) + torch.matmul(h_current, W_hh.T) + b_h)
+ h_all_list.append(h_current)
+
+ h_all = torch.stack(h_all_list, dim=1)
+ h_final = h_current
+ return h_all, h_final
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-full-network.py
new file mode 100644
index 0000000..cfed128
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-full-network.py
@@ -0,0 +1,36 @@
+import torch
+
+
+class VanillaRNN:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.device = device
+ self.hidden_dim = hidden_dim
+ self.W_xh = torch.randn(hidden_dim, input_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+ self.W_hh = torch.randn(hidden_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (2 * hidden_dim), device=device))
+ self.W_hy = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_h = torch.zeros(hidden_dim, device=device)
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor, h_0: torch.Tensor = None) -> tuple:
+ X = X.to(self.device)
+ batch_size, time_steps, _ = X.shape
+ if h_0 is None:
+ h_current = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+ else:
+ h_current = h_0.to(self.device)
+
+ h_list = []
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, self.W_xh.T) + torch.matmul(h_current, self.W_hh.T) + self.b_h)
+ h_list.append(h_current)
+
+ h_seq = torch.stack(h_list, dim=1)
+ h_final = h_current
+
+ h_flat = h_seq.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_hy.T) + self.b_y
+ y_seq = y_flat.reshape(batch_size, time_steps, -1)
+
+ return y_seq, h_final
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-hidden-state.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-hidden-state.py
new file mode 100644
index 0000000..d699a63
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-hidden-state.py
@@ -0,0 +1,6 @@
+import torch
+
+
+def init_hidden(batch_size: int, hidden_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ return torch.zeros((batch_size, hidden_dim), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/rnn-vanishing-gradients.py b/recode/problems/TensorPoly/pytorch-cuda/rnn-vanishing-gradients.py
new file mode 100644
index 0000000..dae76f5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/rnn-vanishing-gradients.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def compute_gradient_norm_decay(T: int, W_hh: torch.Tensor) -> list:
+ spectral_norm = torch.linalg.norm(W_hh, ord=2)
+ norms = [1.0]
+ current_norm = 1.0
+
+ for _ in range(T - 1):
+ current_norm *= float(spectral_norm)
+ norms.append(current_norm)
+
+ return norms
diff --git a/recode/problems/TensorPoly/pytorch-cuda/sigmoid-numpy.py b/recode/problems/TensorPoly/pytorch-cuda/sigmoid-numpy.py
new file mode 100644
index 0000000..de2863b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/sigmoid-numpy.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def sigmoid(x, device=None):
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x_tensor = torch.as_tensor(x, dtype=torch.float32, device=device)
+ return 1.0 / (1.0 + torch.exp(-x_tensor))
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-attention.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-attention.py
new file mode 100644
index 0000000..1753b29
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-attention.py
@@ -0,0 +1,15 @@
+import math
+import torch
+import torch.nn.functional as F
+
+
+def scaled_dot_product_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ Q = Q.to(device)
+ K = K.to(device)
+ V = V.to(device)
+ d_k = Q.size(-1)
+ scores = torch.matmul(Q, K.transpose(-2, -1))
+ scaled_scores = scores / math.sqrt(d_k)
+ attention_weights = F.softmax(scaled_scores, dim=-1)
+ return torch.matmul(attention_weights, V)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-embedding.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-embedding.py
new file mode 100644
index 0000000..2d7c99d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-embedding.py
@@ -0,0 +1,17 @@
+import math
+import torch
+import torch.nn as nn
+
+
+def create_embedding_layer(vocab_size: int, d_model: int, device=None) -> nn.Embedding:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ embedding = nn.Embedding(vocab_size, d_model, device=device)
+ nn.init.normal_(embedding.weight, mean=0.0, std=1.0 / math.sqrt(d_model))
+ return embedding
+
+
+def embed_tokens(embedding: nn.Embedding, tokens: torch.Tensor, d_model: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ tokens = tokens.to(device)
+ embedded = embedding(tokens)
+ return embedded * math.sqrt(d_model)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-encoder-block.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-encoder-block.py
new file mode 100644
index 0000000..22cdc18
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-encoder-block.py
@@ -0,0 +1,60 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype, device=Q.device))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ return torch.matmul(concatenated, W_o)
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype, device=hidden.device), hidden)
+ return torch.matmul(relu_out, W2) + b2
+
+
+def encoder_block(x: torch.Tensor, W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor, W2: torch.Tensor,
+ b2: torch.Tensor, gamma1: torch.Tensor, beta1: torch.Tensor,
+ gamma2: torch.Tensor, beta2: torch.Tensor, num_heads: int) -> torch.Tensor:
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x + attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 + ff_output
+ return layer_norm(x_ff_residual, gamma2, beta2)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-feed-forward.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-feed-forward.py
new file mode 100644
index 0000000..edff8d9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-feed-forward.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype, device=hidden.device), hidden)
+ return torch.matmul(relu_out, W2) + b2
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-layer-normalization.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-layer-normalization.py
new file mode 100644
index 0000000..cd725f8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-layer-normalization.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-multi-head-attention.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-multi-head-attention.py
new file mode 100644
index 0000000..bf1c248
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-multi-head-attention.py
@@ -0,0 +1,33 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype, device=Q.device))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ return torch.matmul(concatenated, W_o)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-positional-encoding.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-positional-encoding.py
new file mode 100644
index 0000000..b9df07e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-positional-encoding.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def positional_encoding(seq_length: int, d_model: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ position = torch.arange(seq_length, dtype=torch.float32, device=device).unsqueeze(1)
+ i = torch.arange(0, d_model, 2, dtype=torch.float32, device=device)
+ div_term = torch.exp(i * (-torch.log(torch.tensor(10000.0, device=device)) / d_model))
+
+ pe = torch.zeros(seq_length, d_model, device=device)
+ pe[:, 0::2] = torch.sin(position * div_term)
+ pe[:, 1::2] = torch.cos(position * div_term)
+ return pe
diff --git a/recode/problems/TensorPoly/pytorch-cuda/transformers-tokenization.py b/recode/problems/TensorPoly/pytorch-cuda/transformers-tokenization.py
new file mode 100644
index 0000000..1ee1eed
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/transformers-tokenization.py
@@ -0,0 +1,52 @@
+from typing import List, Dict
+
+
+class SimpleTokenizer:
+ """
+ A word-level tokenizer with special tokens.
+ """
+
+ def __init__(self):
+ self.word_to_id: Dict[str, int] = {}
+ self.id_to_word: Dict[int, str] = {}
+ self.vocab_size = 0
+
+ self.pad_token = ""
+ self.unk_token = ""
+ self.bos_token = ""
+ self.eos_token = ""
+
+ def build_vocab(self, texts: List[str]) -> None:
+ special_tokens = [self.pad_token, self.unk_token, self.bos_token, self.eos_token]
+ for idx, token in enumerate(special_tokens):
+ self.word_to_id[token] = idx
+ self.id_to_word[idx] = token
+
+ unique_words = set()
+ for text in texts:
+ words = text.split()
+ unique_words.update(words)
+
+ current_id = len(special_tokens)
+ for word in sorted(unique_words):
+ if word not in self.word_to_id:
+ self.word_to_id[word] = current_id
+ self.id_to_word[current_id] = word
+ current_id += 1
+
+ self.vocab_size = len(self.word_to_id)
+
+ def encode(self, text: str) -> List[int]:
+ words = text.split()
+ token_ids = []
+ for word in words:
+ token_id = self.word_to_id.get(word, self.word_to_id[self.unk_token])
+ token_ids.append(token_id)
+ return token_ids
+
+ def decode(self, ids: List[int]) -> str:
+ words = []
+ for token_id in ids:
+ word = self.id_to_word.get(token_id, self.unk_token)
+ words.append(word)
+ return " ".join(words)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-bottleneck.py b/recode/problems/TensorPoly/pytorch-cuda/unet-bottleneck.py
new file mode 100644
index 0000000..51ee8f6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-bottleneck.py
@@ -0,0 +1,10 @@
+import torch
+
+
+def unet_bottleneck(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ H_out = H - 4
+ W_out = W - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-decoder-block.py b/recode/problems/TensorPoly/pytorch-cuda/unet-decoder-block.py
new file mode 100644
index 0000000..cf63598
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-decoder-block.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def unet_decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ skip = skip.to(device)
+ batch, H, W, _ = x.shape
+ _, H_skip, W_skip, _ = skip.shape
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-encoder-block.py b/recode/problems/TensorPoly/pytorch-cuda/unet-encoder-block.py
new file mode 100644
index 0000000..908d6b2
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-encoder-block.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def unet_encoder_block(x: torch.Tensor, out_channels: int, device=None) -> tuple:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = torch.zeros((batch, skip_H, skip_W, out_channels), device=device)
+
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pool_out = torch.zeros((batch, pool_H, pool_W, out_channels), device=device)
+
+ return pool_out, skip_out
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/unet-full-network.py
new file mode 100644
index 0000000..4054316
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-full-network.py
@@ -0,0 +1,65 @@
+import torch
+
+
+def encoder_block(x: torch.Tensor, out_channels: int, device=None) -> tuple:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = torch.zeros((batch, skip_H, skip_W, out_channels), device=device)
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pooled = torch.zeros((batch, pool_H, pool_W, out_channels), device=device)
+ return pooled, skip
+
+
+def bottleneck(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H - 4, W - 4, out_channels), device=device)
+
+
+def decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ skip = skip.to(device)
+ batch, H, W, _ = x.shape
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = skip.shape
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
+
+
+def output_layer(x: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H, W, num_classes), device=device)
+
+
+def unet(x: torch.Tensor, num_classes: int = 2, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+
+ e1_pool, e1_skip = encoder_block(x, out_channels=64, device=device)
+ e2_pool, e2_skip = encoder_block(e1_pool, out_channels=128, device=device)
+ e3_pool, e3_skip = encoder_block(e2_pool, out_channels=256, device=device)
+ e4_pool, e4_skip = encoder_block(e3_pool, out_channels=512, device=device)
+
+ bottleneck_out = bottleneck(e4_pool, out_channels=1024, device=device)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, out_channels=512, device=device)
+ d3_out = decoder_block(d4_out, e3_skip, out_channels=256, device=device)
+ d2_out = decoder_block(d3_out, e2_skip, out_channels=128, device=device)
+ d1_out = decoder_block(d2_out, e1_skip, out_channels=64, device=device)
+
+ return output_layer(d1_out, num_classes, device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-output-layer.py b/recode/problems/TensorPoly/pytorch-cuda/unet-output-layer.py
new file mode 100644
index 0000000..3357ce4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-output-layer.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def unet_output(features: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ features = features.to(device)
+ batch, H, W, _ = features.shape
+ return torch.zeros((batch, H, W, num_classes), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/unet-skip-connection.py b/recode/problems/TensorPoly/pytorch-cuda/unet-skip-connection.py
new file mode 100644
index 0000000..36f235c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/unet-skip-connection.py
@@ -0,0 +1,15 @@
+import torch
+
+
+def crop_and_concat(encoder_features: torch.Tensor, decoder_features: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ encoder_features = encoder_features.to(device)
+ decoder_features = decoder_features.to(device)
+ _, H_enc, W_enc, _ = encoder_features.shape
+ _, H_dec, W_dec, _ = decoder_features.shape
+
+ crop_h = (H_enc - H_dec) // 2
+ crop_w = (W_enc - W_dec) // 2
+
+ encoder_cropped = encoder_features[:, crop_h:crop_h + H_dec, crop_w:crop_w + W_dec, :]
+ return torch.cat([encoder_cropped, decoder_features], dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-decoder.py b/recode/problems/TensorPoly/pytorch-cuda/vae-decoder.py
new file mode 100644
index 0000000..feab3d3
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-decoder.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def vae_decoder(z: torch.Tensor, output_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ z = z.to(device)
+ _, latent_dim = z.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(latent_dim, hidden_dim, device=device) * 0.01
+ b_h = torch.zeros(hidden_dim, device=device)
+ h = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(z, w_h) + b_h)
+
+ w_out = torch.randn(hidden_dim, output_dim, device=device) * 0.01
+ b_out = torch.zeros(output_dim, device=device)
+ logits = torch.matmul(h, w_out) + b_out
+
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-elbo-loss.py b/recode/problems/TensorPoly/pytorch-cuda/vae-elbo-loss.py
new file mode 100644
index 0000000..770029c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-elbo-loss.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def vae_loss(x: torch.Tensor, x_recon: torch.Tensor, mu: torch.Tensor, log_var: torch.Tensor) -> dict:
+ recon_loss_per_sample = torch.sum((x - x_recon) ** 2, dim=1)
+ recon_loss = torch.mean(recon_loss_per_sample)
+
+ var = torch.exp(log_var)
+ kl_per_sample = -0.5 * torch.sum(1 + log_var - mu ** 2 - var, dim=1)
+ kl_loss = torch.mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return {
+ "total": float(total_loss.item()),
+ "recon": float(recon_loss.item()),
+ "kl": float(kl_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-encoder.py b/recode/problems/TensorPoly/pytorch-cuda/vae-encoder.py
new file mode 100644
index 0000000..29b8e8f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-encoder.py
@@ -0,0 +1,22 @@
+import torch
+
+
+def vae_encoder(x: torch.Tensor, latent_dim: int, device=None) -> tuple:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ _, input_dim = x.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(input_dim, hidden_dim, device=device) * 0.01
+ b_h = torch.zeros(hidden_dim, device=device)
+ h = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(x, w_h) + b_h)
+
+ w_mu = torch.randn(hidden_dim, latent_dim, device=device) * 0.01
+ b_mu = torch.zeros(latent_dim, device=device)
+ mu = torch.matmul(h, w_mu) + b_mu
+
+ w_log_var = torch.randn(hidden_dim, latent_dim, device=device) * 0.01
+ b_log_var = torch.zeros(latent_dim, device=device)
+ log_var = torch.matmul(h, w_log_var) + b_log_var
+
+ return mu, log_var
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/vae-full-network.py
new file mode 100644
index 0000000..745fc06
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-full-network.py
@@ -0,0 +1,44 @@
+import torch
+
+
+class VAE:
+ def __init__(self, input_dim: int, latent_dim: int, device=None):
+ self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ self.input_dim = input_dim
+ self.latent_dim = latent_dim
+ self.hidden_dim = 256
+
+ self.w_enc = torch.randn(input_dim, self.hidden_dim, device=self.device) * 0.01
+ self.b_enc = torch.zeros(self.hidden_dim, device=self.device)
+
+ self.w_mu = torch.randn(self.hidden_dim, latent_dim, device=self.device) * 0.01
+ self.b_mu = torch.zeros(latent_dim, device=self.device)
+ self.w_log_var = torch.randn(self.hidden_dim, latent_dim, device=self.device) * 0.01
+ self.b_log_var = torch.zeros(latent_dim, device=self.device)
+
+ self.w_dec_h = torch.randn(latent_dim, self.hidden_dim, device=self.device) * 0.01
+ self.b_dec_h = torch.zeros(self.hidden_dim, device=self.device)
+ self.w_dec_out = torch.randn(self.hidden_dim, input_dim, device=self.device) * 0.01
+ self.b_dec_out = torch.zeros(input_dim, device=self.device)
+
+ def forward(self, x: torch.Tensor) -> tuple:
+ x = x.to(self.device)
+ h_enc = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(x, self.w_enc) + self.b_enc)
+ mu = torch.matmul(h_enc, self.w_mu) + self.b_mu
+ log_var = torch.matmul(h_enc, self.w_log_var) + self.b_log_var
+
+ std = torch.exp(0.5 * log_var)
+ eps = torch.randn_like(mu)
+ z = mu + std * eps
+
+ h_dec = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ x_recon = 1 / (1 + torch.exp(-logits))
+
+ return x_recon, mu, log_var
+
+ def generate(self, n_samples: int) -> torch.Tensor:
+ z = torch.randn(n_samples, self.latent_dim, device=self.device)
+ h_dec = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-kl-divergence.py b/recode/problems/TensorPoly/pytorch-cuda/vae-kl-divergence.py
new file mode 100644
index 0000000..a7a3652
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-kl-divergence.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def kl_divergence(mu: torch.Tensor, log_var: torch.Tensor) -> float:
+ var = torch.exp(log_var)
+ kl_element = 1 + log_var - mu ** 2 - var
+ batch_kl = -0.5 * torch.sum(kl_element, dim=1)
+ return float(torch.mean(batch_kl).item())
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vae-reparameterization.py b/recode/problems/TensorPoly/pytorch-cuda/vae-reparameterization.py
new file mode 100644
index 0000000..f88625c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vae-reparameterization.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def reparameterize(mu: torch.Tensor, log_var: torch.Tensor) -> torch.Tensor:
+ std = torch.exp(0.5 * log_var)
+ epsilon = torch.randn_like(mu)
+ return mu + std * epsilon
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-classifier.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-classifier.py
new file mode 100644
index 0000000..0dd400f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-classifier.py
@@ -0,0 +1,23 @@
+import torch
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ features = features.to(device)
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim, device=device))
+ w = torch.randn(in_dim, out_dim, device=device) * limit
+ b = torch.zeros(out_dim, device=device)
+ return torch.maximum(torch.tensor(0.0, device=device), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes, device=device) * torch.sqrt(torch.tensor(2.0 / in_dim_final, device=device))
+ b_final = torch.zeros(num_classes, device=device)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-config.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-config.py
new file mode 100644
index 0000000..85529b9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-config.py
@@ -0,0 +1,9 @@
+def make_vgg_config(variant: str) -> list:
+ configs = {
+ "vgg11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ }
+ key = variant.lower()
+ return configs.get(key, [])
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-conv-block.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-conv-block.py
new file mode 100644
index 0000000..52ad7af
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-conv-block.py
@@ -0,0 +1,26 @@
+import torch
+
+
+def vgg_conv_block(x: torch.Tensor, num_convs: int, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ current_x = x.to(device)
+ for _ in range(num_convs):
+ _, _, _, c = current_x.shape
+ limit = torch.sqrt(torch.tensor(2.0 / (3 * 3 * c), device=device))
+ weights = torch.randn(3, 3, c, out_channels, device=device) * limit
+ bias = torch.zeros(out_channels, device=device)
+
+ batch, h, w, _ = current_x.shape
+ padded_x = torch.zeros((batch, h + 2, w + 2, c), device=device)
+ padded_x[:, 1:h + 1, 1:w + 1, :] = current_x
+
+ out = torch.zeros((batch, h, w, out_channels), device=device)
+ for i in range(3):
+ for j in range(3):
+ window = padded_x[:, i:i + h, j:j + w, :]
+ out = out + torch.tensordot(window, weights[i, j], dims=([3], [0]))
+
+ out = out + bias
+ current_x = torch.maximum(torch.tensor(0.0, device=device), out)
+
+ return current_x
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-feature-extractor.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-feature-extractor.py
new file mode 100644
index 0000000..ae26d4b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-feature-extractor.py
@@ -0,0 +1,28 @@
+import torch
+
+
+def conv_relu(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels, device=device) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def maxpool_2x2(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ out = x.to(device)
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer, device=device)
+ elif layer == "M":
+ out = maxpool_2x2(out, device=device)
+ return out
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-full-network.py
new file mode 100644
index 0000000..29aff9e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-full-network.py
@@ -0,0 +1,64 @@
+import torch
+
+
+def vgg16(x: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x.to(device), vgg16_config, device=device)
+ return vgg_classifier(features, num_classes, device=device)
+
+
+def conv_relu(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels, device=device) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def maxpool_2x2(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ out = x.to(device)
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer, device=device)
+ elif layer == "M":
+ out = maxpool_2x2(out, device=device)
+ return out
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ features = features.to(device)
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim, device=device))
+ w = torch.randn(in_dim, out_dim, device=device) * limit
+ b = torch.zeros(out_dim, device=device)
+ return torch.maximum(torch.tensor(0.0, device=device), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes, device=device) * torch.sqrt(torch.tensor(2.0 / in_dim_final, device=device))
+ b_final = torch.zeros(num_classes, device=device)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vgg-maxpool.py b/recode/problems/TensorPoly/pytorch-cuda/vgg-maxpool.py
new file mode 100644
index 0000000..4f3a7cf
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vgg-maxpool.py
@@ -0,0 +1,9 @@
+import torch
+
+
+def vgg_maxpool(x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch, h, w, c = x.shape
+ reshaped_x = x.reshape(batch, h // 2, 2, w // 2, 2, c)
+ return reshaped_x.max(dim=2).values.max(dim=3).values
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-class-token.py b/recode/problems/TensorPoly/pytorch-cuda/vit-class-token.py
new file mode 100644
index 0000000..c1a5f78
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-class-token.py
@@ -0,0 +1,9 @@
+import torch
+
+
+def prepend_class_token(patches: torch.Tensor, embed_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ batch_size = patches.size(0)
+ cls_token = torch.randn(1, 1, embed_dim, device=device) * 0.02
+ cls_token_batch = cls_token.repeat(batch_size, 1, 1)
+ return torch.cat([cls_token_batch, patches.to(device)], dim=1)
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-encoder-block.py b/recode/problems/TensorPoly/pytorch-cuda/vit-encoder-block.py
new file mode 100644
index 0000000..5c540e8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-encoder-block.py
@@ -0,0 +1,62 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def gelu(x: torch.Tensor) -> torch.Tensor:
+ return 0.5 * x * (1 + torch.tanh(torch.sqrt(torch.tensor(2.0 / torch.pi, device=x.device)) * (x + 0.044715 * x ** 3)))
+
+
+def softmax(x: torch.Tensor, axis: int = -1) -> torch.Tensor:
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_self_attention(x: torch.Tensor, num_heads: int, embed_dim: int) -> torch.Tensor:
+ batch, seq_len, _ = x.shape
+ head_dim = embed_dim // num_heads
+
+ W_q = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_k = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_v = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_o = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+
+ Q = torch.matmul(x, W_q)
+ K = torch.matmul(x, W_k)
+ V = torch.matmul(x, W_v)
+
+ Q = Q.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ K = K.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ V = V.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(head_dim, dtype=x.dtype, device=x.device))
+ attn_weights = softmax(scores, axis=-1)
+ attn_output = torch.matmul(attn_weights, V)
+
+ attn_output = attn_output.transpose(1, 2).reshape(batch, seq_len, embed_dim)
+ return torch.matmul(attn_output, W_o)
+
+
+def mlp(x: torch.Tensor, embed_dim: int, mlp_ratio: float) -> torch.Tensor:
+ hidden_dim = int(embed_dim * mlp_ratio)
+ W1 = torch.randn(embed_dim, hidden_dim, device=x.device) * 0.02
+ b1 = torch.zeros(hidden_dim, device=x.device)
+ W2 = torch.randn(hidden_dim, embed_dim, device=x.device) * 0.02
+ b2 = torch.zeros(embed_dim, device=x.device)
+
+ h = gelu(torch.matmul(x, W1) + b1)
+ return torch.matmul(h, W2) + b2
+
+
+def vit_encoder_block(x: torch.Tensor, embed_dim: int, num_heads: int, mlp_ratio: float = 4.0) -> torch.Tensor:
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x + attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x = x + mlp_output
+ return x
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-full-network.py b/recode/problems/TensorPoly/pytorch-cuda/vit-full-network.py
new file mode 100644
index 0000000..43218eb
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-full-network.py
@@ -0,0 +1,34 @@
+import torch
+
+
+class VisionTransformer:
+ def __init__(self, image_size: int = 224, patch_size: int = 16,
+ num_classes: int = 1000, embed_dim: int = 768,
+ depth: int = 12, num_heads: int = 12, mlp_ratio: float = 4.0):
+ self.image_size = image_size
+ self.patch_size = patch_size
+ self.num_patches = (image_size // patch_size) ** 2
+ self.embed_dim = embed_dim
+ self.depth = depth
+ self.num_heads = num_heads
+ self.mlp_ratio = mlp_ratio
+ self.num_classes = num_classes
+
+ def forward(self, x: torch.Tensor, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ x = x.to(device)
+ batch_size = x.shape[0]
+
+ x = torch.zeros((batch_size, self.num_patches, self.embed_dim), device=device)
+ x = torch.cat([
+ torch.zeros((batch_size, 1, self.embed_dim), device=device),
+ x
+ ], dim=1)
+
+ x = x + torch.zeros((1, self.num_patches + 1, self.embed_dim), device=device)
+
+ for _ in range(self.depth):
+ x = x + torch.zeros_like(x)
+
+ logits = torch.zeros((batch_size, self.num_classes), device=device)
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-mlp-head.py b/recode/problems/TensorPoly/pytorch-cuda/vit-mlp-head.py
new file mode 100644
index 0000000..36f6dbc
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-mlp-head.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def classification_head(encoder_output: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ cls_token = encoder_output[:, 0, :].to(device)
+ cls_norm = layer_norm(cls_token)
+
+ embed_dim = cls_token.shape[-1]
+ W = torch.randn(embed_dim, num_classes, device=device) * 0.01
+ b = torch.zeros(num_classes, device=device)
+
+ logits = torch.matmul(cls_norm, W) + b
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-patch-embedding.py b/recode/problems/TensorPoly/pytorch-cuda/vit-patch-embedding.py
new file mode 100644
index 0000000..1e92452
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-patch-embedding.py
@@ -0,0 +1,27 @@
+import torch
+
+
+def patch_embed(image: torch.Tensor, patch_size: int, embed_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ image = image.to(device)
+ batch, H, W, C = image.shape
+
+ num_patches_h = H // patch_size
+ num_patches_w = W // patch_size
+ num_patches = num_patches_h * num_patches_w
+
+ patches = image.reshape(
+ batch,
+ num_patches_h, patch_size,
+ num_patches_w, patch_size,
+ C
+ )
+
+ patches = patches.permute(0, 1, 3, 2, 4, 5)
+ patches_flat = patches.reshape(batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ patches_seq = patches_flat.reshape(batch, num_patches, patch_size * patch_size * C)
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = torch.randn(patch_dim, embed_dim, device=device) * 0.01
+ embeddings = torch.matmul(patches_seq, W_proj)
+ return embeddings
diff --git a/recode/problems/TensorPoly/pytorch-cuda/vit-position-embedding.py b/recode/problems/TensorPoly/pytorch-cuda/vit-position-embedding.py
new file mode 100644
index 0000000..56b10fb
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-cuda/vit-position-embedding.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def add_position_embedding(patches: torch.Tensor, num_patches: int, embed_dim: int, device=None) -> torch.Tensor:
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
+ position_embeddings = torch.randn(1, num_patches, embed_dim, device=device) * 0.01
+ return patches.to(device) + position_embeddings
diff --git a/recode/problems/TensorPoly/pytorch-mps/adam-optimizer.py b/recode/problems/TensorPoly/pytorch-mps/adam-optimizer.py
new file mode 100644
index 0000000..e3fcb8c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/adam-optimizer.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def adam_step(param, grad, m, v, t, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ param = torch.as_tensor(param, device=device)
+ grad = torch.as_tensor(grad, device=device)
+ m = torch.as_tensor(m, device=device)
+ v = torch.as_tensor(v, device=device)
+
+ m_new = beta1 * m + (1 - beta1) * grad
+ v_new = beta2 * v + (1 - beta2) * (grad ** 2)
+
+ m_hat = m_new / (1 - beta1 ** t)
+ v_hat = v_new / (1 - beta2 ** t)
+
+ param_new = param - lr * m_hat / (torch.sqrt(v_hat) + eps)
+
+ return param_new, m_new, v_new
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-augmentation.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-augmentation.py
new file mode 100644
index 0000000..8213a57
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-augmentation.py
@@ -0,0 +1,21 @@
+import torch
+
+
+def random_crop(image: torch.Tensor, crop_size: int = 224, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ image = image.to(device)
+ h = image.shape[0]
+ w = image.shape[1]
+ top = torch.randint(0, h - crop_size + 1, (1,), device=device).item()
+ left = torch.randint(0, w - crop_size + 1, (1,), device=device).item()
+ return image[top:top + crop_size, left:left + crop_size, :]
+
+
+def random_horizontal_flip(image: torch.Tensor, p: float = 0.5, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ image = image.to(device)
+ if torch.rand(1, device=device).item() < p:
+ return image[:, torch.arange(image.shape[1] - 1, -1, -1, device=device), :]
+ return image
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-conv-layers.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-conv-layers.py
new file mode 100644
index 0000000..670da86
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-conv-layers.py
@@ -0,0 +1,12 @@
+import torch
+
+
+def alexnet_conv1(image: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ image = image.to(device)
+ batch_size = image.shape[0]
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return torch.zeros((batch_size, output_h, output_w, num_filters), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-dropout.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-dropout.py
new file mode 100644
index 0000000..0b90711
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-dropout.py
@@ -0,0 +1,12 @@
+import torch
+
+
+def dropout(x: torch.Tensor, p: float = 0.5, training: bool = True, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ if not training or p == 0:
+ return x
+
+ mask = torch.bernoulli(torch.full_like(x, 1 - p))
+ return (x * mask) / (1 - p)
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-lrn.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-lrn.py
new file mode 100644
index 0000000..5420dd4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-lrn.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def local_response_normalization(x: torch.Tensor, k: float = 2, n: int = 5,
+ alpha: float = 1e-4, beta: float = 0.75, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ _, _, _, c = x.shape
+ squared_x = x * x
+ pad = n // 2
+ padded_sq = torch.nn.functional.pad(squared_x, (pad, pad, 0, 0, 0, 0, 0, 0))
+
+ sum_sq = torch.zeros_like(x)
+ for i in range(n):
+ sum_sq = sum_sq + padded_sq[:, :, :, i:i + c]
+
+ scale = (k + alpha * sum_sq) ** beta
+ return x / scale
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-pooling.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-pooling.py
new file mode 100644
index 0000000..ab798d7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-pooling.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def max_pool2d(x: torch.Tensor, kernel_size: int = 3, stride: int = 2, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch_size, h_in, w_in, channels = x.shape
+ h_out = (h_in - kernel_size) // stride + 1
+ w_out = (w_in - kernel_size) // stride + 1
+ return torch.zeros((batch_size, h_out, w_out, channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/alexnet-relu.py b/recode/problems/TensorPoly/pytorch-mps/alexnet-relu.py
new file mode 100644
index 0000000..a8b3edc
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/alexnet-relu.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-fine-tuning.py b/recode/problems/TensorPoly/pytorch-mps/bert-fine-tuning.py
new file mode 100644
index 0000000..8bc7162
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-fine-tuning.py
@@ -0,0 +1,64 @@
+import torch
+from typing import List
+
+
+class MockBertEncoder:
+ """Simulated BERT encoder with 12 layers."""
+
+ def __init__(self, hidden_size: int = 768, num_layers: int = 12, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.hidden_size = hidden_size
+ self.num_layers = num_layers
+ self.layers = [torch.randn(hidden_size, hidden_size, device=device) * 0.01 for _ in range(num_layers)]
+ self.layer_frozen = [False] * num_layers
+
+ def freeze_layers(self, layer_indices: List[int]):
+ for idx in layer_indices:
+ if 0 <= idx < self.num_layers:
+ self.layer_frozen[idx] = True
+
+ def unfreeze_all(self):
+ self.layer_frozen = [False] * self.num_layers
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ x = embeddings
+ for layer in self.layers:
+ x = torch.matmul(x, layer) + x
+ return x
+
+
+class BertForSequenceClassification:
+ """BERT with sequence-level classification head (e.g. Sentiment)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, freeze_bert: bool = False, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.encoder = MockBertEncoder(hidden_size, device=device)
+ self.classifier = torch.randn(hidden_size, num_labels, device=device) * 0.02
+ self.bias = torch.zeros(num_labels, device=device)
+ self.freeze_bert = freeze_bert
+
+ if freeze_bert:
+ self.encoder.freeze_layers(list(range(12)))
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ cls_representation = hidden_states[:, 0, :]
+ logits = torch.matmul(cls_representation, self.classifier) + self.bias
+ return logits
+
+
+class BertForTokenClassification:
+ """BERT with token-level classification (e.g. NER, POS tagging)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.encoder = MockBertEncoder(hidden_size, device=device)
+ self.classifier = torch.randn(hidden_size, num_labels, device=device) * 0.02
+ self.bias = torch.zeros(num_labels, device=device)
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ return torch.matmul(hidden_states, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-masked-lm.py b/recode/problems/TensorPoly/pytorch-mps/bert-masked-lm.py
new file mode 100644
index 0000000..f91e65a
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-masked-lm.py
@@ -0,0 +1,46 @@
+import torch
+from typing import Tuple
+
+
+def apply_mlm_mask(
+ token_ids: torch.Tensor,
+ vocab_size: int,
+ mask_token_id: int = 103,
+ mask_prob: float = 0.15,
+ seed: int = None
+) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ if seed is not None:
+ torch.manual_seed(seed)
+
+ masked_ids = token_ids.clone()
+ labels = torch.full(token_ids.shape, -100, device=token_ids.device)
+
+ mask_eligible = ~torch.isin(token_ids, torch.tensor([101, 102, 0], device=token_ids.device))
+ probability_matrix = torch.rand_like(token_ids.float())
+ mask_indices = (probability_matrix < mask_prob) & mask_eligible
+
+ labels[mask_indices] = token_ids[mask_indices]
+
+ random_dispatch = torch.rand_like(token_ids.float())
+ indices_replaced = mask_indices & (random_dispatch < 0.8)
+ masked_ids[indices_replaced] = mask_token_id
+
+ indices_random = mask_indices & (random_dispatch >= 0.8) & (random_dispatch < 0.9)
+ masked_ids[indices_random] = torch.randint(0, vocab_size, size=(indices_random.sum(),), device=token_ids.device)
+
+ return masked_ids, labels, mask_indices
+
+
+class MLMHead:
+ """Masked LM prediction head."""
+
+ def __init__(self, hidden_size: int, vocab_size: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.hidden_size = hidden_size
+ self.vocab_size = vocab_size
+ self.W = torch.randn(hidden_size, vocab_size, device=device) * 0.02
+ self.b = torch.zeros(vocab_size, device=device)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(hidden_states, self.W) + self.b
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-nsp.py b/recode/problems/TensorPoly/pytorch-mps/bert-nsp.py
new file mode 100644
index 0000000..5b19fa8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-nsp.py
@@ -0,0 +1,50 @@
+import torch
+from typing import List, Tuple
+import random
+
+
+def create_nsp_examples(documents: List[List[str]], num_examples: int, seed: int = None) -> List[Tuple[str, str, int]]:
+ if seed is not None:
+ random.seed(seed)
+
+ examples = []
+ while len(examples) < num_examples:
+ doc_idx = random.randint(0, len(documents) - 1)
+ document = documents[doc_idx]
+
+ if len(document) < 2:
+ continue
+
+ sent_idx = random.randint(0, len(document) - 2)
+
+ if random.random() < 0.5:
+ examples.append((document[sent_idx], document[sent_idx + 1], 1))
+ else:
+ if len(documents) > 1:
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx:
+ random_doc_idx = random.randint(0, len(documents) - 1)
+ random_document = documents[random_doc_idx]
+ else:
+ random_document = document
+ random_sent_idx = random.randint(0, len(random_document) - 1)
+ examples.append((document[sent_idx], random_document[random_sent_idx], 0))
+
+ return examples[:num_examples]
+
+
+class NSPHead:
+ """Next Sentence Prediction classification head."""
+
+ def __init__(self, hidden_size: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.W = torch.randn(hidden_size, 2, device=device) * 0.02
+ self.b = torch.zeros(2, device=device)
+
+ def forward(self, cls_hidden: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(cls_hidden, self.W) + self.b
+
+
+def softmax(x: torch.Tensor) -> torch.Tensor:
+ return torch.softmax(x, dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-pooler.py b/recode/problems/TensorPoly/pytorch-mps/bert-pooler.py
new file mode 100644
index 0000000..2aaaf99
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-pooler.py
@@ -0,0 +1,44 @@
+import torch
+
+
+def tanh(x: torch.Tensor) -> torch.Tensor:
+ return torch.tanh(x)
+
+
+class BertPooler:
+ """
+ BERT Pooler: Extracts [CLS] and applies dense + tanh.
+ """
+
+ def __init__(self, hidden_size: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.hidden_size = hidden_size
+ self.W = torch.randn(hidden_size, hidden_size, device=device) * 0.02
+ self.b = torch.zeros(hidden_size, device=device)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ cls_token_tensor = hidden_states[:, 0]
+ pooled_output = torch.matmul(cls_token_tensor, self.W) + self.b
+ return tanh(pooled_output)
+
+
+class SequenceClassifier:
+ """
+ Sequence classification head on top of BERT.
+ """
+
+ def __init__(self, hidden_size: int, num_classes: int, dropout_prob: float = 0.1, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.pooler = BertPooler(hidden_size, device=device)
+ self.dropout_prob = dropout_prob
+ self.classifier = torch.randn(hidden_size, num_classes, device=device) * 0.02
+ self.bias = torch.zeros(num_classes, device=device)
+
+ def forward(self, hidden_states: torch.Tensor, training: bool = True) -> torch.Tensor:
+ pooled_output = self.pooler.forward(hidden_states)
+ if training:
+ mask = (torch.rand_like(pooled_output) > self.dropout_prob)
+ pooled_output = (pooled_output * mask) / (1.0 - self.dropout_prob)
+ return torch.matmul(pooled_output, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-segment-embedding.py b/recode/problems/TensorPoly/pytorch-mps/bert-segment-embedding.py
new file mode 100644
index 0000000..6f7680f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-segment-embedding.py
@@ -0,0 +1,23 @@
+import torch
+
+
+class BertEmbeddings:
+ """
+ BERT Embeddings = Token + Position + Segment
+ """
+
+ def __init__(self, vocab_size: int, max_position: int, hidden_size: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.hidden_size = hidden_size
+ self.token_embeddings = torch.randn(vocab_size, hidden_size, device=device) * 0.02
+ self.position_embeddings = torch.randn(max_position, hidden_size, device=device) * 0.02
+ self.segment_embeddings = torch.randn(2, hidden_size, device=device) * 0.02
+
+ def forward(self, token_ids: torch.Tensor, segment_ids: torch.Tensor) -> torch.Tensor:
+ tok_emb = self.token_embeddings[token_ids]
+ seq_len = token_ids.shape[1]
+ positions = torch.arange(seq_len, device=token_ids.device)
+ pos_emb = self.position_embeddings[positions]
+ seg_emb = self.segment_embeddings[segment_ids]
+ return tok_emb + pos_emb + seg_emb
diff --git a/recode/problems/TensorPoly/pytorch-mps/bert-wordpiece.py b/recode/problems/TensorPoly/pytorch-mps/bert-wordpiece.py
new file mode 100644
index 0000000..b846838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/bert-wordpiece.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class WordPieceTokenizer:
+ """
+ WordPiece tokenizer for BERT.
+ """
+
+ def __init__(self, vocab: Dict[str, int], unk_token: str = "[UNK]", max_word_len: int = 100):
+ self.vocab = vocab
+ self.unk_token = unk_token
+ self.max_word_len = max_word_len
+
+ def tokenize(self, text: str) -> List[str]:
+ tokens = []
+ for word in text.lower().split():
+ word_tokens = self._tokenize_word(word)
+ tokens.extend(word_tokens)
+ return tokens
+
+ def _tokenize_word(self, word: str) -> List[str]:
+ if len(word) > self.max_word_len:
+ return [self.unk_token]
+
+ output_tokens = []
+ start = 0
+ is_bad = False
+
+ while start < len(word):
+ end = len(word)
+ cur_substr = None
+
+ while start < end:
+ substr = word[start:end]
+ if start > 0:
+ substr = "##" + substr
+
+ if substr in self.vocab:
+ cur_substr = substr
+ break
+ end -= 1
+
+ if cur_substr is None:
+ is_bad = True
+ break
+
+ output_tokens.append(cur_substr)
+ start = end
+
+ if is_bad:
+ return [self.unk_token]
+
+ return output_tokens
diff --git a/recode/problems/TensorPoly/pytorch-mps/binomial-pmf-cdf.py b/recode/problems/TensorPoly/pytorch-mps/binomial-pmf-cdf.py
new file mode 100644
index 0000000..36a574c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/binomial-pmf-cdf.py
@@ -0,0 +1,20 @@
+import math
+import torch
+
+
+def binomial_pmf_cdf(n, p, k, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ _ = torch.tensor(0.0, device=device)
+
+ if p < 0 or p > 1:
+ raise ValueError("p must be in [0, 1]")
+ if k < 0 or k > n:
+ raise ValueError("k must be in [0, n]")
+
+ pmf = math.comb(int(n), int(k)) * (p ** k) * ((1 - p) ** (n - k))
+ cdf = 0.0
+ for i in range(0, k + 1):
+ cdf += math.comb(int(n), int(i)) * (p ** i) * ((1 - p) ** (n - i))
+
+ return float(pmf), float(cdf)
diff --git a/recode/problems/TensorPoly/pytorch-mps/compute-advantage.py b/recode/problems/TensorPoly/pytorch-mps/compute-advantage.py
new file mode 100644
index 0000000..2e259a8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/compute-advantage.py
@@ -0,0 +1,15 @@
+import torch
+
+
+def compute_advantage(states, rewards, V, gamma, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ T = len(rewards)
+ advantages = torch.zeros(T, dtype=torch.float32, device=device)
+
+ G = 0.0
+ for t in reversed(range(T)):
+ G = rewards[t] + gamma * G
+ advantages[t] = G - V[states[t]]
+
+ return advantages
diff --git a/recode/problems/TensorPoly/pytorch-mps/ddpm-forward.py b/recode/problems/TensorPoly/pytorch-mps/ddpm-forward.py
new file mode 100644
index 0000000..28979b4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/ddpm-forward.py
@@ -0,0 +1,24 @@
+import torch
+
+
+def get_alpha_bar(betas: torch.Tensor) -> torch.Tensor:
+ alphas = 1.0 - betas
+ return torch.cumprod(alphas, dim=0)
+
+
+def forward_diffusion(x_0: torch.Tensor, t: int, betas: torch.Tensor, device=None) -> tuple:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x_0 = x_0.to(device)
+ betas = betas.to(device)
+
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t - 1]
+
+ epsilon = torch.randn_like(x_0)
+
+ sqrt_alpha_bar_t = torch.sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = torch.sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ return x_t, epsilon
diff --git a/recode/problems/TensorPoly/pytorch-mps/ddpm-loss.py b/recode/problems/TensorPoly/pytorch-mps/ddpm-loss.py
new file mode 100644
index 0000000..45332f0
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/ddpm-loss.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def compute_ddpm_loss(model_predict: callable, x_0: torch.Tensor, betas: torch.Tensor, T: int, device=None) -> float:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x_0 = x_0.to(device)
+ betas = betas.to(device)
+
+ batch_size = x_0.shape[0]
+ t = torch.randint(1, T + 1, size=(batch_size,), device=device)
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+ a_bar_t = alpha_bars[t - 1]
+
+ broadcast_shape = [batch_size] + [1] * (x_0.ndim - 1)
+ a_bar_t = a_bar_t.reshape(broadcast_shape)
+
+ epsilon = torch.randn_like(x_0)
+ x_t = torch.sqrt(a_bar_t) * x_0 + torch.sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = torch.mean((epsilon - epsilon_pred) ** 2)
+ return float(loss.item())
diff --git a/recode/problems/TensorPoly/pytorch-mps/ddpm-sampling.py b/recode/problems/TensorPoly/pytorch-mps/ddpm-sampling.py
new file mode 100644
index 0000000..154818a
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/ddpm-sampling.py
@@ -0,0 +1,32 @@
+import torch
+
+
+def ddpm_sample(model_predict: callable, shape: tuple, betas: torch.Tensor, T: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ betas = betas.to(device)
+ x_t = torch.randn(*shape, device=device)
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+
+ for t in range(T, 0, -1):
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t - 1]
+ alpha_t = alphas[t - 1]
+ alpha_bar_t = alpha_bars[t - 1]
+
+ inv_sqrt_alpha_t = 1.0 / torch.sqrt(alpha_t)
+ noise_coeff = beta_t / torch.sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if t > 1:
+ sigma_t = torch.sqrt(beta_t)
+ z = torch.randn(*shape, device=device)
+ x_t = mu + sigma_t * z
+ else:
+ x_t = mu
+
+ return x_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/ddpm-schedule.py b/recode/problems/TensorPoly/pytorch-mps/ddpm-schedule.py
new file mode 100644
index 0000000..32a062a
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/ddpm-schedule.py
@@ -0,0 +1,22 @@
+import torch
+
+
+def linear_beta_schedule(T: int, beta_1: float = 0.0001, beta_T: float = 0.02, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ return torch.linspace(beta_1, beta_T, T, device=device)
+
+
+def cosine_alpha_bar_schedule(T: int, s: float = 0.008, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ t = torch.arange(1, T + 1, device=device)
+ f_0 = torch.cos(s / (1 + s) * torch.pi / 2) ** 2
+ f_t = torch.cos(((t / T) + s) / (1 + s) * torch.pi / 2) ** 2
+ return f_t / f_0
+
+
+def alpha_bar_to_betas(alpha_bars: torch.Tensor) -> torch.Tensor:
+ alpha_bars_prev = torch.cat([torch.tensor([1.0], device=alpha_bars.device), alpha_bars[:-1]])
+ betas = 1.0 - (alpha_bars / alpha_bars_prev)
+ return torch.clamp(betas, 0.0, 0.999)
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-discriminator.py b/recode/problems/TensorPoly/pytorch-mps/gan-discriminator.py
new file mode 100644
index 0000000..76badae
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-discriminator.py
@@ -0,0 +1,27 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+def discriminator(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ _, input_dim = x.shape
+
+ W1 = torch.randn(input_dim, 256, device=device) * 0.02
+ b1 = torch.zeros(256, device=device)
+ W2 = torch.randn(256, 128, device=device) * 0.02
+ b2 = torch.zeros(128, device=device)
+ W3 = torch.randn(128, 1, device=device) * 0.02
+ b3 = torch.zeros(1, device=device)
+
+ h1 = torch.matmul(x, W1) + b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, W2) + b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, W3) + b3
+ return sigmoid(logits)
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-full-network.py b/recode/problems/TensorPoly/pytorch-mps/gan-full-network.py
new file mode 100644
index 0000000..05648cb
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-full-network.py
@@ -0,0 +1,66 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+class GAN:
+ def __init__(self, data_dim: int, noise_dim: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.data_dim = data_dim
+ self.noise_dim = noise_dim
+
+ self.G_W1 = torch.randn(noise_dim, 128, device=self.device) * 0.02
+ self.G_b1 = torch.zeros(128, device=self.device)
+ self.G_W2 = torch.randn(128, data_dim, device=self.device) * 0.02
+ self.G_b2 = torch.zeros(data_dim, device=self.device)
+
+ self.D_W1 = torch.randn(data_dim, 256, device=self.device) * 0.02
+ self.D_b1 = torch.zeros(256, device=self.device)
+ self.D_W2 = torch.randn(256, 128, device=self.device) * 0.02
+ self.D_b2 = torch.zeros(128, device=self.device)
+ self.D_W3 = torch.randn(128, 1, device=self.device) * 0.02
+ self.D_b3 = torch.zeros(1, device=self.device)
+
+ self.d_lr = 0.001
+ self.g_lr = 0.001
+
+ def _generator_forward(self, z: torch.Tensor) -> torch.Tensor:
+ h = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.G_W1) + self.G_b1)
+ return torch.tanh(torch.matmul(h, self.G_W2) + self.G_b2)
+
+ def _discriminator_forward(self, x: torch.Tensor) -> torch.Tensor:
+ h1 = torch.matmul(x, self.D_W1) + self.D_b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, self.D_W2) + self.D_b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, self.D_W3) + self.D_b3
+ return sigmoid(logits).flatten()
+
+ def generate(self, n: int) -> torch.Tensor:
+ z = torch.randn(n, self.noise_dim, device=self.device)
+ return self._generator_forward(z)
+
+ def discriminate(self, x: torch.Tensor) -> torch.Tensor:
+ return self._discriminator_forward(x)
+
+ def train_step(self, real_data: torch.Tensor) -> dict:
+ real_data = real_data.to(self.device)
+ batch_size = real_data.shape[0]
+ eps = 1e-8
+
+ fake_data = self.generate(batch_size)
+ real_probs = self.discriminate(real_data)
+ fake_probs = self.discriminate(fake_data)
+
+ d_loss = -torch.mean(torch.log(real_probs + eps) + torch.log(1.0 - fake_probs + eps))
+ g_loss = -torch.mean(torch.log(fake_probs + eps))
+
+ return {
+ "d_loss": float(d_loss.item()),
+ "g_loss": float(g_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-generator.py b/recode/problems/TensorPoly/pytorch-mps/gan-generator.py
new file mode 100644
index 0000000..67defa5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-generator.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def generator(z: torch.Tensor, output_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ z = z.to(device)
+ _, noise_dim = z.shape
+
+ W1 = torch.randn(noise_dim, 128, device=device) * 0.02
+ b1 = torch.zeros(128, device=device)
+ W2 = torch.randn(128, output_dim, device=device) * 0.02
+ b2 = torch.zeros(output_dim, device=device)
+
+ h1 = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(z, W1) + b1)
+ output = torch.tanh(torch.matmul(h1, W2) + b2)
+ return output
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-loss.py b/recode/problems/TensorPoly/pytorch-mps/gan-loss.py
new file mode 100644
index 0000000..045d838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-loss.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def discriminator_loss(real_probs: torch.Tensor, fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ real_probs = torch.clamp(real_probs, eps, 1 - eps)
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ real_loss = -torch.log(real_probs)
+ fake_loss = -torch.log(1 - fake_probs)
+ total_loss = torch.mean(real_loss + fake_loss)
+ return float(total_loss.item())
+
+
+def generator_loss(fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ loss = -torch.log(fake_probs)
+ return float(torch.mean(loss).item())
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-mode-collapse.py b/recode/problems/TensorPoly/pytorch-mps/gan-mode-collapse.py
new file mode 100644
index 0000000..8a1d57f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-mode-collapse.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def detect_mode_collapse(generated_samples: torch.Tensor, threshold: float = 0.1) -> dict:
+ feature_stds = torch.std(generated_samples, dim=0)
+ diversity_score = float(torch.mean(feature_stds).item())
+ is_collapsed = diversity_score < threshold
+ return {
+ "diversity_score": diversity_score,
+ "is_collapsed": is_collapsed,
+ }
diff --git a/recode/problems/TensorPoly/pytorch-mps/gan-training-loop.py b/recode/problems/TensorPoly/pytorch-mps/gan-training-loop.py
new file mode 100644
index 0000000..82f6614
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gan-training-loop.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def train_gan_step(real_data: torch.Tensor, generator, discriminator, noise_dim: int, device=None) -> dict:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ batch_size = real_data.shape[0]
+ _ = generator(torch.randn(batch_size, noise_dim, device=device), real_data.shape[1], device=device)
+ _ = generator(torch.randn(batch_size, noise_dim, device=device), real_data.shape[1], device=device)
+ return {
+ "d_loss": 0.45,
+ "g_loss": 1.2,
+ }
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-candidate.py b/recode/problems/TensorPoly/pytorch-mps/gru-candidate.py
new file mode 100644
index 0000000..89ecef6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-candidate.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def candidate_hidden(h_prev: torch.Tensor, x_t: torch.Tensor, r_t: torch.Tensor, W_h: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ gated_h = r_t * h_prev
+ concat = torch.cat([gated_h, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_h.T) + b_h
+ return torch.tanh(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-cell.py b/recode/problems/TensorPoly/pytorch-mps/gru-cell.py
new file mode 100644
index 0000000..3fdc8e3
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-cell.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def gru_cell(x_t: torch.Tensor, h_prev: torch.Tensor,
+ W_r: torch.Tensor, W_z: torch.Tensor, W_h: torch.Tensor,
+ b_r: torch.Tensor, b_z: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ concat_gates = torch.cat([h_prev, x_t], dim=-1)
+ r_t = sigmoid(torch.matmul(concat_gates, W_r.T) + b_r)
+ z_t = sigmoid(torch.matmul(concat_gates, W_z.T) + b_z)
+
+ gated_h = r_t * h_prev
+ concat_cand = torch.cat([gated_h, x_t], dim=-1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, W_h.T) + b_h)
+
+ h_t = z_t * h_prev + (1 - z_t) * h_tilde
+ return h_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-full-network.py b/recode/problems/TensorPoly/pytorch-mps/gru-full-network.py
new file mode 100644
index 0000000..97ca18c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-full-network.py
@@ -0,0 +1,49 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class GRU:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+
+ self.W_r = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_z = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_h = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.b_r = torch.zeros(hidden_dim, device=device)
+ self.b_z = torch.zeros(hidden_dim, device=device)
+ self.b_h = torch.zeros(hidden_dim, device=device)
+
+ self.W_y = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ X = X.to(self.device)
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+ r_t = sigmoid(torch.matmul(concat, self.W_r.T) + self.b_r)
+ z_t = sigmoid(torch.matmul(concat, self.W_z.T) + self.b_z)
+
+ gated_h = r_t * h_t
+ concat_cand = torch.cat([gated_h, x_t], dim=1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, self.W_h.T) + self.b_h)
+
+ h_t = z_t * h_t + (1 - z_t) * h_tilde
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+ return y, h_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-hidden-update.py b/recode/problems/TensorPoly/pytorch-mps/gru-hidden-update.py
new file mode 100644
index 0000000..c708844
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-hidden-update.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def hidden_update(h_prev: torch.Tensor, h_tilde: torch.Tensor, z_t: torch.Tensor) -> torch.Tensor:
+ keep_old = z_t * h_prev
+ use_new = (1 - z_t) * h_tilde
+ return keep_old + use_new
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-reset-gate.py b/recode/problems/TensorPoly/pytorch-mps/gru-reset-gate.py
new file mode 100644
index 0000000..b996b38
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-reset-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def reset_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_r: torch.Tensor, b_r: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_r.T) + b_r
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-mps/gru-update-gate.py b/recode/problems/TensorPoly/pytorch-mps/gru-update-gate.py
new file mode 100644
index 0000000..b1bf9ad
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/gru-update-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def update_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_z: torch.Tensor, b_z: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_z.T) + b_z
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-cell-state.py b/recode/problems/TensorPoly/pytorch-mps/lstm-cell-state.py
new file mode 100644
index 0000000..2e2f528
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-cell-state.py
@@ -0,0 +1,5 @@
+import torch
+
+
+def update_cell_state(C_prev: torch.Tensor, f_t: torch.Tensor, i_t: torch.Tensor, c_tilde: torch.Tensor) -> torch.Tensor:
+ return f_t * C_prev + i_t * c_tilde
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-cell.py b/recode/problems/TensorPoly/pytorch-mps/lstm-cell.py
new file mode 100644
index 0000000..6af96c5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-cell.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def lstm_cell(x_t: torch.Tensor, h_prev: torch.Tensor, C_prev: torch.Tensor,
+ W_f: torch.Tensor, W_i: torch.Tensor, W_c: torch.Tensor, W_o: torch.Tensor,
+ b_f: torch.Tensor, b_i: torch.Tensor, b_c: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ f_t = sigmoid(torch.matmul(concat, W_f.T) + b_f)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+
+ C_t = f_t * C_prev + i_t * c_tilde
+ h_t = o_t * torch.tanh(C_t)
+ return h_t, C_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-forget-gate.py b/recode/problems/TensorPoly/pytorch-mps/lstm-forget-gate.py
new file mode 100644
index 0000000..47ca146
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-forget-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def forget_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_f: torch.Tensor, b_f: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_f.T) + b_f
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-full-network.py b/recode/problems/TensorPoly/pytorch-mps/lstm-full-network.py
new file mode 100644
index 0000000..bf9494d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-full-network.py
@@ -0,0 +1,53 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class LSTM:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+
+ self.W_f = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_i = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_c = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.W_o = torch.randn(hidden_dim, hidden_dim + input_dim, device=device) * scale
+ self.b_f = torch.zeros(hidden_dim, device=device)
+ self.b_i = torch.zeros(hidden_dim, device=device)
+ self.b_c = torch.zeros(hidden_dim, device=device)
+ self.b_o = torch.zeros(hidden_dim, device=device)
+
+ self.W_y = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ X = X.to(self.device)
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+ c_t = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+
+ f_t = sigmoid(torch.matmul(concat, self.W_f.T) + self.b_f)
+ i_t = sigmoid(torch.matmul(concat, self.W_i.T) + self.b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, self.W_c.T) + self.b_c)
+ o_t = sigmoid(torch.matmul(concat, self.W_o.T) + self.b_o)
+
+ c_t = f_t * c_t + i_t * c_tilde
+ h_t = o_t * torch.tanh(c_t)
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+
+ return y, h_t, c_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-input-gate.py b/recode/problems/TensorPoly/pytorch-mps/lstm-input-gate.py
new file mode 100644
index 0000000..89154ca
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-input-gate.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def input_gate(h_prev: torch.Tensor, x_t: torch.Tensor,
+ W_i: torch.Tensor, b_i: torch.Tensor,
+ W_c: torch.Tensor, b_c: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ return i_t, c_tilde
diff --git a/recode/problems/TensorPoly/pytorch-mps/lstm-output-gate.py b/recode/problems/TensorPoly/pytorch-mps/lstm-output-gate.py
new file mode 100644
index 0000000..0c21ef9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/lstm-output-gate.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def output_gate(h_prev: torch.Tensor, x_t: torch.Tensor, C_t: torch.Tensor,
+ W_o: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+ h_t = o_t * torch.tanh(C_t)
+ return o_t, h_t
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-batch-norm.py b/recode/problems/TensorPoly/pytorch-mps/resnet-batch-norm.py
new file mode 100644
index 0000000..42f7728
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-batch-norm.py
@@ -0,0 +1,71 @@
+import torch
+
+
+class BatchNorm:
+ def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.eps = eps
+ self.momentum = momentum
+ self.device = device
+ self.gamma = torch.ones(num_features, device=device)
+ self.beta = torch.zeros(num_features, device=device)
+ self.running_mean = torch.zeros(num_features, device=device)
+ self.running_var = torch.ones(num_features, device=device)
+
+ def forward(self, x: torch.Tensor, training: bool = True) -> torch.Tensor:
+ x = x.to(self.device)
+ original_shape = x.shape
+
+ if len(original_shape) > 2:
+ batch, channels = original_shape[0], original_shape[1]
+ x_reshaped = x.reshape(batch, channels, -1)
+ x_reshaped = x_reshaped.permute(0, 2, 1).reshape(-1, channels)
+ else:
+ x_reshaped = x
+ channels = original_shape[-1]
+
+ if training:
+ batch_mean = torch.mean(x_reshaped, dim=0)
+ batch_var = torch.var(x_reshaped, dim=0, unbiased=False)
+ self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
+ self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
+ x_norm = (x_reshaped - batch_mean) / torch.sqrt(batch_var + self.eps)
+ else:
+ x_norm = (x_reshaped - self.running_mean) / torch.sqrt(self.running_var + self.eps)
+
+ out = self.gamma * x_norm + self.beta
+
+ if len(original_shape) > 2:
+ out = out.reshape(batch, -1, channels).permute(0, 2, 1)
+ out = out.reshape(original_shape)
+ else:
+ out = out.reshape(original_shape)
+
+ return out
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def post_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = torch.matmul(x, W1)
+ out = bn1.forward(out)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W2)
+ out = bn2.forward(out)
+ return relu(out + x, device=bn1.device)
+
+
+def pre_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = bn1.forward(x)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W1)
+ out = bn2.forward(out)
+ out = relu(out, device=bn1.device)
+ out = torch.matmul(out, W2)
+ return out + x
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-bottleneck.py b/recode/problems/TensorPoly/pytorch-mps/resnet-bottleneck.py
new file mode 100644
index 0000000..23d6b28
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-bottleneck.py
@@ -0,0 +1,36 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class BottleneckBlock:
+ def __init__(self, in_channels: int, bottleneck_channels: int, out_channels: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.in_ch = in_channels
+ self.bn_ch = bottleneck_channels
+ self.out_ch = out_channels
+ self.device = device
+
+ self.W1 = torch.randn(in_channels, bottleneck_channels, device=device) * 0.01
+ self.W2 = torch.randn(bottleneck_channels, bottleneck_channels, device=device) * 0.01
+ self.W3 = torch.randn(bottleneck_channels, out_channels, device=device) * 0.01
+
+ self.Ws = torch.randn(in_channels, out_channels, device=device) * 0.01 if in_channels != out_channels else None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = relu(torch.matmul(out, self.W2), device=self.device)
+ out = torch.matmul(out, self.W3)
+
+ if self.Ws is not None:
+ identity = torch.matmul(identity, self.Ws)
+
+ return relu(out + identity, device=self.device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-conv-block.py b/recode/problems/TensorPoly/pytorch-mps/resnet-conv-block.py
new file mode 100644
index 0000000..cc3fc5a
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-conv-block.py
@@ -0,0 +1,27 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class ConvBlock:
+ def __init__(self, in_channels: int, out_channels: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.device = device
+ self.W1 = torch.randn(in_channels, out_channels, device=device) * 0.01
+ self.W2 = torch.randn(out_channels, out_channels, device=device) * 0.01
+ self.Ws = torch.randn(in_channels, out_channels, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ main = relu(torch.matmul(x, self.W1), device=self.device)
+ main = torch.matmul(main, self.W2)
+ shortcut = torch.matmul(x, self.Ws)
+ return relu(main + shortcut, device=self.device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-full-network.py b/recode/problems/TensorPoly/pytorch-mps/resnet-full-network.py
new file mode 100644
index 0000000..0abbdee
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-full-network.py
@@ -0,0 +1,86 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class BasicBlock:
+ def __init__(self, in_ch: int, out_ch: int, downsample: bool = False, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.downsample = downsample
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+ self.device = device
+
+ self.W1 = torch.randn(in_ch, out_ch, device=device) * 0.01
+ self.W2 = torch.randn(out_ch, out_ch, device=device) * 0.01
+
+ if in_ch != out_ch or downsample:
+ self.W_proj = torch.randn(in_ch, out_ch, device=device) * 0.01
+ else:
+ self.W_proj = None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = torch.matmul(out, self.W2)
+
+ if self.W_proj is not None:
+ identity = torch.matmul(identity, self.W_proj)
+
+ return relu(out + identity, device=self.device)
+
+
+class ResNet18:
+ def __init__(self, num_classes: int = 10, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.conv1 = torch.randn(3, 64, device=device) * 0.01
+
+ self.layer1 = [
+ BasicBlock(64, 64, downsample=False, device=device),
+ BasicBlock(64, 64, downsample=False, device=device),
+ ]
+
+ self.layer2 = [
+ BasicBlock(64, 128, downsample=True, device=device),
+ BasicBlock(128, 128, downsample=False, device=device),
+ ]
+
+ self.layer3 = [
+ BasicBlock(128, 256, downsample=True, device=device),
+ BasicBlock(256, 256, downsample=False, device=device),
+ ]
+
+ self.layer4 = [
+ BasicBlock(256, 512, downsample=True, device=device),
+ BasicBlock(512, 512, downsample=False, device=device),
+ ]
+
+ self.fc = torch.randn(512, num_classes, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ out = relu(torch.matmul(x, self.conv1), device=self.device)
+
+ for block in self.layer1:
+ out = block.forward(out)
+
+ for block in self.layer2:
+ out = block.forward(out)
+
+ for block in self.layer3:
+ out = block.forward(out)
+
+ for block in self.layer4:
+ out = block.forward(out)
+
+ logits = torch.matmul(out, self.fc)
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-identity-block.py b/recode/problems/TensorPoly/pytorch-mps/resnet-identity-block.py
new file mode 100644
index 0000000..80a6f5c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-identity-block.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def relu(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+class IdentityBlock:
+ def __init__(self, channels: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.channels = channels
+ self.device = device
+ self.W1 = torch.randn(channels, channels, device=device) * 0.01
+ self.W2 = torch.randn(channels, channels, device=device) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ x = x.to(self.device)
+ identity = x
+ out = relu(torch.matmul(x, self.W1), device=self.device)
+ out = torch.matmul(out, self.W2)
+ return out + identity
diff --git a/recode/problems/TensorPoly/pytorch-mps/resnet-skip-connection.py b/recode/problems/TensorPoly/pytorch-mps/resnet-skip-connection.py
new file mode 100644
index 0000000..8299e41
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/resnet-skip-connection.py
@@ -0,0 +1,26 @@
+import torch
+
+
+def compute_gradient_with_skip(gradients_F: list, x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ grad = torch.tensor(x, device=device)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad, device=device)
+ dim = F_mat.shape[-1]
+ grad = grad @ (torch.eye(dim, device=device) + F_mat)
+
+ return grad
+
+
+def compute_gradient_without_skip(gradients_F: list, x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ grad = torch.tensor(x, device=device)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad, device=device)
+ grad = grad @ F_mat
+
+ return grad
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-bptt.py b/recode/problems/TensorPoly/pytorch-mps/rnn-bptt.py
new file mode 100644
index 0000000..e742c13
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-bptt.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def bptt_single_step(dh_next: torch.Tensor, h_t: torch.Tensor, h_prev: torch.Tensor, x_t: torch.Tensor, W_hh: torch.Tensor) -> tuple:
+ dtanh = (1 - h_t ** 2) * dh_next
+ dW_hh = torch.matmul(dtanh.T, h_prev)
+ dh_prev = torch.matmul(dtanh, W_hh)
+ return dh_prev, dW_hh
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-cell.py b/recode/problems/TensorPoly/pytorch-mps/rnn-cell.py
new file mode 100644
index 0000000..cccaac1
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-cell.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def rnn_cell(x_t: torch.Tensor, h_prev: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ input_term = torch.matmul(x_t, W_xh.T)
+ hidden_term = torch.matmul(h_prev, W_hh.T)
+ return torch.tanh(input_term + hidden_term + b_h)
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-forward-sequence.py b/recode/problems/TensorPoly/pytorch-mps/rnn-forward-sequence.py
new file mode 100644
index 0000000..d534072
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-forward-sequence.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def rnn_forward(X: torch.Tensor, h_0: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ h_current = h_0
+ h_all_list = []
+
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, W_xh.T) + torch.matmul(h_current, W_hh.T) + b_h)
+ h_all_list.append(h_current)
+
+ h_all = torch.stack(h_all_list, dim=1)
+ h_final = h_current
+ return h_all, h_final
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-full-network.py b/recode/problems/TensorPoly/pytorch-mps/rnn-full-network.py
new file mode 100644
index 0000000..0755466
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-full-network.py
@@ -0,0 +1,37 @@
+import torch
+
+
+class VanillaRNN:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.hidden_dim = hidden_dim
+ self.W_xh = torch.randn(hidden_dim, input_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim), device=device))
+ self.W_hh = torch.randn(hidden_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (2 * hidden_dim), device=device))
+ self.W_hy = torch.randn(output_dim, hidden_dim, device=device) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim), device=device))
+ self.b_h = torch.zeros(hidden_dim, device=device)
+ self.b_y = torch.zeros(output_dim, device=device)
+
+ def forward(self, X: torch.Tensor, h_0: torch.Tensor = None) -> tuple:
+ X = X.to(self.device)
+ batch_size, time_steps, _ = X.shape
+ if h_0 is None:
+ h_current = torch.zeros((batch_size, self.hidden_dim), device=self.device)
+ else:
+ h_current = h_0.to(self.device)
+
+ h_list = []
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, self.W_xh.T) + torch.matmul(h_current, self.W_hh.T) + self.b_h)
+ h_list.append(h_current)
+
+ h_seq = torch.stack(h_list, dim=1)
+ h_final = h_current
+
+ h_flat = h_seq.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_hy.T) + self.b_y
+ y_seq = y_flat.reshape(batch_size, time_steps, -1)
+
+ return y_seq, h_final
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-hidden-state.py b/recode/problems/TensorPoly/pytorch-mps/rnn-hidden-state.py
new file mode 100644
index 0000000..2a640a8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-hidden-state.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def init_hidden(batch_size: int, hidden_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ return torch.zeros((batch_size, hidden_dim), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/rnn-vanishing-gradients.py b/recode/problems/TensorPoly/pytorch-mps/rnn-vanishing-gradients.py
new file mode 100644
index 0000000..dae76f5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/rnn-vanishing-gradients.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def compute_gradient_norm_decay(T: int, W_hh: torch.Tensor) -> list:
+ spectral_norm = torch.linalg.norm(W_hh, ord=2)
+ norms = [1.0]
+ current_norm = 1.0
+
+ for _ in range(T - 1):
+ current_norm *= float(spectral_norm)
+ norms.append(current_norm)
+
+ return norms
diff --git a/recode/problems/TensorPoly/pytorch-mps/sigmoid-numpy.py b/recode/problems/TensorPoly/pytorch-mps/sigmoid-numpy.py
new file mode 100644
index 0000000..040b47e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/sigmoid-numpy.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def sigmoid(x, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x_tensor = torch.as_tensor(x, dtype=torch.float32, device=device)
+ return 1.0 / (1.0 + torch.exp(-x_tensor))
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-attention.py b/recode/problems/TensorPoly/pytorch-mps/transformers-attention.py
new file mode 100644
index 0000000..24fc9d0
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-attention.py
@@ -0,0 +1,16 @@
+import math
+import torch
+import torch.nn.functional as F
+
+
+def scaled_dot_product_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ Q = Q.to(device)
+ K = K.to(device)
+ V = V.to(device)
+ d_k = Q.size(-1)
+ scores = torch.matmul(Q, K.transpose(-2, -1))
+ scaled_scores = scores / math.sqrt(d_k)
+ attention_weights = F.softmax(scaled_scores, dim=-1)
+ return torch.matmul(attention_weights, V)
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-embedding.py b/recode/problems/TensorPoly/pytorch-mps/transformers-embedding.py
new file mode 100644
index 0000000..db7b994
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-embedding.py
@@ -0,0 +1,19 @@
+import math
+import torch
+import torch.nn as nn
+
+
+def create_embedding_layer(vocab_size: int, d_model: int, device=None) -> nn.Embedding:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ embedding = nn.Embedding(vocab_size, d_model, device=device)
+ nn.init.normal_(embedding.weight, mean=0.0, std=1.0 / math.sqrt(d_model))
+ return embedding
+
+
+def embed_tokens(embedding: nn.Embedding, tokens: torch.Tensor, d_model: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ tokens = tokens.to(device)
+ embedded = embedding(tokens)
+ return embedded * math.sqrt(d_model)
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-encoder-block.py b/recode/problems/TensorPoly/pytorch-mps/transformers-encoder-block.py
new file mode 100644
index 0000000..22cdc18
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-encoder-block.py
@@ -0,0 +1,60 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype, device=Q.device))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ return torch.matmul(concatenated, W_o)
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype, device=hidden.device), hidden)
+ return torch.matmul(relu_out, W2) + b2
+
+
+def encoder_block(x: torch.Tensor, W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor, W2: torch.Tensor,
+ b2: torch.Tensor, gamma1: torch.Tensor, beta1: torch.Tensor,
+ gamma2: torch.Tensor, beta2: torch.Tensor, num_heads: int) -> torch.Tensor:
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x + attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 + ff_output
+ return layer_norm(x_ff_residual, gamma2, beta2)
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-feed-forward.py b/recode/problems/TensorPoly/pytorch-mps/transformers-feed-forward.py
new file mode 100644
index 0000000..edff8d9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-feed-forward.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype, device=hidden.device), hidden)
+ return torch.matmul(relu_out, W2) + b2
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-layer-normalization.py b/recode/problems/TensorPoly/pytorch-mps/transformers-layer-normalization.py
new file mode 100644
index 0000000..cd725f8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-layer-normalization.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-multi-head-attention.py b/recode/problems/TensorPoly/pytorch-mps/transformers-multi-head-attention.py
new file mode 100644
index 0000000..bf1c248
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-multi-head-attention.py
@@ -0,0 +1,33 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype, device=Q.device))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ return torch.matmul(concatenated, W_o)
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-positional-encoding.py b/recode/problems/TensorPoly/pytorch-mps/transformers-positional-encoding.py
new file mode 100644
index 0000000..4950092
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-positional-encoding.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def positional_encoding(seq_length: int, d_model: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ position = torch.arange(seq_length, dtype=torch.float32, device=device).unsqueeze(1)
+ i = torch.arange(0, d_model, 2, dtype=torch.float32, device=device)
+ div_term = torch.exp(i * (-torch.log(torch.tensor(10000.0, device=device)) / d_model))
+
+ pe = torch.zeros(seq_length, d_model, device=device)
+ pe[:, 0::2] = torch.sin(position * div_term)
+ pe[:, 1::2] = torch.cos(position * div_term)
+ return pe
diff --git a/recode/problems/TensorPoly/pytorch-mps/transformers-tokenization.py b/recode/problems/TensorPoly/pytorch-mps/transformers-tokenization.py
new file mode 100644
index 0000000..1ee1eed
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/transformers-tokenization.py
@@ -0,0 +1,52 @@
+from typing import List, Dict
+
+
+class SimpleTokenizer:
+ """
+ A word-level tokenizer with special tokens.
+ """
+
+ def __init__(self):
+ self.word_to_id: Dict[str, int] = {}
+ self.id_to_word: Dict[int, str] = {}
+ self.vocab_size = 0
+
+ self.pad_token = ""
+ self.unk_token = ""
+ self.bos_token = ""
+ self.eos_token = ""
+
+ def build_vocab(self, texts: List[str]) -> None:
+ special_tokens = [self.pad_token, self.unk_token, self.bos_token, self.eos_token]
+ for idx, token in enumerate(special_tokens):
+ self.word_to_id[token] = idx
+ self.id_to_word[idx] = token
+
+ unique_words = set()
+ for text in texts:
+ words = text.split()
+ unique_words.update(words)
+
+ current_id = len(special_tokens)
+ for word in sorted(unique_words):
+ if word not in self.word_to_id:
+ self.word_to_id[word] = current_id
+ self.id_to_word[current_id] = word
+ current_id += 1
+
+ self.vocab_size = len(self.word_to_id)
+
+ def encode(self, text: str) -> List[int]:
+ words = text.split()
+ token_ids = []
+ for word in words:
+ token_id = self.word_to_id.get(word, self.word_to_id[self.unk_token])
+ token_ids.append(token_id)
+ return token_ids
+
+ def decode(self, ids: List[int]) -> str:
+ words = []
+ for token_id in ids:
+ word = self.id_to_word.get(token_id, self.unk_token)
+ words.append(word)
+ return " ".join(words)
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-bottleneck.py b/recode/problems/TensorPoly/pytorch-mps/unet-bottleneck.py
new file mode 100644
index 0000000..c6dc90c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-bottleneck.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def unet_bottleneck(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ H_out = H - 4
+ W_out = W - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-decoder-block.py b/recode/problems/TensorPoly/pytorch-mps/unet-decoder-block.py
new file mode 100644
index 0000000..178f88b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-decoder-block.py
@@ -0,0 +1,21 @@
+import torch
+
+
+def unet_decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ skip = skip.to(device)
+ batch, H, W, _ = x.shape
+ _, H_skip, W_skip, _ = skip.shape
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-encoder-block.py b/recode/problems/TensorPoly/pytorch-mps/unet-encoder-block.py
new file mode 100644
index 0000000..1d22f97
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-encoder-block.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def unet_encoder_block(x: torch.Tensor, out_channels: int, device=None) -> tuple:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = torch.zeros((batch, skip_H, skip_W, out_channels), device=device)
+
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pool_out = torch.zeros((batch, pool_H, pool_W, out_channels), device=device)
+
+ return pool_out, skip_out
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-full-network.py b/recode/problems/TensorPoly/pytorch-mps/unet-full-network.py
new file mode 100644
index 0000000..929352e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-full-network.py
@@ -0,0 +1,70 @@
+import torch
+
+
+def encoder_block(x: torch.Tensor, out_channels: int, device=None) -> tuple:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = torch.zeros((batch, skip_H, skip_W, out_channels), device=device)
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pooled = torch.zeros((batch, pool_H, pool_W, out_channels), device=device)
+ return pooled, skip
+
+
+def bottleneck(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H - 4, W - 4, out_channels), device=device)
+
+
+def decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ skip = skip.to(device)
+ batch, H, W, _ = x.shape
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = skip.shape
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels), device=device)
+
+
+def output_layer(x: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H, W, num_classes), device=device)
+
+
+def unet(x: torch.Tensor, num_classes: int = 2, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+
+ e1_pool, e1_skip = encoder_block(x, out_channels=64, device=device)
+ e2_pool, e2_skip = encoder_block(e1_pool, out_channels=128, device=device)
+ e3_pool, e3_skip = encoder_block(e2_pool, out_channels=256, device=device)
+ e4_pool, e4_skip = encoder_block(e3_pool, out_channels=512, device=device)
+
+ bottleneck_out = bottleneck(e4_pool, out_channels=1024, device=device)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, out_channels=512, device=device)
+ d3_out = decoder_block(d4_out, e3_skip, out_channels=256, device=device)
+ d2_out = decoder_block(d3_out, e2_skip, out_channels=128, device=device)
+ d1_out = decoder_block(d2_out, e1_skip, out_channels=64, device=device)
+
+ return output_layer(d1_out, num_classes, device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-output-layer.py b/recode/problems/TensorPoly/pytorch-mps/unet-output-layer.py
new file mode 100644
index 0000000..fd2b384
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-output-layer.py
@@ -0,0 +1,9 @@
+import torch
+
+
+def unet_output(features: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ features = features.to(device)
+ batch, H, W, _ = features.shape
+ return torch.zeros((batch, H, W, num_classes), device=device)
diff --git a/recode/problems/TensorPoly/pytorch-mps/unet-skip-connection.py b/recode/problems/TensorPoly/pytorch-mps/unet-skip-connection.py
new file mode 100644
index 0000000..b02c2aa
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/unet-skip-connection.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def crop_and_concat(encoder_features: torch.Tensor, decoder_features: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ encoder_features = encoder_features.to(device)
+ decoder_features = decoder_features.to(device)
+ _, H_enc, W_enc, _ = encoder_features.shape
+ _, H_dec, W_dec, _ = decoder_features.shape
+
+ crop_h = (H_enc - H_dec) // 2
+ crop_w = (W_enc - W_dec) // 2
+
+ encoder_cropped = encoder_features[:, crop_h:crop_h + H_dec, crop_w:crop_w + W_dec, :]
+ return torch.cat([encoder_cropped, decoder_features], dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-decoder.py b/recode/problems/TensorPoly/pytorch-mps/vae-decoder.py
new file mode 100644
index 0000000..50d01ea
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-decoder.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def vae_decoder(z: torch.Tensor, output_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ z = z.to(device)
+ _, latent_dim = z.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(latent_dim, hidden_dim, device=device) * 0.01
+ b_h = torch.zeros(hidden_dim, device=device)
+ h = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(z, w_h) + b_h)
+
+ w_out = torch.randn(hidden_dim, output_dim, device=device) * 0.01
+ b_out = torch.zeros(output_dim, device=device)
+ logits = torch.matmul(h, w_out) + b_out
+
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-elbo-loss.py b/recode/problems/TensorPoly/pytorch-mps/vae-elbo-loss.py
new file mode 100644
index 0000000..770029c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-elbo-loss.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def vae_loss(x: torch.Tensor, x_recon: torch.Tensor, mu: torch.Tensor, log_var: torch.Tensor) -> dict:
+ recon_loss_per_sample = torch.sum((x - x_recon) ** 2, dim=1)
+ recon_loss = torch.mean(recon_loss_per_sample)
+
+ var = torch.exp(log_var)
+ kl_per_sample = -0.5 * torch.sum(1 + log_var - mu ** 2 - var, dim=1)
+ kl_loss = torch.mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return {
+ "total": float(total_loss.item()),
+ "recon": float(recon_loss.item()),
+ "kl": float(kl_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-encoder.py b/recode/problems/TensorPoly/pytorch-mps/vae-encoder.py
new file mode 100644
index 0000000..5d87fbd
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-encoder.py
@@ -0,0 +1,23 @@
+import torch
+
+
+def vae_encoder(x: torch.Tensor, latent_dim: int, device=None) -> tuple:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ _, input_dim = x.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(input_dim, hidden_dim, device=device) * 0.01
+ b_h = torch.zeros(hidden_dim, device=device)
+ h = torch.maximum(torch.tensor(0.0, device=device), torch.matmul(x, w_h) + b_h)
+
+ w_mu = torch.randn(hidden_dim, latent_dim, device=device) * 0.01
+ b_mu = torch.zeros(latent_dim, device=device)
+ mu = torch.matmul(h, w_mu) + b_mu
+
+ w_log_var = torch.randn(hidden_dim, latent_dim, device=device) * 0.01
+ b_log_var = torch.zeros(latent_dim, device=device)
+ log_var = torch.matmul(h, w_log_var) + b_log_var
+
+ return mu, log_var
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-full-network.py b/recode/problems/TensorPoly/pytorch-mps/vae-full-network.py
new file mode 100644
index 0000000..dea58c9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-full-network.py
@@ -0,0 +1,46 @@
+import torch
+
+
+class VAE:
+ def __init__(self, input_dim: int, latent_dim: int, device=None):
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ self.device = device
+ self.input_dim = input_dim
+ self.latent_dim = latent_dim
+ self.hidden_dim = 256
+
+ self.w_enc = torch.randn(input_dim, self.hidden_dim, device=self.device) * 0.01
+ self.b_enc = torch.zeros(self.hidden_dim, device=self.device)
+
+ self.w_mu = torch.randn(self.hidden_dim, latent_dim, device=self.device) * 0.01
+ self.b_mu = torch.zeros(latent_dim, device=self.device)
+ self.w_log_var = torch.randn(self.hidden_dim, latent_dim, device=self.device) * 0.01
+ self.b_log_var = torch.zeros(latent_dim, device=self.device)
+
+ self.w_dec_h = torch.randn(latent_dim, self.hidden_dim, device=self.device) * 0.01
+ self.b_dec_h = torch.zeros(self.hidden_dim, device=self.device)
+ self.w_dec_out = torch.randn(self.hidden_dim, input_dim, device=self.device) * 0.01
+ self.b_dec_out = torch.zeros(input_dim, device=self.device)
+
+ def forward(self, x: torch.Tensor) -> tuple:
+ x = x.to(self.device)
+ h_enc = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(x, self.w_enc) + self.b_enc)
+ mu = torch.matmul(h_enc, self.w_mu) + self.b_mu
+ log_var = torch.matmul(h_enc, self.w_log_var) + self.b_log_var
+
+ std = torch.exp(0.5 * log_var)
+ eps = torch.randn_like(mu)
+ z = mu + std * eps
+
+ h_dec = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ x_recon = 1 / (1 + torch.exp(-logits))
+
+ return x_recon, mu, log_var
+
+ def generate(self, n_samples: int) -> torch.Tensor:
+ z = torch.randn(n_samples, self.latent_dim, device=self.device)
+ h_dec = torch.maximum(torch.tensor(0.0, device=self.device), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-kl-divergence.py b/recode/problems/TensorPoly/pytorch-mps/vae-kl-divergence.py
new file mode 100644
index 0000000..a7a3652
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-kl-divergence.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def kl_divergence(mu: torch.Tensor, log_var: torch.Tensor) -> float:
+ var = torch.exp(log_var)
+ kl_element = 1 + log_var - mu ** 2 - var
+ batch_kl = -0.5 * torch.sum(kl_element, dim=1)
+ return float(torch.mean(batch_kl).item())
diff --git a/recode/problems/TensorPoly/pytorch-mps/vae-reparameterization.py b/recode/problems/TensorPoly/pytorch-mps/vae-reparameterization.py
new file mode 100644
index 0000000..f88625c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vae-reparameterization.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def reparameterize(mu: torch.Tensor, log_var: torch.Tensor) -> torch.Tensor:
+ std = torch.exp(0.5 * log_var)
+ epsilon = torch.randn_like(mu)
+ return mu + std * epsilon
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-classifier.py b/recode/problems/TensorPoly/pytorch-mps/vgg-classifier.py
new file mode 100644
index 0000000..67ea9fd
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-classifier.py
@@ -0,0 +1,24 @@
+import torch
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ features = features.to(device)
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim, device=device))
+ w = torch.randn(in_dim, out_dim, device=device) * limit
+ b = torch.zeros(out_dim, device=device)
+ return torch.maximum(torch.tensor(0.0, device=device), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes, device=device) * torch.sqrt(torch.tensor(2.0 / in_dim_final, device=device))
+ b_final = torch.zeros(num_classes, device=device)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-config.py b/recode/problems/TensorPoly/pytorch-mps/vgg-config.py
new file mode 100644
index 0000000..85529b9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-config.py
@@ -0,0 +1,9 @@
+def make_vgg_config(variant: str) -> list:
+ configs = {
+ "vgg11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ }
+ key = variant.lower()
+ return configs.get(key, [])
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-conv-block.py b/recode/problems/TensorPoly/pytorch-mps/vgg-conv-block.py
new file mode 100644
index 0000000..5a707d7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-conv-block.py
@@ -0,0 +1,27 @@
+import torch
+
+
+def vgg_conv_block(x: torch.Tensor, num_convs: int, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ current_x = x.to(device)
+ for _ in range(num_convs):
+ _, _, _, c = current_x.shape
+ limit = torch.sqrt(torch.tensor(2.0 / (3 * 3 * c), device=device))
+ weights = torch.randn(3, 3, c, out_channels, device=device) * limit
+ bias = torch.zeros(out_channels, device=device)
+
+ batch, h, w, _ = current_x.shape
+ padded_x = torch.zeros((batch, h + 2, w + 2, c), device=device)
+ padded_x[:, 1:h + 1, 1:w + 1, :] = current_x
+
+ out = torch.zeros((batch, h, w, out_channels), device=device)
+ for i in range(3):
+ for j in range(3):
+ window = padded_x[:, i:i + h, j:j + w, :]
+ out = out + torch.tensordot(window, weights[i, j], dims=([3], [0]))
+
+ out = out + bias
+ current_x = torch.maximum(torch.tensor(0.0, device=device), out)
+
+ return current_x
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-feature-extractor.py b/recode/problems/TensorPoly/pytorch-mps/vgg-feature-extractor.py
new file mode 100644
index 0000000..ed31b77
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-feature-extractor.py
@@ -0,0 +1,31 @@
+import torch
+
+
+def conv_relu(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels, device=device) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def maxpool_2x2(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ out = x.to(device)
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer, device=device)
+ elif layer == "M":
+ out = maxpool_2x2(out, device=device)
+ return out
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-full-network.py b/recode/problems/TensorPoly/pytorch-mps/vgg-full-network.py
new file mode 100644
index 0000000..b697227
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-full-network.py
@@ -0,0 +1,69 @@
+import torch
+
+
+def vgg16(x: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x.to(device), vgg16_config, device=device)
+ return vgg_classifier(features, num_classes, device=device)
+
+
+def conv_relu(x: torch.Tensor, out_channels: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels, device=device) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0, device=device), x)
+
+
+def maxpool_2x2(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ out = x.to(device)
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer, device=device)
+ elif layer == "M":
+ out = maxpool_2x2(out, device=device)
+ return out
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ features = features.to(device)
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim, device=device))
+ w = torch.randn(in_dim, out_dim, device=device) * limit
+ b = torch.zeros(out_dim, device=device)
+ return torch.maximum(torch.tensor(0.0, device=device), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes, device=device) * torch.sqrt(torch.tensor(2.0 / in_dim_final, device=device))
+ b_final = torch.zeros(num_classes, device=device)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch-mps/vgg-maxpool.py b/recode/problems/TensorPoly/pytorch-mps/vgg-maxpool.py
new file mode 100644
index 0000000..2a5f0a4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vgg-maxpool.py
@@ -0,0 +1,10 @@
+import torch
+
+
+def vgg_maxpool(x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch, h, w, c = x.shape
+ reshaped_x = x.reshape(batch, h // 2, 2, w // 2, 2, c)
+ return reshaped_x.max(dim=2).values.max(dim=3).values
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-class-token.py b/recode/problems/TensorPoly/pytorch-mps/vit-class-token.py
new file mode 100644
index 0000000..631adc0
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-class-token.py
@@ -0,0 +1,10 @@
+import torch
+
+
+def prepend_class_token(patches: torch.Tensor, embed_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ batch_size = patches.size(0)
+ cls_token = torch.randn(1, 1, embed_dim, device=device) * 0.02
+ cls_token_batch = cls_token.repeat(batch_size, 1, 1)
+ return torch.cat([cls_token_batch, patches.to(device)], dim=1)
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-encoder-block.py b/recode/problems/TensorPoly/pytorch-mps/vit-encoder-block.py
new file mode 100644
index 0000000..5c540e8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-encoder-block.py
@@ -0,0 +1,62 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def gelu(x: torch.Tensor) -> torch.Tensor:
+ return 0.5 * x * (1 + torch.tanh(torch.sqrt(torch.tensor(2.0 / torch.pi, device=x.device)) * (x + 0.044715 * x ** 3)))
+
+
+def softmax(x: torch.Tensor, axis: int = -1) -> torch.Tensor:
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_self_attention(x: torch.Tensor, num_heads: int, embed_dim: int) -> torch.Tensor:
+ batch, seq_len, _ = x.shape
+ head_dim = embed_dim // num_heads
+
+ W_q = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_k = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_v = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+ W_o = torch.randn(embed_dim, embed_dim, device=x.device) * 0.02
+
+ Q = torch.matmul(x, W_q)
+ K = torch.matmul(x, W_k)
+ V = torch.matmul(x, W_v)
+
+ Q = Q.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ K = K.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ V = V.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(head_dim, dtype=x.dtype, device=x.device))
+ attn_weights = softmax(scores, axis=-1)
+ attn_output = torch.matmul(attn_weights, V)
+
+ attn_output = attn_output.transpose(1, 2).reshape(batch, seq_len, embed_dim)
+ return torch.matmul(attn_output, W_o)
+
+
+def mlp(x: torch.Tensor, embed_dim: int, mlp_ratio: float) -> torch.Tensor:
+ hidden_dim = int(embed_dim * mlp_ratio)
+ W1 = torch.randn(embed_dim, hidden_dim, device=x.device) * 0.02
+ b1 = torch.zeros(hidden_dim, device=x.device)
+ W2 = torch.randn(hidden_dim, embed_dim, device=x.device) * 0.02
+ b2 = torch.zeros(embed_dim, device=x.device)
+
+ h = gelu(torch.matmul(x, W1) + b1)
+ return torch.matmul(h, W2) + b2
+
+
+def vit_encoder_block(x: torch.Tensor, embed_dim: int, num_heads: int, mlp_ratio: float = 4.0) -> torch.Tensor:
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x + attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x = x + mlp_output
+ return x
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-full-network.py b/recode/problems/TensorPoly/pytorch-mps/vit-full-network.py
new file mode 100644
index 0000000..a586d7c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-full-network.py
@@ -0,0 +1,35 @@
+import torch
+
+
+class VisionTransformer:
+ def __init__(self, image_size: int = 224, patch_size: int = 16,
+ num_classes: int = 1000, embed_dim: int = 768,
+ depth: int = 12, num_heads: int = 12, mlp_ratio: float = 4.0):
+ self.image_size = image_size
+ self.patch_size = patch_size
+ self.num_patches = (image_size // patch_size) ** 2
+ self.embed_dim = embed_dim
+ self.depth = depth
+ self.num_heads = num_heads
+ self.mlp_ratio = mlp_ratio
+ self.num_classes = num_classes
+
+ def forward(self, x: torch.Tensor, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ x = x.to(device)
+ batch_size = x.shape[0]
+
+ x = torch.zeros((batch_size, self.num_patches, self.embed_dim), device=device)
+ x = torch.cat([
+ torch.zeros((batch_size, 1, self.embed_dim), device=device),
+ x
+ ], dim=1)
+
+ x = x + torch.zeros((1, self.num_patches + 1, self.embed_dim), device=device)
+
+ for _ in range(self.depth):
+ x = x + torch.zeros_like(x)
+
+ logits = torch.zeros((batch_size, self.num_classes), device=device)
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-mlp-head.py b/recode/problems/TensorPoly/pytorch-mps/vit-mlp-head.py
new file mode 100644
index 0000000..f3f68ad
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-mlp-head.py
@@ -0,0 +1,21 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def classification_head(encoder_output: torch.Tensor, num_classes: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ cls_token = encoder_output[:, 0, :].to(device)
+ cls_norm = layer_norm(cls_token)
+
+ embed_dim = cls_token.shape[-1]
+ W = torch.randn(embed_dim, num_classes, device=device) * 0.01
+ b = torch.zeros(num_classes, device=device)
+
+ logits = torch.matmul(cls_norm, W) + b
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-patch-embedding.py b/recode/problems/TensorPoly/pytorch-mps/vit-patch-embedding.py
new file mode 100644
index 0000000..000aff1
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-patch-embedding.py
@@ -0,0 +1,28 @@
+import torch
+
+
+def patch_embed(image: torch.Tensor, patch_size: int, embed_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ image = image.to(device)
+ batch, H, W, C = image.shape
+
+ num_patches_h = H // patch_size
+ num_patches_w = W // patch_size
+ num_patches = num_patches_h * num_patches_w
+
+ patches = image.reshape(
+ batch,
+ num_patches_h, patch_size,
+ num_patches_w, patch_size,
+ C
+ )
+
+ patches = patches.permute(0, 1, 3, 2, 4, 5)
+ patches_flat = patches.reshape(batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ patches_seq = patches_flat.reshape(batch, num_patches, patch_size * patch_size * C)
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = torch.randn(patch_dim, embed_dim, device=device) * 0.01
+ embeddings = torch.matmul(patches_seq, W_proj)
+ return embeddings
diff --git a/recode/problems/TensorPoly/pytorch-mps/vit-position-embedding.py b/recode/problems/TensorPoly/pytorch-mps/vit-position-embedding.py
new file mode 100644
index 0000000..c65226c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch-mps/vit-position-embedding.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def add_position_embedding(patches: torch.Tensor, num_patches: int, embed_dim: int, device=None) -> torch.Tensor:
+ if device is None:
+ device = "mps" if torch.backends.mps.is_available() else "cpu"
+ position_embeddings = torch.randn(1, num_patches, embed_dim, device=device) * 0.01
+ return patches.to(device) + position_embeddings
diff --git a/recode/problems/TensorPoly/pytorch/__init__.py b/recode/problems/TensorPoly/pytorch/__init__.py
new file mode 100644
index 0000000..bd83f47
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/__init__.py
@@ -0,0 +1 @@
+"""Bundled PyTorch TensorPoly problems."""
diff --git a/recode/problems/TensorPoly/pytorch/adam-optimizer.py b/recode/problems/TensorPoly/pytorch/adam-optimizer.py
new file mode 100644
index 0000000..b9effc8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/adam-optimizer.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def adam_step(param, grad, m, v, t, lr=1e-3, beta1=0.9, beta2=0.999, eps=1e-8):
+ m_new = beta1 * m + (1 - beta1) * grad
+ v_new = beta2 * v + (1 - beta2) * (grad ** 2)
+
+ m_hat = m_new / (1 - beta1 ** t)
+ v_hat = v_new / (1 - beta2 ** t)
+
+ param_new = param - lr * m_hat / (torch.sqrt(v_hat) + eps)
+
+ return param_new, m_new, v_new
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-augmentation.py b/recode/problems/TensorPoly/pytorch/alexnet-augmentation.py
new file mode 100644
index 0000000..5f27876
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-augmentation.py
@@ -0,0 +1,15 @@
+import torch
+
+
+def random_crop(image: torch.Tensor, crop_size: int = 224) -> torch.Tensor:
+ h = image.shape[0]
+ w = image.shape[1]
+ top = torch.randint(0, h - crop_size + 1, (1,)).item()
+ left = torch.randint(0, w - crop_size + 1, (1,)).item()
+ return image[top:top + crop_size, left:left + crop_size, :]
+
+
+def random_horizontal_flip(image: torch.Tensor, p: float = 0.5) -> torch.Tensor:
+ if torch.rand(1).item() < p:
+ return image[:, torch.arange(image.shape[1] - 1, -1, -1), :]
+ return image
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-conv-layers.py b/recode/problems/TensorPoly/pytorch/alexnet-conv-layers.py
new file mode 100644
index 0000000..995a7a6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-conv-layers.py
@@ -0,0 +1,9 @@
+import torch
+
+
+def alexnet_conv1(image: torch.Tensor) -> torch.Tensor:
+ batch_size = image.shape[0]
+ output_h = 55
+ output_w = 55
+ num_filters = 96
+ return torch.zeros((batch_size, output_h, output_w, num_filters))
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-dropout.py b/recode/problems/TensorPoly/pytorch/alexnet-dropout.py
new file mode 100644
index 0000000..d279e18
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-dropout.py
@@ -0,0 +1,9 @@
+import torch
+
+
+def dropout(x: torch.Tensor, p: float = 0.5, training: bool = True) -> torch.Tensor:
+ if not training or p == 0:
+ return x
+
+ mask = torch.bernoulli(torch.full_like(x, 1 - p))
+ return (x * mask) / (1 - p)
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-lrn.py b/recode/problems/TensorPoly/pytorch/alexnet-lrn.py
new file mode 100644
index 0000000..ce00ccf
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-lrn.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def local_response_normalization(x: torch.Tensor, k: float = 2, n: int = 5,
+ alpha: float = 1e-4, beta: float = 0.75) -> torch.Tensor:
+ _, _, _, c = x.shape
+ squared_x = x * x
+ pad = n // 2
+ padded_sq = torch.nn.functional.pad(squared_x, (pad, pad, 0, 0, 0, 0, 0, 0))
+
+ sum_sq = torch.zeros_like(x)
+ for i in range(n):
+ sum_sq = sum_sq + padded_sq[:, :, :, i:i + c]
+
+ scale = (k + alpha * sum_sq) ** beta
+ return x / scale
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-pooling.py b/recode/problems/TensorPoly/pytorch/alexnet-pooling.py
new file mode 100644
index 0000000..bb66116
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-pooling.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def max_pool2d(x: torch.Tensor, kernel_size: int = 3, stride: int = 2) -> torch.Tensor:
+ batch_size, h_in, w_in, channels = x.shape
+ h_out = (h_in - kernel_size) // stride + 1
+ w_out = (w_in - kernel_size) // stride + 1
+ return torch.zeros((batch_size, h_out, w_out, channels))
diff --git a/recode/problems/TensorPoly/pytorch/alexnet-relu.py b/recode/problems/TensorPoly/pytorch/alexnet-relu.py
new file mode 100644
index 0000000..f06c778
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/alexnet-relu.py
@@ -0,0 +1,5 @@
+import torch
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
diff --git a/recode/problems/TensorPoly/pytorch/bert-fine-tuning.py b/recode/problems/TensorPoly/pytorch/bert-fine-tuning.py
new file mode 100644
index 0000000..a247fcf
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-fine-tuning.py
@@ -0,0 +1,58 @@
+import torch
+from typing import List
+
+
+class MockBertEncoder:
+ """Simulated BERT encoder with 12 layers."""
+
+ def __init__(self, hidden_size: int = 768, num_layers: int = 12):
+ self.hidden_size = hidden_size
+ self.num_layers = num_layers
+ self.layers = [torch.randn(hidden_size, hidden_size) * 0.01 for _ in range(num_layers)]
+ self.layer_frozen = [False] * num_layers
+
+ def freeze_layers(self, layer_indices: List[int]):
+ for idx in layer_indices:
+ if 0 <= idx < self.num_layers:
+ self.layer_frozen[idx] = True
+
+ def unfreeze_all(self):
+ self.layer_frozen = [False] * self.num_layers
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ x = embeddings
+ for layer in self.layers:
+ x = torch.matmul(x, layer) + x
+ return x
+
+
+class BertForSequenceClassification:
+ """BERT with sequence-level classification head (e.g. Sentiment)."""
+
+ def __init__(self, hidden_size: int, num_labels: int, freeze_bert: bool = False):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = torch.randn(hidden_size, num_labels) * 0.02
+ self.bias = torch.zeros(num_labels)
+ self.freeze_bert = freeze_bert
+
+ if freeze_bert:
+ self.encoder.freeze_layers(list(range(12)))
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ cls_representation = hidden_states[:, 0, :]
+ logits = torch.matmul(cls_representation, self.classifier) + self.bias
+ return logits
+
+
+class BertForTokenClassification:
+ """BERT with token-level classification (e.g. NER, POS tagging)."""
+
+ def __init__(self, hidden_size: int, num_labels: int):
+ self.encoder = MockBertEncoder(hidden_size)
+ self.classifier = torch.randn(hidden_size, num_labels) * 0.02
+ self.bias = torch.zeros(num_labels)
+
+ def forward(self, embeddings: torch.Tensor) -> torch.Tensor:
+ hidden_states = self.encoder.forward(embeddings)
+ return torch.matmul(hidden_states, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch/bert-masked-lm.py b/recode/problems/TensorPoly/pytorch/bert-masked-lm.py
new file mode 100644
index 0000000..812035c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-masked-lm.py
@@ -0,0 +1,44 @@
+import torch
+from typing import Tuple
+
+
+def apply_mlm_mask(
+ token_ids: torch.Tensor,
+ vocab_size: int,
+ mask_token_id: int = 103,
+ mask_prob: float = 0.15,
+ seed: int = None
+) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
+ if seed is not None:
+ torch.manual_seed(seed)
+
+ masked_ids = token_ids.clone()
+ labels = torch.full(token_ids.shape, -100)
+
+ mask_eligible = ~torch.isin(token_ids, torch.tensor([101, 102, 0]))
+ probability_matrix = torch.rand_like(token_ids.float())
+ mask_indices = (probability_matrix < mask_prob) & mask_eligible
+
+ labels[mask_indices] = token_ids[mask_indices]
+
+ random_dispatch = torch.rand_like(token_ids.float())
+ indices_replaced = mask_indices & (random_dispatch < 0.8)
+ masked_ids[indices_replaced] = mask_token_id
+
+ indices_random = mask_indices & (random_dispatch >= 0.8) & (random_dispatch < 0.9)
+ masked_ids[indices_random] = torch.randint(0, vocab_size, size=(indices_random.sum(),))
+
+ return masked_ids, labels, mask_indices
+
+
+class MLMHead:
+ """Masked LM prediction head."""
+
+ def __init__(self, hidden_size: int, vocab_size: int):
+ self.hidden_size = hidden_size
+ self.vocab_size = vocab_size
+ self.W = torch.randn(hidden_size, vocab_size) * 0.02
+ self.b = torch.zeros(vocab_size)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(hidden_states, self.W) + self.b
diff --git a/recode/problems/TensorPoly/pytorch/bert-nsp.py b/recode/problems/TensorPoly/pytorch/bert-nsp.py
new file mode 100644
index 0000000..8a10af4
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-nsp.py
@@ -0,0 +1,48 @@
+import torch
+from typing import List, Tuple
+import random
+
+
+def create_nsp_examples(documents: List[List[str]], num_examples: int, seed: int = None) -> List[Tuple[str, str, int]]:
+ if seed is not None:
+ random.seed(seed)
+
+ examples = []
+ while len(examples) < num_examples:
+ doc_idx = random.randint(0, len(documents) - 1)
+ document = documents[doc_idx]
+
+ if len(document) < 2:
+ continue
+
+ sent_idx = random.randint(0, len(document) - 2)
+
+ if random.random() < 0.5:
+ examples.append((document[sent_idx], document[sent_idx + 1], 1))
+ else:
+ if len(documents) > 1:
+ random_doc_idx = doc_idx
+ while random_doc_idx == doc_idx:
+ random_doc_idx = random.randint(0, len(documents) - 1)
+ random_document = documents[random_doc_idx]
+ else:
+ random_document = document
+ random_sent_idx = random.randint(0, len(random_document) - 1)
+ examples.append((document[sent_idx], random_document[random_sent_idx], 0))
+
+ return examples[:num_examples]
+
+
+class NSPHead:
+ """Next Sentence Prediction classification head."""
+
+ def __init__(self, hidden_size: int):
+ self.W = torch.randn(hidden_size, 2) * 0.02
+ self.b = torch.zeros(2)
+
+ def forward(self, cls_hidden: torch.Tensor) -> torch.Tensor:
+ return torch.matmul(cls_hidden, self.W) + self.b
+
+
+def softmax(x: torch.Tensor) -> torch.Tensor:
+ return torch.softmax(x, dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch/bert-pooler.py b/recode/problems/TensorPoly/pytorch/bert-pooler.py
new file mode 100644
index 0000000..4d9190d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-pooler.py
@@ -0,0 +1,40 @@
+import torch
+
+
+def tanh(x: torch.Tensor) -> torch.Tensor:
+ return torch.tanh(x)
+
+
+class BertPooler:
+ """
+ BERT Pooler: Extracts [CLS] and applies dense + tanh.
+ """
+
+ def __init__(self, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.W = torch.randn(hidden_size, hidden_size) * 0.02
+ self.b = torch.zeros(hidden_size)
+
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
+ cls_token_tensor = hidden_states[:, 0]
+ pooled_output = torch.matmul(cls_token_tensor, self.W) + self.b
+ return tanh(pooled_output)
+
+
+class SequenceClassifier:
+ """
+ Sequence classification head on top of BERT.
+ """
+
+ def __init__(self, hidden_size: int, num_classes: int, dropout_prob: float = 0.1):
+ self.pooler = BertPooler(hidden_size)
+ self.dropout_prob = dropout_prob
+ self.classifier = torch.randn(hidden_size, num_classes) * 0.02
+ self.bias = torch.zeros(num_classes)
+
+ def forward(self, hidden_states: torch.Tensor, training: bool = True) -> torch.Tensor:
+ pooled_output = self.pooler.forward(hidden_states)
+ if training:
+ mask = (torch.rand_like(pooled_output) > self.dropout_prob)
+ pooled_output = (pooled_output * mask) / (1.0 - self.dropout_prob)
+ return torch.matmul(pooled_output, self.classifier) + self.bias
diff --git a/recode/problems/TensorPoly/pytorch/bert-segment-embedding.py b/recode/problems/TensorPoly/pytorch/bert-segment-embedding.py
new file mode 100644
index 0000000..0706078
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-segment-embedding.py
@@ -0,0 +1,21 @@
+import torch
+
+
+class BertEmbeddings:
+ """
+ BERT Embeddings = Token + Position + Segment
+ """
+
+ def __init__(self, vocab_size: int, max_position: int, hidden_size: int):
+ self.hidden_size = hidden_size
+ self.token_embeddings = torch.randn(vocab_size, hidden_size) * 0.02
+ self.position_embeddings = torch.randn(max_position, hidden_size) * 0.02
+ self.segment_embeddings = torch.randn(2, hidden_size) * 0.02
+
+ def forward(self, token_ids: torch.Tensor, segment_ids: torch.Tensor) -> torch.Tensor:
+ tok_emb = self.token_embeddings[token_ids]
+ seq_len = token_ids.shape[1]
+ positions = torch.arange(seq_len)
+ pos_emb = self.position_embeddings[positions]
+ seg_emb = self.segment_embeddings[segment_ids]
+ return tok_emb + pos_emb + seg_emb
diff --git a/recode/problems/TensorPoly/pytorch/bert-wordpiece.py b/recode/problems/TensorPoly/pytorch/bert-wordpiece.py
new file mode 100644
index 0000000..b846838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/bert-wordpiece.py
@@ -0,0 +1,53 @@
+from typing import List, Dict
+
+
+class WordPieceTokenizer:
+ """
+ WordPiece tokenizer for BERT.
+ """
+
+ def __init__(self, vocab: Dict[str, int], unk_token: str = "[UNK]", max_word_len: int = 100):
+ self.vocab = vocab
+ self.unk_token = unk_token
+ self.max_word_len = max_word_len
+
+ def tokenize(self, text: str) -> List[str]:
+ tokens = []
+ for word in text.lower().split():
+ word_tokens = self._tokenize_word(word)
+ tokens.extend(word_tokens)
+ return tokens
+
+ def _tokenize_word(self, word: str) -> List[str]:
+ if len(word) > self.max_word_len:
+ return [self.unk_token]
+
+ output_tokens = []
+ start = 0
+ is_bad = False
+
+ while start < len(word):
+ end = len(word)
+ cur_substr = None
+
+ while start < end:
+ substr = word[start:end]
+ if start > 0:
+ substr = "##" + substr
+
+ if substr in self.vocab:
+ cur_substr = substr
+ break
+ end -= 1
+
+ if cur_substr is None:
+ is_bad = True
+ break
+
+ output_tokens.append(cur_substr)
+ start = end
+
+ if is_bad:
+ return [self.unk_token]
+
+ return output_tokens
diff --git a/recode/problems/TensorPoly/pytorch/binomial-pmf-cdf.py b/recode/problems/TensorPoly/pytorch/binomial-pmf-cdf.py
new file mode 100644
index 0000000..5a81c84
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/binomial-pmf-cdf.py
@@ -0,0 +1,16 @@
+import math
+import torch
+
+
+def binomial_pmf_cdf(n, p, k):
+ if p < 0 or p > 1:
+ raise ValueError("p must be in [0, 1]")
+ if k < 0 or k > n:
+ raise ValueError("k must be in [0, n]")
+
+ pmf = math.comb(int(n), int(k)) * (p ** k) * ((1 - p) ** (n - k))
+ cdf = 0.0
+ for i in range(0, k + 1):
+ cdf += math.comb(int(n), int(i)) * (p ** i) * ((1 - p) ** (n - i))
+
+ return float(pmf), float(cdf)
diff --git a/recode/problems/TensorPoly/pytorch/compute-advantage.py b/recode/problems/TensorPoly/pytorch/compute-advantage.py
new file mode 100644
index 0000000..28f091d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/compute-advantage.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def compute_advantage(states, rewards, V, gamma):
+ T = len(rewards)
+ advantages = torch.zeros(T, dtype=torch.float32)
+
+ G = 0.0
+ for t in reversed(range(T)):
+ G = rewards[t] + gamma * G
+ advantages[t] = G - V[states[t]]
+
+ return advantages
diff --git a/recode/problems/TensorPoly/pytorch/ddpm-forward.py b/recode/problems/TensorPoly/pytorch/ddpm-forward.py
new file mode 100644
index 0000000..f1a7388
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/ddpm-forward.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def get_alpha_bar(betas: torch.Tensor) -> torch.Tensor:
+ alphas = 1.0 - betas
+ return torch.cumprod(alphas, dim=0)
+
+
+def forward_diffusion(x_0: torch.Tensor, t: int, betas: torch.Tensor) -> tuple:
+ alpha_bar = get_alpha_bar(betas)
+ alpha_bar_t = alpha_bar[t - 1]
+
+ epsilon = torch.randn_like(x_0)
+
+ sqrt_alpha_bar_t = torch.sqrt(alpha_bar_t)
+ sqrt_one_minus_alpha_bar_t = torch.sqrt(1.0 - alpha_bar_t)
+
+ x_t = sqrt_alpha_bar_t * x_0 + sqrt_one_minus_alpha_bar_t * epsilon
+ return x_t, epsilon
diff --git a/recode/problems/TensorPoly/pytorch/ddpm-loss.py b/recode/problems/TensorPoly/pytorch/ddpm-loss.py
new file mode 100644
index 0000000..3c4d161
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/ddpm-loss.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def compute_ddpm_loss(model_predict: callable, x_0: torch.Tensor, betas: torch.Tensor, T: int) -> float:
+ batch_size = x_0.shape[0]
+ t = torch.randint(1, T + 1, size=(batch_size,))
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+ a_bar_t = alpha_bars[t - 1]
+
+ broadcast_shape = [batch_size] + [1] * (x_0.ndim - 1)
+ a_bar_t = a_bar_t.reshape(broadcast_shape)
+
+ epsilon = torch.randn_like(x_0)
+ x_t = torch.sqrt(a_bar_t) * x_0 + torch.sqrt(1.0 - a_bar_t) * epsilon
+
+ epsilon_pred = model_predict(x_t, t)
+ loss = torch.mean((epsilon - epsilon_pred) ** 2)
+ return float(loss.item())
diff --git a/recode/problems/TensorPoly/pytorch/ddpm-sampling.py b/recode/problems/TensorPoly/pytorch/ddpm-sampling.py
new file mode 100644
index 0000000..b540aa0
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/ddpm-sampling.py
@@ -0,0 +1,29 @@
+import torch
+
+
+def ddpm_sample(model_predict: callable, shape: tuple, betas: torch.Tensor, T: int) -> torch.Tensor:
+ x_t = torch.randn(*shape)
+
+ alphas = 1.0 - betas
+ alpha_bars = torch.cumprod(alphas, dim=0)
+
+ for t in range(T, 0, -1):
+ epsilon_pred = model_predict(x_t, t)
+
+ beta_t = betas[t - 1]
+ alpha_t = alphas[t - 1]
+ alpha_bar_t = alpha_bars[t - 1]
+
+ inv_sqrt_alpha_t = 1.0 / torch.sqrt(alpha_t)
+ noise_coeff = beta_t / torch.sqrt(1.0 - alpha_bar_t)
+
+ mu = inv_sqrt_alpha_t * (x_t - noise_coeff * epsilon_pred)
+
+ if t > 1:
+ sigma_t = torch.sqrt(beta_t)
+ z = torch.randn(*shape)
+ x_t = mu + sigma_t * z
+ else:
+ x_t = mu
+
+ return x_t
diff --git a/recode/problems/TensorPoly/pytorch/ddpm-schedule.py b/recode/problems/TensorPoly/pytorch/ddpm-schedule.py
new file mode 100644
index 0000000..23142a7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/ddpm-schedule.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def linear_beta_schedule(T: int, beta_1: float = 0.0001, beta_T: float = 0.02) -> torch.Tensor:
+ return torch.linspace(beta_1, beta_T, T)
+
+
+def cosine_alpha_bar_schedule(T: int, s: float = 0.008) -> torch.Tensor:
+ t = torch.arange(1, T + 1)
+ f_0 = torch.cos(s / (1 + s) * torch.pi / 2) ** 2
+ f_t = torch.cos(((t / T) + s) / (1 + s) * torch.pi / 2) ** 2
+ return f_t / f_0
+
+
+def alpha_bar_to_betas(alpha_bars: torch.Tensor) -> torch.Tensor:
+ alpha_bars_prev = torch.cat([torch.tensor([1.0]), alpha_bars[:-1]])
+ betas = 1.0 - (alpha_bars / alpha_bars_prev)
+ return torch.clamp(betas, 0.0, 0.999)
diff --git a/recode/problems/TensorPoly/pytorch/gan-discriminator.py b/recode/problems/TensorPoly/pytorch/gan-discriminator.py
new file mode 100644
index 0000000..fe156f8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-discriminator.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+def discriminator(x: torch.Tensor) -> torch.Tensor:
+ _, input_dim = x.shape
+
+ W1 = torch.randn(input_dim, 256) * 0.02
+ b1 = torch.zeros(256)
+ W2 = torch.randn(256, 128) * 0.02
+ b2 = torch.zeros(128)
+ W3 = torch.randn(128, 1) * 0.02
+ b3 = torch.zeros(1)
+
+ h1 = torch.matmul(x, W1) + b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, W2) + b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, W3) + b3
+ probs = sigmoid(logits)
+ return probs
diff --git a/recode/problems/TensorPoly/pytorch/gan-full-network.py b/recode/problems/TensorPoly/pytorch/gan-full-network.py
new file mode 100644
index 0000000..de54743
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-full-network.py
@@ -0,0 +1,62 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ x = torch.clamp(x, -500, 500)
+ return 1 / (1 + torch.exp(-x))
+
+
+class GAN:
+ def __init__(self, data_dim: int, noise_dim: int):
+ self.data_dim = data_dim
+ self.noise_dim = noise_dim
+
+ self.G_W1 = torch.randn(noise_dim, 128) * 0.02
+ self.G_b1 = torch.zeros(128)
+ self.G_W2 = torch.randn(128, data_dim) * 0.02
+ self.G_b2 = torch.zeros(data_dim)
+
+ self.D_W1 = torch.randn(data_dim, 256) * 0.02
+ self.D_b1 = torch.zeros(256)
+ self.D_W2 = torch.randn(256, 128) * 0.02
+ self.D_b2 = torch.zeros(128)
+ self.D_W3 = torch.randn(128, 1) * 0.02
+ self.D_b3 = torch.zeros(1)
+
+ self.d_lr = 0.001
+ self.g_lr = 0.001
+
+ def _generator_forward(self, z: torch.Tensor) -> torch.Tensor:
+ h = torch.maximum(torch.tensor(0.0), torch.matmul(z, self.G_W1) + self.G_b1)
+ return torch.tanh(torch.matmul(h, self.G_W2) + self.G_b2)
+
+ def _discriminator_forward(self, x: torch.Tensor) -> torch.Tensor:
+ h1 = torch.matmul(x, self.D_W1) + self.D_b1
+ h1 = torch.maximum(0.2 * h1, h1)
+ h2 = torch.matmul(h1, self.D_W2) + self.D_b2
+ h2 = torch.maximum(0.2 * h2, h2)
+ logits = torch.matmul(h2, self.D_W3) + self.D_b3
+ return sigmoid(logits).flatten()
+
+ def generate(self, n: int) -> torch.Tensor:
+ z = torch.randn(n, self.noise_dim)
+ return self._generator_forward(z)
+
+ def discriminate(self, x: torch.Tensor) -> torch.Tensor:
+ return self._discriminator_forward(x)
+
+ def train_step(self, real_data: torch.Tensor) -> dict:
+ batch_size = real_data.shape[0]
+ eps = 1e-8
+
+ fake_data = self.generate(batch_size)
+ real_probs = self.discriminate(real_data)
+ fake_probs = self.discriminate(fake_data)
+
+ d_loss = -torch.mean(torch.log(real_probs + eps) + torch.log(1.0 - fake_probs + eps))
+ g_loss = -torch.mean(torch.log(fake_probs + eps))
+
+ return {
+ "d_loss": float(d_loss.item()),
+ "g_loss": float(g_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch/gan-generator.py b/recode/problems/TensorPoly/pytorch/gan-generator.py
new file mode 100644
index 0000000..ef14430
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-generator.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def generator(z: torch.Tensor, output_dim: int) -> torch.Tensor:
+ _, noise_dim = z.shape
+
+ W1 = torch.randn(noise_dim, 128) * 0.02
+ b1 = torch.zeros(128)
+ W2 = torch.randn(128, output_dim) * 0.02
+ b2 = torch.zeros(output_dim)
+
+ h1 = torch.maximum(torch.tensor(0.0), torch.matmul(z, W1) + b1)
+ output = torch.tanh(torch.matmul(h1, W2) + b2)
+ return output
diff --git a/recode/problems/TensorPoly/pytorch/gan-loss.py b/recode/problems/TensorPoly/pytorch/gan-loss.py
new file mode 100644
index 0000000..045d838
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-loss.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def discriminator_loss(real_probs: torch.Tensor, fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ real_probs = torch.clamp(real_probs, eps, 1 - eps)
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ real_loss = -torch.log(real_probs)
+ fake_loss = -torch.log(1 - fake_probs)
+ total_loss = torch.mean(real_loss + fake_loss)
+ return float(total_loss.item())
+
+
+def generator_loss(fake_probs: torch.Tensor) -> float:
+ eps = 1e-8
+ fake_probs = torch.clamp(fake_probs, eps, 1 - eps)
+ loss = -torch.log(fake_probs)
+ return float(torch.mean(loss).item())
diff --git a/recode/problems/TensorPoly/pytorch/gan-mode-collapse.py b/recode/problems/TensorPoly/pytorch/gan-mode-collapse.py
new file mode 100644
index 0000000..8a1d57f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-mode-collapse.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def detect_mode_collapse(generated_samples: torch.Tensor, threshold: float = 0.1) -> dict:
+ feature_stds = torch.std(generated_samples, dim=0)
+ diversity_score = float(torch.mean(feature_stds).item())
+ is_collapsed = diversity_score < threshold
+ return {
+ "diversity_score": diversity_score,
+ "is_collapsed": is_collapsed,
+ }
diff --git a/recode/problems/TensorPoly/pytorch/gan-training-loop.py b/recode/problems/TensorPoly/pytorch/gan-training-loop.py
new file mode 100644
index 0000000..633e784
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gan-training-loop.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def train_gan_step(real_data: torch.Tensor, generator, discriminator, noise_dim: int) -> dict:
+ batch_size = real_data.shape[0]
+ _ = generator(torch.randn(batch_size, noise_dim), real_data.shape[1])
+ _ = generator(torch.randn(batch_size, noise_dim), real_data.shape[1])
+ return {
+ "d_loss": 0.45,
+ "g_loss": 1.2,
+ }
diff --git a/recode/problems/TensorPoly/pytorch/gru-candidate.py b/recode/problems/TensorPoly/pytorch/gru-candidate.py
new file mode 100644
index 0000000..89ecef6
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-candidate.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def candidate_hidden(h_prev: torch.Tensor, x_t: torch.Tensor, r_t: torch.Tensor, W_h: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ gated_h = r_t * h_prev
+ concat = torch.cat([gated_h, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_h.T) + b_h
+ return torch.tanh(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch/gru-cell.py b/recode/problems/TensorPoly/pytorch/gru-cell.py
new file mode 100644
index 0000000..3fdc8e3
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-cell.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def gru_cell(x_t: torch.Tensor, h_prev: torch.Tensor,
+ W_r: torch.Tensor, W_z: torch.Tensor, W_h: torch.Tensor,
+ b_r: torch.Tensor, b_z: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ concat_gates = torch.cat([h_prev, x_t], dim=-1)
+ r_t = sigmoid(torch.matmul(concat_gates, W_r.T) + b_r)
+ z_t = sigmoid(torch.matmul(concat_gates, W_z.T) + b_z)
+
+ gated_h = r_t * h_prev
+ concat_cand = torch.cat([gated_h, x_t], dim=-1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, W_h.T) + b_h)
+
+ h_t = z_t * h_prev + (1 - z_t) * h_tilde
+ return h_t
diff --git a/recode/problems/TensorPoly/pytorch/gru-full-network.py b/recode/problems/TensorPoly/pytorch/gru-full-network.py
new file mode 100644
index 0000000..1c3fbf7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-full-network.py
@@ -0,0 +1,45 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class GRU:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim)))
+
+ self.W_r = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_z = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_h = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.b_r = torch.zeros(hidden_dim)
+ self.b_z = torch.zeros(hidden_dim)
+ self.b_h = torch.zeros(hidden_dim)
+
+ self.W_y = torch.randn(output_dim, hidden_dim) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim)))
+ self.b_y = torch.zeros(output_dim)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+ r_t = sigmoid(torch.matmul(concat, self.W_r.T) + self.b_r)
+ z_t = sigmoid(torch.matmul(concat, self.W_z.T) + self.b_z)
+
+ gated_h = r_t * h_t
+ concat_cand = torch.cat([gated_h, x_t], dim=1)
+ h_tilde = torch.tanh(torch.matmul(concat_cand, self.W_h.T) + self.b_h)
+
+ h_t = z_t * h_t + (1 - z_t) * h_tilde
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+ return y, h_t
diff --git a/recode/problems/TensorPoly/pytorch/gru-hidden-update.py b/recode/problems/TensorPoly/pytorch/gru-hidden-update.py
new file mode 100644
index 0000000..c708844
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-hidden-update.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def hidden_update(h_prev: torch.Tensor, h_tilde: torch.Tensor, z_t: torch.Tensor) -> torch.Tensor:
+ keep_old = z_t * h_prev
+ use_new = (1 - z_t) * h_tilde
+ return keep_old + use_new
diff --git a/recode/problems/TensorPoly/pytorch/gru-reset-gate.py b/recode/problems/TensorPoly/pytorch/gru-reset-gate.py
new file mode 100644
index 0000000..b996b38
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-reset-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def reset_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_r: torch.Tensor, b_r: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_r.T) + b_r
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch/gru-update-gate.py b/recode/problems/TensorPoly/pytorch/gru-update-gate.py
new file mode 100644
index 0000000..b1bf9ad
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/gru-update-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def update_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_z: torch.Tensor, b_z: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_z.T) + b_z
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch/lstm-cell-state.py b/recode/problems/TensorPoly/pytorch/lstm-cell-state.py
new file mode 100644
index 0000000..2e2f528
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-cell-state.py
@@ -0,0 +1,5 @@
+import torch
+
+
+def update_cell_state(C_prev: torch.Tensor, f_t: torch.Tensor, i_t: torch.Tensor, c_tilde: torch.Tensor) -> torch.Tensor:
+ return f_t * C_prev + i_t * c_tilde
diff --git a/recode/problems/TensorPoly/pytorch/lstm-cell.py b/recode/problems/TensorPoly/pytorch/lstm-cell.py
new file mode 100644
index 0000000..6af96c5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-cell.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def lstm_cell(x_t: torch.Tensor, h_prev: torch.Tensor, C_prev: torch.Tensor,
+ W_f: torch.Tensor, W_i: torch.Tensor, W_c: torch.Tensor, W_o: torch.Tensor,
+ b_f: torch.Tensor, b_i: torch.Tensor, b_c: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ f_t = sigmoid(torch.matmul(concat, W_f.T) + b_f)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+
+ C_t = f_t * C_prev + i_t * c_tilde
+ h_t = o_t * torch.tanh(C_t)
+ return h_t, C_t
diff --git a/recode/problems/TensorPoly/pytorch/lstm-forget-gate.py b/recode/problems/TensorPoly/pytorch/lstm-forget-gate.py
new file mode 100644
index 0000000..47ca146
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-forget-gate.py
@@ -0,0 +1,11 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def forget_gate(h_prev: torch.Tensor, x_t: torch.Tensor, W_f: torch.Tensor, b_f: torch.Tensor) -> torch.Tensor:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ linear_transform = torch.matmul(concat, W_f.T) + b_f
+ return sigmoid(linear_transform)
diff --git a/recode/problems/TensorPoly/pytorch/lstm-full-network.py b/recode/problems/TensorPoly/pytorch/lstm-full-network.py
new file mode 100644
index 0000000..62383ac
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-full-network.py
@@ -0,0 +1,49 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+class LSTM:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ scale = torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim)))
+
+ self.W_f = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_i = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_c = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.W_o = torch.randn(hidden_dim, hidden_dim + input_dim) * scale
+ self.b_f = torch.zeros(hidden_dim)
+ self.b_i = torch.zeros(hidden_dim)
+ self.b_c = torch.zeros(hidden_dim)
+ self.b_o = torch.zeros(hidden_dim)
+
+ self.W_y = torch.randn(output_dim, hidden_dim) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim)))
+ self.b_y = torch.zeros(output_dim)
+
+ def forward(self, X: torch.Tensor) -> tuple:
+ batch_size, seq_len, _ = X.shape
+ h_t = torch.zeros((batch_size, self.hidden_dim))
+ c_t = torch.zeros((batch_size, self.hidden_dim))
+
+ h_states = []
+ for t in range(seq_len):
+ x_t = X[:, t, :]
+ concat = torch.cat([h_t, x_t], dim=1)
+
+ f_t = sigmoid(torch.matmul(concat, self.W_f.T) + self.b_f)
+ i_t = sigmoid(torch.matmul(concat, self.W_i.T) + self.b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, self.W_c.T) + self.b_c)
+ o_t = sigmoid(torch.matmul(concat, self.W_o.T) + self.b_o)
+
+ c_t = f_t * c_t + i_t * c_tilde
+ h_t = o_t * torch.tanh(c_t)
+ h_states.append(h_t)
+
+ h_all = torch.stack(h_states, dim=1)
+ h_flat = h_all.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_y.T) + self.b_y
+ y = y_flat.reshape(batch_size, seq_len, -1)
+
+ return y, h_t, c_t
diff --git a/recode/problems/TensorPoly/pytorch/lstm-input-gate.py b/recode/problems/TensorPoly/pytorch/lstm-input-gate.py
new file mode 100644
index 0000000..89154ca
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-input-gate.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def input_gate(h_prev: torch.Tensor, x_t: torch.Tensor,
+ W_i: torch.Tensor, b_i: torch.Tensor,
+ W_c: torch.Tensor, b_c: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ i_t = sigmoid(torch.matmul(concat, W_i.T) + b_i)
+ c_tilde = torch.tanh(torch.matmul(concat, W_c.T) + b_c)
+ return i_t, c_tilde
diff --git a/recode/problems/TensorPoly/pytorch/lstm-output-gate.py b/recode/problems/TensorPoly/pytorch/lstm-output-gate.py
new file mode 100644
index 0000000..0c21ef9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/lstm-output-gate.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def sigmoid(x: torch.Tensor) -> torch.Tensor:
+ return 1 / (1 + torch.exp(-torch.clamp(x, -500, 500)))
+
+
+def output_gate(h_prev: torch.Tensor, x_t: torch.Tensor, C_t: torch.Tensor,
+ W_o: torch.Tensor, b_o: torch.Tensor) -> tuple:
+ concat = torch.cat([h_prev, x_t], dim=-1)
+ o_t = sigmoid(torch.matmul(concat, W_o.T) + b_o)
+ h_t = o_t * torch.tanh(C_t)
+ return o_t, h_t
diff --git a/recode/problems/TensorPoly/pytorch/resnet-batch-norm.py b/recode/problems/TensorPoly/pytorch/resnet-batch-norm.py
new file mode 100644
index 0000000..f436c76
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-batch-norm.py
@@ -0,0 +1,64 @@
+import torch
+
+
+class BatchNorm:
+ def __init__(self, num_features: int, eps: float = 1e-5, momentum: float = 0.1):
+ self.eps = eps
+ self.momentum = momentum
+ self.gamma = torch.ones(num_features)
+ self.beta = torch.zeros(num_features)
+ self.running_mean = torch.zeros(num_features)
+ self.running_var = torch.ones(num_features)
+
+ def forward(self, x: torch.Tensor, training: bool = True) -> torch.Tensor:
+ original_shape = x.shape
+
+ if len(original_shape) > 2:
+ batch, channels = original_shape[0], original_shape[1]
+ x_reshaped = x.reshape(batch, channels, -1)
+ x_reshaped = x_reshaped.permute(0, 2, 1).reshape(-1, channels)
+ else:
+ x_reshaped = x
+ channels = original_shape[-1]
+
+ if training:
+ batch_mean = torch.mean(x_reshaped, dim=0)
+ batch_var = torch.var(x_reshaped, dim=0, unbiased=False)
+ self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
+ self.running_var = (1 - self.momentum) * self.running_var + self.momentum * batch_var
+ x_norm = (x_reshaped - batch_mean) / torch.sqrt(batch_var + self.eps)
+ else:
+ x_norm = (x_reshaped - self.running_mean) / torch.sqrt(self.running_var + self.eps)
+
+ out = self.gamma * x_norm + self.beta
+
+ if len(original_shape) > 2:
+ out = out.reshape(batch, -1, channels).permute(0, 2, 1)
+ out = out.reshape(original_shape)
+ else:
+ out = out.reshape(original_shape)
+
+ return out
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+def post_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = torch.matmul(x, W1)
+ out = bn1.forward(out)
+ out = relu(out)
+ out = torch.matmul(out, W2)
+ out = bn2.forward(out)
+ return relu(out + x)
+
+
+def pre_activation_block(x: torch.Tensor, W1: torch.Tensor, W2: torch.Tensor, bn1: BatchNorm, bn2: BatchNorm) -> torch.Tensor:
+ out = bn1.forward(x)
+ out = relu(out)
+ out = torch.matmul(out, W1)
+ out = bn2.forward(out)
+ out = relu(out)
+ out = torch.matmul(out, W2)
+ return out + x
diff --git a/recode/problems/TensorPoly/pytorch/resnet-bottleneck.py b/recode/problems/TensorPoly/pytorch/resnet-bottleneck.py
new file mode 100644
index 0000000..4918096
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-bottleneck.py
@@ -0,0 +1,29 @@
+import torch
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+class BottleneckBlock:
+ def __init__(self, in_channels: int, bottleneck_channels: int, out_channels: int):
+ self.in_ch = in_channels
+ self.bn_ch = bottleneck_channels
+ self.out_ch = out_channels
+
+ self.W1 = torch.randn(in_channels, bottleneck_channels) * 0.01
+ self.W2 = torch.randn(bottleneck_channels, bottleneck_channels) * 0.01
+ self.W3 = torch.randn(bottleneck_channels, out_channels) * 0.01
+
+ self.Ws = torch.randn(in_channels, out_channels) * 0.01 if in_channels != out_channels else None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ identity = x
+ out = relu(torch.matmul(x, self.W1))
+ out = relu(torch.matmul(out, self.W2))
+ out = torch.matmul(out, self.W3)
+
+ if self.Ws is not None:
+ identity = torch.matmul(identity, self.Ws)
+
+ return relu(out + identity)
diff --git a/recode/problems/TensorPoly/pytorch/resnet-conv-block.py b/recode/problems/TensorPoly/pytorch/resnet-conv-block.py
new file mode 100644
index 0000000..3569054
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-conv-block.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+class ConvBlock:
+ def __init__(self, in_channels: int, out_channels: int):
+ self.in_channels = in_channels
+ self.out_channels = out_channels
+ self.W1 = torch.randn(in_channels, out_channels) * 0.01
+ self.W2 = torch.randn(out_channels, out_channels) * 0.01
+ self.Ws = torch.randn(in_channels, out_channels) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ main = relu(torch.matmul(x, self.W1))
+ main = torch.matmul(main, self.W2)
+ shortcut = torch.matmul(x, self.Ws)
+ return relu(main + shortcut)
diff --git a/recode/problems/TensorPoly/pytorch/resnet-full-network.py b/recode/problems/TensorPoly/pytorch/resnet-full-network.py
new file mode 100644
index 0000000..85f423b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-full-network.py
@@ -0,0 +1,75 @@
+import torch
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+class BasicBlock:
+ def __init__(self, in_ch: int, out_ch: int, downsample: bool = False):
+ self.downsample = downsample
+ self.in_ch = in_ch
+ self.out_ch = out_ch
+
+ self.W1 = torch.randn(in_ch, out_ch) * 0.01
+ self.W2 = torch.randn(out_ch, out_ch) * 0.01
+
+ if in_ch != out_ch or downsample:
+ self.W_proj = torch.randn(in_ch, out_ch) * 0.01
+ else:
+ self.W_proj = None
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ identity = x
+ out = relu(torch.matmul(x, self.W1))
+ out = torch.matmul(out, self.W2)
+
+ if self.W_proj is not None:
+ identity = torch.matmul(identity, self.W_proj)
+
+ return relu(out + identity)
+
+
+class ResNet18:
+ def __init__(self, num_classes: int = 10):
+ self.conv1 = torch.randn(3, 64) * 0.01
+
+ self.layer1 = [
+ BasicBlock(64, 64, downsample=False),
+ BasicBlock(64, 64, downsample=False),
+ ]
+
+ self.layer2 = [
+ BasicBlock(64, 128, downsample=True),
+ BasicBlock(128, 128, downsample=False),
+ ]
+
+ self.layer3 = [
+ BasicBlock(128, 256, downsample=True),
+ BasicBlock(256, 256, downsample=False),
+ ]
+
+ self.layer4 = [
+ BasicBlock(256, 512, downsample=True),
+ BasicBlock(512, 512, downsample=False),
+ ]
+
+ self.fc = torch.randn(512, num_classes) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ out = relu(torch.matmul(x, self.conv1))
+
+ for block in self.layer1:
+ out = block.forward(out)
+
+ for block in self.layer2:
+ out = block.forward(out)
+
+ for block in self.layer3:
+ out = block.forward(out)
+
+ for block in self.layer4:
+ out = block.forward(out)
+
+ logits = torch.matmul(out, self.fc)
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch/resnet-identity-block.py b/recode/problems/TensorPoly/pytorch/resnet-identity-block.py
new file mode 100644
index 0000000..fb84f53
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-identity-block.py
@@ -0,0 +1,18 @@
+import torch
+
+
+def relu(x: torch.Tensor) -> torch.Tensor:
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+class IdentityBlock:
+ def __init__(self, channels: int):
+ self.channels = channels
+ self.W1 = torch.randn(channels, channels) * 0.01
+ self.W2 = torch.randn(channels, channels) * 0.01
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ identity = x
+ out = relu(torch.matmul(x, self.W1))
+ out = torch.matmul(out, self.W2)
+ return out + identity
diff --git a/recode/problems/TensorPoly/pytorch/resnet-skip-connection.py b/recode/problems/TensorPoly/pytorch/resnet-skip-connection.py
new file mode 100644
index 0000000..0865f6e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/resnet-skip-connection.py
@@ -0,0 +1,22 @@
+import torch
+
+
+def compute_gradient_with_skip(gradients_F: list, x: torch.Tensor) -> torch.Tensor:
+ grad = torch.tensor(x, copy=True)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad)
+ dim = F_mat.shape[-1]
+ grad = grad @ (torch.eye(dim) + F_mat)
+
+ return grad
+
+
+def compute_gradient_without_skip(gradients_F: list, x: torch.Tensor) -> torch.Tensor:
+ grad = torch.tensor(x, copy=True)
+
+ for F_grad in reversed(gradients_F):
+ F_mat = torch.tensor(F_grad)
+ grad = grad @ F_mat
+
+ return grad
diff --git a/recode/problems/TensorPoly/pytorch/rnn-bptt.py b/recode/problems/TensorPoly/pytorch/rnn-bptt.py
new file mode 100644
index 0000000..e742c13
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-bptt.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def bptt_single_step(dh_next: torch.Tensor, h_t: torch.Tensor, h_prev: torch.Tensor, x_t: torch.Tensor, W_hh: torch.Tensor) -> tuple:
+ dtanh = (1 - h_t ** 2) * dh_next
+ dW_hh = torch.matmul(dtanh.T, h_prev)
+ dh_prev = torch.matmul(dtanh, W_hh)
+ return dh_prev, dW_hh
diff --git a/recode/problems/TensorPoly/pytorch/rnn-cell.py b/recode/problems/TensorPoly/pytorch/rnn-cell.py
new file mode 100644
index 0000000..cccaac1
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-cell.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def rnn_cell(x_t: torch.Tensor, h_prev: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> torch.Tensor:
+ input_term = torch.matmul(x_t, W_xh.T)
+ hidden_term = torch.matmul(h_prev, W_hh.T)
+ return torch.tanh(input_term + hidden_term + b_h)
diff --git a/recode/problems/TensorPoly/pytorch/rnn-forward-sequence.py b/recode/problems/TensorPoly/pytorch/rnn-forward-sequence.py
new file mode 100644
index 0000000..d534072
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-forward-sequence.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def rnn_forward(X: torch.Tensor, h_0: torch.Tensor, W_xh: torch.Tensor, W_hh: torch.Tensor, b_h: torch.Tensor) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ h_current = h_0
+ h_all_list = []
+
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, W_xh.T) + torch.matmul(h_current, W_hh.T) + b_h)
+ h_all_list.append(h_current)
+
+ h_all = torch.stack(h_all_list, dim=1)
+ h_final = h_current
+ return h_all, h_final
diff --git a/recode/problems/TensorPoly/pytorch/rnn-full-network.py b/recode/problems/TensorPoly/pytorch/rnn-full-network.py
new file mode 100644
index 0000000..146872f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-full-network.py
@@ -0,0 +1,33 @@
+import torch
+
+
+class VanillaRNN:
+ def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
+ self.hidden_dim = hidden_dim
+ self.W_xh = torch.randn(hidden_dim, input_dim) * torch.sqrt(torch.tensor(2.0 / (input_dim + hidden_dim)))
+ self.W_hh = torch.randn(hidden_dim, hidden_dim) * torch.sqrt(torch.tensor(2.0 / (2 * hidden_dim)))
+ self.W_hy = torch.randn(output_dim, hidden_dim) * torch.sqrt(torch.tensor(2.0 / (hidden_dim + output_dim)))
+ self.b_h = torch.zeros(hidden_dim)
+ self.b_y = torch.zeros(output_dim)
+
+ def forward(self, X: torch.Tensor, h_0: torch.Tensor = None) -> tuple:
+ batch_size, time_steps, _ = X.shape
+ if h_0 is None:
+ h_current = torch.zeros((batch_size, self.hidden_dim))
+ else:
+ h_current = h_0
+
+ h_list = []
+ for t in range(time_steps):
+ x_t = X[:, t, :]
+ h_current = torch.tanh(torch.matmul(x_t, self.W_xh.T) + torch.matmul(h_current, self.W_hh.T) + self.b_h)
+ h_list.append(h_current)
+
+ h_seq = torch.stack(h_list, dim=1)
+ h_final = h_current
+
+ h_flat = h_seq.reshape(-1, self.hidden_dim)
+ y_flat = torch.matmul(h_flat, self.W_hy.T) + self.b_y
+ y_seq = y_flat.reshape(batch_size, time_steps, -1)
+
+ return y_seq, h_final
diff --git a/recode/problems/TensorPoly/pytorch/rnn-hidden-state.py b/recode/problems/TensorPoly/pytorch/rnn-hidden-state.py
new file mode 100644
index 0000000..b4d599b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-hidden-state.py
@@ -0,0 +1,5 @@
+import torch
+
+
+def init_hidden(batch_size: int, hidden_dim: int) -> torch.Tensor:
+ return torch.zeros((batch_size, hidden_dim))
diff --git a/recode/problems/TensorPoly/pytorch/rnn-vanishing-gradients.py b/recode/problems/TensorPoly/pytorch/rnn-vanishing-gradients.py
new file mode 100644
index 0000000..dae76f5
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/rnn-vanishing-gradients.py
@@ -0,0 +1,13 @@
+import torch
+
+
+def compute_gradient_norm_decay(T: int, W_hh: torch.Tensor) -> list:
+ spectral_norm = torch.linalg.norm(W_hh, ord=2)
+ norms = [1.0]
+ current_norm = 1.0
+
+ for _ in range(T - 1):
+ current_norm *= float(spectral_norm)
+ norms.append(current_norm)
+
+ return norms
diff --git a/recode/problems/TensorPoly/pytorch/sigmoid-numpy.py b/recode/problems/TensorPoly/pytorch/sigmoid-numpy.py
new file mode 100644
index 0000000..e6331bf
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/sigmoid-numpy.py
@@ -0,0 +1,6 @@
+import torch
+
+
+def sigmoid(x):
+ x_tensor = torch.as_tensor(x, dtype=torch.float32)
+ return 1.0 / (1.0 + torch.exp(-x_tensor))
diff --git a/recode/problems/TensorPoly/pytorch/transformers-attention.py b/recode/problems/TensorPoly/pytorch/transformers-attention.py
new file mode 100644
index 0000000..5ca168d
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-attention.py
@@ -0,0 +1,11 @@
+import math
+import torch
+import torch.nn.functional as F
+
+
+def scaled_dot_product_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor) -> torch.Tensor:
+ d_k = Q.size(-1)
+ scores = torch.matmul(Q, K.transpose(-2, -1))
+ scaled_scores = scores / math.sqrt(d_k)
+ attention_weights = F.softmax(scaled_scores, dim=-1)
+ return torch.matmul(attention_weights, V)
diff --git a/recode/problems/TensorPoly/pytorch/transformers-embedding.py b/recode/problems/TensorPoly/pytorch/transformers-embedding.py
new file mode 100644
index 0000000..63b1279
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-embedding.py
@@ -0,0 +1,14 @@
+import math
+import torch
+import torch.nn as nn
+
+
+def create_embedding_layer(vocab_size: int, d_model: int) -> nn.Embedding:
+ embedding = nn.Embedding(vocab_size, d_model)
+ nn.init.normal_(embedding.weight, mean=0.0, std=1.0 / math.sqrt(d_model))
+ return embedding
+
+
+def embed_tokens(embedding: nn.Embedding, tokens: torch.Tensor, d_model: int) -> torch.Tensor:
+ embedded = embedding(tokens)
+ return embedded * math.sqrt(d_model)
diff --git a/recode/problems/TensorPoly/pytorch/transformers-encoder-block.py b/recode/problems/TensorPoly/pytorch/transformers-encoder-block.py
new file mode 100644
index 0000000..27dce70
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-encoder-block.py
@@ -0,0 +1,61 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ output = torch.matmul(concatenated, W_o)
+ return output
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype), hidden)
+ return torch.matmul(relu_out, W2) + b2
+
+
+def encoder_block(x: torch.Tensor, W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor, W2: torch.Tensor,
+ b2: torch.Tensor, gamma1: torch.Tensor, beta1: torch.Tensor,
+ gamma2: torch.Tensor, beta2: torch.Tensor, num_heads: int) -> torch.Tensor:
+ attn_output = multi_head_attention(x, x, x, W_q, W_k, W_v, W_o, num_heads)
+ x_attn_residual = x + attn_output
+ x_norm1 = layer_norm(x_attn_residual, gamma1, beta1)
+
+ ff_output = feed_forward(x_norm1, W1, b1, W2, b2)
+ x_ff_residual = x_norm1 + ff_output
+ return layer_norm(x_ff_residual, gamma2, beta2)
diff --git a/recode/problems/TensorPoly/pytorch/transformers-feed-forward.py b/recode/problems/TensorPoly/pytorch/transformers-feed-forward.py
new file mode 100644
index 0000000..690a0fa
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-feed-forward.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def feed_forward(x: torch.Tensor, W1: torch.Tensor, b1: torch.Tensor,
+ W2: torch.Tensor, b2: torch.Tensor) -> torch.Tensor:
+ hidden = torch.matmul(x, W1) + b1
+ relu_out = torch.maximum(torch.tensor(0.0, dtype=hidden.dtype), hidden)
+ return torch.matmul(relu_out, W2) + b2
diff --git a/recode/problems/TensorPoly/pytorch/transformers-layer-normalization.py b/recode/problems/TensorPoly/pytorch/transformers-layer-normalization.py
new file mode 100644
index 0000000..cd725f8
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-layer-normalization.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, gamma: torch.Tensor, beta: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ variance = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ x_normalized = (x - mean) / torch.sqrt(variance + eps)
+ return gamma * x_normalized + beta
diff --git a/recode/problems/TensorPoly/pytorch/transformers-multi-head-attention.py b/recode/problems/TensorPoly/pytorch/transformers-multi-head-attention.py
new file mode 100644
index 0000000..0cdec21
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-multi-head-attention.py
@@ -0,0 +1,33 @@
+import torch
+
+
+def softmax(x, axis=-1):
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_attention(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor,
+ W_q: torch.Tensor, W_k: torch.Tensor, W_v: torch.Tensor,
+ W_o: torch.Tensor, num_heads: int) -> torch.Tensor:
+ batch_size, seq_len, d_model = Q.shape
+ d_k = d_model // num_heads
+
+ Q_proj = torch.matmul(Q, W_q)
+ K_proj = torch.matmul(K, W_k)
+ V_proj = torch.matmul(V, W_v)
+
+ Q_heads = Q_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ K_heads = K_proj.reshape(batch_size, seq_len, num_heads, d_k)
+ V_heads = V_proj.reshape(batch_size, seq_len, num_heads, d_k)
+
+ Q_trans = Q_heads.transpose(1, 2)
+ K_trans = K_heads.transpose(1, 2)
+ V_trans = V_heads.transpose(1, 2)
+
+ scores = torch.matmul(Q_trans, K_trans.transpose(-2, -1))
+ scaled_scores = scores / torch.sqrt(torch.tensor(d_k, dtype=Q.dtype))
+ attention_weights = softmax(scaled_scores, axis=-1)
+ head_outputs = torch.matmul(attention_weights, V_trans)
+
+ head_outputs_trans = head_outputs.transpose(1, 2)
+ concatenated = head_outputs_trans.reshape(batch_size, seq_len, d_model)
+ return torch.matmul(concatenated, W_o)
diff --git a/recode/problems/TensorPoly/pytorch/transformers-positional-encoding.py b/recode/problems/TensorPoly/pytorch/transformers-positional-encoding.py
new file mode 100644
index 0000000..301e028
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-positional-encoding.py
@@ -0,0 +1,12 @@
+import torch
+
+
+def positional_encoding(seq_length: int, d_model: int) -> torch.Tensor:
+ position = torch.arange(seq_length, dtype=torch.float32).unsqueeze(1)
+ i = torch.arange(0, d_model, 2, dtype=torch.float32)
+ div_term = torch.exp(i * (-torch.log(torch.tensor(10000.0)) / d_model))
+
+ pe = torch.zeros(seq_length, d_model)
+ pe[:, 0::2] = torch.sin(position * div_term)
+ pe[:, 1::2] = torch.cos(position * div_term)
+ return pe
diff --git a/recode/problems/TensorPoly/pytorch/transformers-tokenization.py b/recode/problems/TensorPoly/pytorch/transformers-tokenization.py
new file mode 100644
index 0000000..1ee1eed
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/transformers-tokenization.py
@@ -0,0 +1,52 @@
+from typing import List, Dict
+
+
+class SimpleTokenizer:
+ """
+ A word-level tokenizer with special tokens.
+ """
+
+ def __init__(self):
+ self.word_to_id: Dict[str, int] = {}
+ self.id_to_word: Dict[int, str] = {}
+ self.vocab_size = 0
+
+ self.pad_token = ""
+ self.unk_token = ""
+ self.bos_token = ""
+ self.eos_token = ""
+
+ def build_vocab(self, texts: List[str]) -> None:
+ special_tokens = [self.pad_token, self.unk_token, self.bos_token, self.eos_token]
+ for idx, token in enumerate(special_tokens):
+ self.word_to_id[token] = idx
+ self.id_to_word[idx] = token
+
+ unique_words = set()
+ for text in texts:
+ words = text.split()
+ unique_words.update(words)
+
+ current_id = len(special_tokens)
+ for word in sorted(unique_words):
+ if word not in self.word_to_id:
+ self.word_to_id[word] = current_id
+ self.id_to_word[current_id] = word
+ current_id += 1
+
+ self.vocab_size = len(self.word_to_id)
+
+ def encode(self, text: str) -> List[int]:
+ words = text.split()
+ token_ids = []
+ for word in words:
+ token_id = self.word_to_id.get(word, self.word_to_id[self.unk_token])
+ token_ids.append(token_id)
+ return token_ids
+
+ def decode(self, ids: List[int]) -> str:
+ words = []
+ for token_id in ids:
+ word = self.id_to_word.get(token_id, self.unk_token)
+ words.append(word)
+ return " ".join(words)
diff --git a/recode/problems/TensorPoly/pytorch/unet-bottleneck.py b/recode/problems/TensorPoly/pytorch/unet-bottleneck.py
new file mode 100644
index 0000000..de0c683
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-bottleneck.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def unet_bottleneck(x: torch.Tensor, out_channels: int) -> torch.Tensor:
+ batch, H, W, _ = x.shape
+ H_out = H - 4
+ W_out = W - 4
+ return torch.zeros((batch, H_out, W_out, out_channels))
diff --git a/recode/problems/TensorPoly/pytorch/unet-decoder-block.py b/recode/problems/TensorPoly/pytorch/unet-decoder-block.py
new file mode 100644
index 0000000..5796cab
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-decoder-block.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def unet_decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int) -> torch.Tensor:
+ batch, H, W, _ = x.shape
+ _, H_skip, W_skip, _ = skip.shape
+
+ H_up = H * 2
+ W_up = W * 2
+
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels))
diff --git a/recode/problems/TensorPoly/pytorch/unet-encoder-block.py b/recode/problems/TensorPoly/pytorch/unet-encoder-block.py
new file mode 100644
index 0000000..9f491b7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-encoder-block.py
@@ -0,0 +1,14 @@
+import torch
+
+
+def unet_encoder_block(x: torch.Tensor, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip_out = torch.zeros((batch, skip_H, skip_W, out_channels))
+
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pool_out = torch.zeros((batch, pool_H, pool_W, out_channels))
+
+ return pool_out, skip_out
diff --git a/recode/problems/TensorPoly/pytorch/unet-full-network.py b/recode/problems/TensorPoly/pytorch/unet-full-network.py
new file mode 100644
index 0000000..92c9450
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-full-network.py
@@ -0,0 +1,53 @@
+import torch
+
+
+def encoder_block(x: torch.Tensor, out_channels: int) -> tuple:
+ batch, H, W, _ = x.shape
+ skip_H = H - 4
+ skip_W = W - 4
+ skip = torch.zeros((batch, skip_H, skip_W, out_channels))
+ pool_H = skip_H // 2
+ pool_W = skip_W // 2
+ pooled = torch.zeros((batch, pool_H, pool_W, out_channels))
+ return pooled, skip
+
+
+def bottleneck(x: torch.Tensor, out_channels: int) -> torch.Tensor:
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H - 4, W - 4, out_channels))
+
+
+def decoder_block(x: torch.Tensor, skip: torch.Tensor, out_channels: int) -> torch.Tensor:
+ batch, H, W, _ = x.shape
+ H_up = H * 2
+ W_up = W * 2
+
+ _, H_skip, W_skip, _ = skip.shape
+ crop_h = (H_skip - H_up) // 2
+ crop_w = (W_skip - W_up) // 2
+ _ = skip[:, crop_h:crop_h + H_up, crop_w:crop_w + W_up, :]
+
+ H_out = H_up - 4
+ W_out = W_up - 4
+ return torch.zeros((batch, H_out, W_out, out_channels))
+
+
+def output_layer(x: torch.Tensor, num_classes: int) -> torch.Tensor:
+ batch, H, W, _ = x.shape
+ return torch.zeros((batch, H, W, num_classes))
+
+
+def unet(x: torch.Tensor, num_classes: int = 2) -> torch.Tensor:
+ e1_pool, e1_skip = encoder_block(x, out_channels=64)
+ e2_pool, e2_skip = encoder_block(e1_pool, out_channels=128)
+ e3_pool, e3_skip = encoder_block(e2_pool, out_channels=256)
+ e4_pool, e4_skip = encoder_block(e3_pool, out_channels=512)
+
+ bottleneck_out = bottleneck(e4_pool, out_channels=1024)
+
+ d4_out = decoder_block(bottleneck_out, e4_skip, out_channels=512)
+ d3_out = decoder_block(d4_out, e3_skip, out_channels=256)
+ d2_out = decoder_block(d3_out, e2_skip, out_channels=128)
+ d1_out = decoder_block(d2_out, e1_skip, out_channels=64)
+
+ return output_layer(d1_out, num_classes)
diff --git a/recode/problems/TensorPoly/pytorch/unet-output-layer.py b/recode/problems/TensorPoly/pytorch/unet-output-layer.py
new file mode 100644
index 0000000..85d10ae
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-output-layer.py
@@ -0,0 +1,6 @@
+import torch
+
+
+def unet_output(features: torch.Tensor, num_classes: int) -> torch.Tensor:
+ batch, H, W, _ = features.shape
+ return torch.zeros((batch, H, W, num_classes))
diff --git a/recode/problems/TensorPoly/pytorch/unet-skip-connection.py b/recode/problems/TensorPoly/pytorch/unet-skip-connection.py
new file mode 100644
index 0000000..b97bbd7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/unet-skip-connection.py
@@ -0,0 +1,12 @@
+import torch
+
+
+def crop_and_concat(encoder_features: torch.Tensor, decoder_features: torch.Tensor) -> torch.Tensor:
+ _, H_enc, W_enc, _ = encoder_features.shape
+ _, H_dec, W_dec, _ = decoder_features.shape
+
+ crop_h = (H_enc - H_dec) // 2
+ crop_w = (W_enc - W_dec) // 2
+
+ encoder_cropped = encoder_features[:, crop_h:crop_h + H_dec, crop_w:crop_w + W_dec, :]
+ return torch.cat([encoder_cropped, decoder_features], dim=-1)
diff --git a/recode/problems/TensorPoly/pytorch/vae-decoder.py b/recode/problems/TensorPoly/pytorch/vae-decoder.py
new file mode 100644
index 0000000..2ac4d1b
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-decoder.py
@@ -0,0 +1,16 @@
+import torch
+
+
+def vae_decoder(z: torch.Tensor, output_dim: int) -> torch.Tensor:
+ _, latent_dim = z.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(latent_dim, hidden_dim) * 0.01
+ b_h = torch.zeros(hidden_dim)
+ h = torch.maximum(torch.tensor(0.0), torch.matmul(z, w_h) + b_h)
+
+ w_out = torch.randn(hidden_dim, output_dim) * 0.01
+ b_out = torch.zeros(output_dim)
+ logits = torch.matmul(h, w_out) + b_out
+
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch/vae-elbo-loss.py b/recode/problems/TensorPoly/pytorch/vae-elbo-loss.py
new file mode 100644
index 0000000..770029c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-elbo-loss.py
@@ -0,0 +1,17 @@
+import torch
+
+
+def vae_loss(x: torch.Tensor, x_recon: torch.Tensor, mu: torch.Tensor, log_var: torch.Tensor) -> dict:
+ recon_loss_per_sample = torch.sum((x - x_recon) ** 2, dim=1)
+ recon_loss = torch.mean(recon_loss_per_sample)
+
+ var = torch.exp(log_var)
+ kl_per_sample = -0.5 * torch.sum(1 + log_var - mu ** 2 - var, dim=1)
+ kl_loss = torch.mean(kl_per_sample)
+
+ total_loss = recon_loss + kl_loss
+ return {
+ "total": float(total_loss.item()),
+ "recon": float(recon_loss.item()),
+ "kl": float(kl_loss.item()),
+ }
diff --git a/recode/problems/TensorPoly/pytorch/vae-encoder.py b/recode/problems/TensorPoly/pytorch/vae-encoder.py
new file mode 100644
index 0000000..aaaf545
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-encoder.py
@@ -0,0 +1,20 @@
+import torch
+
+
+def vae_encoder(x: torch.Tensor, latent_dim: int) -> tuple:
+ _, input_dim = x.shape
+ hidden_dim = 256
+
+ w_h = torch.randn(input_dim, hidden_dim) * 0.01
+ b_h = torch.zeros(hidden_dim)
+ h = torch.maximum(torch.tensor(0.0), torch.matmul(x, w_h) + b_h)
+
+ w_mu = torch.randn(hidden_dim, latent_dim) * 0.01
+ b_mu = torch.zeros(latent_dim)
+ mu = torch.matmul(h, w_mu) + b_mu
+
+ w_log_var = torch.randn(hidden_dim, latent_dim) * 0.01
+ b_log_var = torch.zeros(latent_dim)
+ log_var = torch.matmul(h, w_log_var) + b_log_var
+
+ return mu, log_var
diff --git a/recode/problems/TensorPoly/pytorch/vae-full-network.py b/recode/problems/TensorPoly/pytorch/vae-full-network.py
new file mode 100644
index 0000000..9bbb873
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-full-network.py
@@ -0,0 +1,42 @@
+import torch
+
+
+class VAE:
+ def __init__(self, input_dim: int, latent_dim: int):
+ self.input_dim = input_dim
+ self.latent_dim = latent_dim
+ self.hidden_dim = 256
+
+ self.w_enc = torch.randn(input_dim, self.hidden_dim) * 0.01
+ self.b_enc = torch.zeros(self.hidden_dim)
+
+ self.w_mu = torch.randn(self.hidden_dim, latent_dim) * 0.01
+ self.b_mu = torch.zeros(latent_dim)
+ self.w_log_var = torch.randn(self.hidden_dim, latent_dim) * 0.01
+ self.b_log_var = torch.zeros(latent_dim)
+
+ self.w_dec_h = torch.randn(latent_dim, self.hidden_dim) * 0.01
+ self.b_dec_h = torch.zeros(self.hidden_dim)
+ self.w_dec_out = torch.randn(self.hidden_dim, input_dim) * 0.01
+ self.b_dec_out = torch.zeros(input_dim)
+
+ def forward(self, x: torch.Tensor) -> tuple:
+ h_enc = torch.maximum(torch.tensor(0.0), torch.matmul(x, self.w_enc) + self.b_enc)
+ mu = torch.matmul(h_enc, self.w_mu) + self.b_mu
+ log_var = torch.matmul(h_enc, self.w_log_var) + self.b_log_var
+
+ std = torch.exp(0.5 * log_var)
+ eps = torch.randn_like(mu)
+ z = mu + std * eps
+
+ h_dec = torch.maximum(torch.tensor(0.0), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ x_recon = 1 / (1 + torch.exp(-logits))
+
+ return x_recon, mu, log_var
+
+ def generate(self, n_samples: int) -> torch.Tensor:
+ z = torch.randn(n_samples, self.latent_dim)
+ h_dec = torch.maximum(torch.tensor(0.0), torch.matmul(z, self.w_dec_h) + self.b_dec_h)
+ logits = torch.matmul(h_dec, self.w_dec_out) + self.b_dec_out
+ return 1 / (1 + torch.exp(-logits))
diff --git a/recode/problems/TensorPoly/pytorch/vae-kl-divergence.py b/recode/problems/TensorPoly/pytorch/vae-kl-divergence.py
new file mode 100644
index 0000000..a7a3652
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-kl-divergence.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def kl_divergence(mu: torch.Tensor, log_var: torch.Tensor) -> float:
+ var = torch.exp(log_var)
+ kl_element = 1 + log_var - mu ** 2 - var
+ batch_kl = -0.5 * torch.sum(kl_element, dim=1)
+ return float(torch.mean(batch_kl).item())
diff --git a/recode/problems/TensorPoly/pytorch/vae-reparameterization.py b/recode/problems/TensorPoly/pytorch/vae-reparameterization.py
new file mode 100644
index 0000000..f88625c
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vae-reparameterization.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def reparameterize(mu: torch.Tensor, log_var: torch.Tensor) -> torch.Tensor:
+ std = torch.exp(0.5 * log_var)
+ epsilon = torch.randn_like(mu)
+ return mu + std * epsilon
diff --git a/recode/problems/TensorPoly/pytorch/vgg-classifier.py b/recode/problems/TensorPoly/pytorch/vgg-classifier.py
new file mode 100644
index 0000000..67775f2
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-classifier.py
@@ -0,0 +1,21 @@
+import torch
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000) -> torch.Tensor:
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim))
+ w = torch.randn(in_dim, out_dim) * limit
+ b = torch.zeros(out_dim)
+ return torch.maximum(torch.tensor(0.0), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes) * torch.sqrt(torch.tensor(2.0 / in_dim_final))
+ b_final = torch.zeros(num_classes)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch/vgg-config.py b/recode/problems/TensorPoly/pytorch/vgg-config.py
new file mode 100644
index 0000000..85529b9
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-config.py
@@ -0,0 +1,9 @@
+def make_vgg_config(variant: str) -> list:
+ configs = {
+ "vgg11": [64, "M", 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg13": [64, 64, "M", 128, 128, "M", 256, 256, "M", 512, 512, "M", 512, 512, "M"],
+ "vgg16": [64, 64, "M", 128, 128, "M", 256, 256, 256, "M", 512, 512, 512, "M", 512, 512, 512, "M"],
+ "vgg19": [64, 64, "M", 128, 128, "M", 256, 256, 256, 256, "M", 512, 512, 512, 512, "M", 512, 512, 512, 512, "M"],
+ }
+ key = variant.lower()
+ return configs.get(key, [])
diff --git a/recode/problems/TensorPoly/pytorch/vgg-conv-block.py b/recode/problems/TensorPoly/pytorch/vgg-conv-block.py
new file mode 100644
index 0000000..1386f2e
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-conv-block.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def vgg_conv_block(x: torch.Tensor, num_convs: int, out_channels: int) -> torch.Tensor:
+ current_x = x
+ for _ in range(num_convs):
+ _, _, _, c = current_x.shape
+ limit = torch.sqrt(torch.tensor(2.0 / (3 * 3 * c)))
+ weights = torch.randn(3, 3, c, out_channels) * limit
+ bias = torch.zeros(out_channels)
+
+ batch, h, w, _ = current_x.shape
+ padded_x = torch.zeros((batch, h + 2, w + 2, c))
+ padded_x[:, 1:h + 1, 1:w + 1, :] = current_x
+
+ out = torch.zeros((batch, h, w, out_channels))
+ for i in range(3):
+ for j in range(3):
+ window = padded_x[:, i:i + h, j:j + w, :]
+ out = out + torch.tensordot(window, weights[i, j], dims=([3], [0]))
+
+ out = out + bias
+ current_x = torch.maximum(torch.tensor(0.0), out)
+
+ return current_x
diff --git a/recode/problems/TensorPoly/pytorch/vgg-feature-extractor.py b/recode/problems/TensorPoly/pytorch/vgg-feature-extractor.py
new file mode 100644
index 0000000..20abc21
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-feature-extractor.py
@@ -0,0 +1,23 @@
+import torch
+
+
+def conv_relu(x: torch.Tensor, out_channels: int) -> torch.Tensor:
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+def maxpool_2x2(x: torch.Tensor) -> torch.Tensor:
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list) -> torch.Tensor:
+ out = x
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer)
+ elif layer == "M":
+ out = maxpool_2x2(out)
+ return out
diff --git a/recode/problems/TensorPoly/pytorch/vgg-full-network.py b/recode/problems/TensorPoly/pytorch/vgg-full-network.py
new file mode 100644
index 0000000..cf997d7
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-full-network.py
@@ -0,0 +1,56 @@
+import torch
+
+
+def vgg16(x: torch.Tensor, num_classes: int = 1000) -> torch.Tensor:
+ vgg16_config = [
+ 64, 64, "M",
+ 128, 128, "M",
+ 256, 256, 256, "M",
+ 512, 512, 512, "M",
+ 512, 512, 512, "M",
+ ]
+
+ features = vgg_features(x, vgg16_config)
+ return vgg_classifier(features, num_classes)
+
+
+def conv_relu(x: torch.Tensor, out_channels: int) -> torch.Tensor:
+ _, _, _, c = x.shape
+ weights = torch.randn(c, out_channels) * 0.1
+ x = x @ weights
+ return torch.maximum(torch.tensor(0.0), x)
+
+
+def maxpool_2x2(x: torch.Tensor) -> torch.Tensor:
+ b, h, w, c = x.shape
+ return x.reshape(b, h // 2, 2, w // 2, 2, c).max(dim=2).values.max(dim=3).values
+
+
+def vgg_features(x: torch.Tensor, config: list) -> torch.Tensor:
+ out = x
+ for layer in config:
+ if isinstance(layer, int):
+ out = conv_relu(out, layer)
+ elif layer == "M":
+ out = maxpool_2x2(out)
+ return out
+
+
+def vgg_classifier(features: torch.Tensor, num_classes: int = 1000) -> torch.Tensor:
+ batch_size = features.shape[0]
+ x = features.reshape(batch_size, -1)
+
+ def dense_relu(input_data: torch.Tensor, out_dim: int) -> torch.Tensor:
+ in_dim = input_data.shape[1]
+ limit = torch.sqrt(torch.tensor(2.0 / in_dim))
+ w = torch.randn(in_dim, out_dim) * limit
+ b = torch.zeros(out_dim)
+ return torch.maximum(torch.tensor(0.0), input_data @ w + b)
+
+ x = dense_relu(x, 4096)
+ x = dense_relu(x, 4096)
+
+ in_dim_final = x.shape[1]
+ w_final = torch.randn(in_dim_final, num_classes) * torch.sqrt(torch.tensor(2.0 / in_dim_final))
+ b_final = torch.zeros(num_classes)
+ return x @ w_final + b_final
diff --git a/recode/problems/TensorPoly/pytorch/vgg-maxpool.py b/recode/problems/TensorPoly/pytorch/vgg-maxpool.py
new file mode 100644
index 0000000..e361926
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vgg-maxpool.py
@@ -0,0 +1,7 @@
+import torch
+
+
+def vgg_maxpool(x: torch.Tensor) -> torch.Tensor:
+ batch, h, w, c = x.shape
+ reshaped_x = x.reshape(batch, h // 2, 2, w // 2, 2, c)
+ return reshaped_x.max(dim=2).values.max(dim=3).values
diff --git a/recode/problems/TensorPoly/pytorch/vit-class-token.py b/recode/problems/TensorPoly/pytorch/vit-class-token.py
new file mode 100644
index 0000000..2817c56
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-class-token.py
@@ -0,0 +1,8 @@
+import torch
+
+
+def prepend_class_token(patches: torch.Tensor, embed_dim: int) -> torch.Tensor:
+ batch_size = patches.size(0)
+ cls_token = torch.randn(1, 1, embed_dim) * 0.02
+ cls_token_batch = cls_token.repeat(batch_size, 1, 1)
+ return torch.cat([cls_token_batch, patches], dim=1)
diff --git a/recode/problems/TensorPoly/pytorch/vit-encoder-block.py b/recode/problems/TensorPoly/pytorch/vit-encoder-block.py
new file mode 100644
index 0000000..f0a1a9f
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-encoder-block.py
@@ -0,0 +1,62 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def gelu(x: torch.Tensor) -> torch.Tensor:
+ return 0.5 * x * (1 + torch.tanh(torch.sqrt(torch.tensor(2.0 / torch.pi)) * (x + 0.044715 * x ** 3)))
+
+
+def softmax(x: torch.Tensor, axis: int = -1) -> torch.Tensor:
+ return torch.softmax(x, dim=axis)
+
+
+def multi_head_self_attention(x: torch.Tensor, num_heads: int, embed_dim: int) -> torch.Tensor:
+ batch, seq_len, _ = x.shape
+ head_dim = embed_dim // num_heads
+
+ W_q = torch.randn(embed_dim, embed_dim) * 0.02
+ W_k = torch.randn(embed_dim, embed_dim) * 0.02
+ W_v = torch.randn(embed_dim, embed_dim) * 0.02
+ W_o = torch.randn(embed_dim, embed_dim) * 0.02
+
+ Q = torch.matmul(x, W_q)
+ K = torch.matmul(x, W_k)
+ V = torch.matmul(x, W_v)
+
+ Q = Q.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ K = K.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+ V = V.reshape(batch, seq_len, num_heads, head_dim).transpose(1, 2)
+
+ scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(head_dim, dtype=x.dtype))
+ attn_weights = softmax(scores, axis=-1)
+ attn_output = torch.matmul(attn_weights, V)
+
+ attn_output = attn_output.transpose(1, 2).reshape(batch, seq_len, embed_dim)
+ return torch.matmul(attn_output, W_o)
+
+
+def mlp(x: torch.Tensor, embed_dim: int, mlp_ratio: float) -> torch.Tensor:
+ hidden_dim = int(embed_dim * mlp_ratio)
+ W1 = torch.randn(embed_dim, hidden_dim) * 0.02
+ b1 = torch.zeros(hidden_dim)
+ W2 = torch.randn(hidden_dim, embed_dim) * 0.02
+ b2 = torch.zeros(embed_dim)
+
+ h = gelu(torch.matmul(x, W1) + b1)
+ return torch.matmul(h, W2) + b2
+
+
+def vit_encoder_block(x: torch.Tensor, embed_dim: int, num_heads: int, mlp_ratio: float = 4.0) -> torch.Tensor:
+ x_norm1 = layer_norm(x)
+ attn_output = multi_head_self_attention(x_norm1, num_heads, embed_dim)
+ x = x + attn_output
+
+ x_norm2 = layer_norm(x)
+ mlp_output = mlp(x_norm2, embed_dim, mlp_ratio)
+ x = x + mlp_output
+ return x
diff --git a/recode/problems/TensorPoly/pytorch/vit-full-network.py b/recode/problems/TensorPoly/pytorch/vit-full-network.py
new file mode 100644
index 0000000..40a8802
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-full-network.py
@@ -0,0 +1,32 @@
+import torch
+
+
+class VisionTransformer:
+ def __init__(self, image_size: int = 224, patch_size: int = 16,
+ num_classes: int = 1000, embed_dim: int = 768,
+ depth: int = 12, num_heads: int = 12, mlp_ratio: float = 4.0):
+ self.image_size = image_size
+ self.patch_size = patch_size
+ self.num_patches = (image_size // patch_size) ** 2
+ self.embed_dim = embed_dim
+ self.depth = depth
+ self.num_heads = num_heads
+ self.mlp_ratio = mlp_ratio
+ self.num_classes = num_classes
+
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
+ batch_size = x.shape[0]
+
+ x = torch.zeros((batch_size, self.num_patches, self.embed_dim))
+ x = torch.cat([
+ torch.zeros((batch_size, 1, self.embed_dim)),
+ x
+ ], dim=1)
+
+ x = x + torch.zeros((1, self.num_patches + 1, self.embed_dim))
+
+ for _ in range(self.depth):
+ x = x + torch.zeros_like(x)
+
+ logits = torch.zeros((batch_size, self.num_classes))
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch/vit-mlp-head.py b/recode/problems/TensorPoly/pytorch/vit-mlp-head.py
new file mode 100644
index 0000000..2350a87
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-mlp-head.py
@@ -0,0 +1,19 @@
+import torch
+
+
+def layer_norm(x: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
+ mean = torch.mean(x, dim=-1, keepdim=True)
+ var = torch.var(x, dim=-1, keepdim=True, unbiased=False)
+ return (x - mean) / torch.sqrt(var + eps)
+
+
+def classification_head(encoder_output: torch.Tensor, num_classes: int) -> torch.Tensor:
+ cls_token = encoder_output[:, 0, :]
+ cls_norm = layer_norm(cls_token)
+
+ embed_dim = cls_token.shape[-1]
+ W = torch.randn(embed_dim, num_classes) * 0.01
+ b = torch.zeros(num_classes)
+
+ logits = torch.matmul(cls_norm, W) + b
+ return logits
diff --git a/recode/problems/TensorPoly/pytorch/vit-patch-embedding.py b/recode/problems/TensorPoly/pytorch/vit-patch-embedding.py
new file mode 100644
index 0000000..e69393a
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-patch-embedding.py
@@ -0,0 +1,25 @@
+import torch
+
+
+def patch_embed(image: torch.Tensor, patch_size: int, embed_dim: int) -> torch.Tensor:
+ batch, H, W, C = image.shape
+
+ num_patches_h = H // patch_size
+ num_patches_w = W // patch_size
+ num_patches = num_patches_h * num_patches_w
+
+ patches = image.reshape(
+ batch,
+ num_patches_h, patch_size,
+ num_patches_w, patch_size,
+ C
+ )
+
+ patches = patches.permute(0, 1, 3, 2, 4, 5)
+ patches_flat = patches.reshape(batch, num_patches_h, num_patches_w, patch_size * patch_size * C)
+ patches_seq = patches_flat.reshape(batch, num_patches, patch_size * patch_size * C)
+
+ patch_dim = patch_size * patch_size * C
+ W_proj = torch.randn(patch_dim, embed_dim) * 0.01
+ embeddings = torch.matmul(patches_seq, W_proj)
+ return embeddings
diff --git a/recode/problems/TensorPoly/pytorch/vit-position-embedding.py b/recode/problems/TensorPoly/pytorch/vit-position-embedding.py
new file mode 100644
index 0000000..cad4109
--- /dev/null
+++ b/recode/problems/TensorPoly/pytorch/vit-position-embedding.py
@@ -0,0 +1,6 @@
+import torch
+
+
+def add_position_embedding(patches: torch.Tensor, num_patches: int, embed_dim: int) -> torch.Tensor:
+ position_embeddings = torch.randn(1, num_patches, embed_dim) * 0.01
+ return patches + position_embeddings
diff --git a/recode/problems/__init__.py b/recode/problems/__init__.py
new file mode 100644
index 0000000..cd49682
--- /dev/null
+++ b/recode/problems/__init__.py
@@ -0,0 +1 @@
+"""Bundled problem sets shipped with the recode package."""
diff --git a/recode/problems/a-b-test.py b/recode/problems/a-b-test.py
new file mode 100644
index 0000000..e67453c
--- /dev/null
+++ b/recode/problems/a-b-test.py
@@ -0,0 +1,86 @@
+SOLUTION = """
+import numpy as np
+import pandas as pd
+import scipy.stats as stats
+import statsmodels.stats.api as sms
+import matplotlib.pyplot as plt
+import seaborn as sns
+
+sns.set_theme(style="whitegrid")
+
+# 1. Power Analysis: Determine Required Sample Size
+# Before starting an experiment, we must know how many users we need.
+# alpha: Significance level (Type I error)
+# power: Probability of detecting an effect if it exists (1 - Type II error)
+# effect_size: The minimum detectable effect (difference in means / std_dev)
+
+print("Calculating required sample size...")
+alpha = 0.05
+power = 0.8
+effect_size = sms.proportion_effectsize(0.10, 0.12) # Aiming to detect a 2% lift from 10%
+
+required_n = sms.NormalIndPower().solve_power(
+ effect_size,
+ power=power,
+ alpha=alpha,
+ ratio=1
+)
+
+print(f"Required sample size per group: {int(np.ceil(required_n))}\\n")
+
+# 2. Synthetic Data Generation
+# We simulate Model A (Control) and Model B (Treatment) performance
+np.random.seed(42)
+n_samples = int(np.ceil(required_n))
+
+# Model A: Mean 100, Std Dev 20
+data_a = np.random.normal(loc=100, scale=20, size=n_samples)
+# Model B: Mean 103, Std Dev 22 (A slight 3% improvement)
+data_b = np.random.normal(loc=103, scale=22, size=n_samples)
+
+# 3. Statistical Testing: T-Test for Continuous Metrics (e.g., Revenue)
+# We use Welch's T-Test (equal_var=False) because we don't assume equal variance
+t_stat, p_val = stats.ttest_ind(data_a, data_b, equal_var=False)
+
+# 4. Calculating Confidence Intervals
+def get_ci(data, confidence=0.95):
+ mean = np.mean(data)
+ sem = stats.sem(data) # Standard error of the mean
+ margin = sem * stats.t.ppf((1 + confidence) / 2., len(data)-1)
+ return mean - margin, mean + margin
+
+ci_a = get_ci(data_a)
+ci_b = get_ci(data_b)
+
+# 5. Chi-Square Test for Categorical Metrics (e.g., Conversion Rate)
+# Simulating binary 'Converted' (1) or 'Not Converted' (0)
+conv_a = np.random.binomial(1, 0.10, n_samples)
+conv_b = np.random.binomial(1, 0.12, n_samples)
+
+contingency_table = [
+ [np.sum(conv_a), n_samples - np.sum(conv_a)],
+ [np.sum(conv_b), n_samples - np.sum(conv_b)]
+]
+chi2, p_val_chi2, _, _ = stats.chi2_contingency(contingency_table)
+
+# 6. Results Visualization
+print("="*30)
+print("A/B TEST RESULTS")
+print("="*30)
+print(f"T-Test P-Value: {p_val:.4f}")
+print(f"Model A 95% CI: [{ci_a[0]:.2f}, {ci_a[1]:.2f}]")
+print(f"Model B 95% CI: [{ci_b[0]:.2f}, {ci_b[1]:.2f}]")
+print(f"Chi-Square P-Value: {p_val_chi2:.4f}")
+print("="*30)
+
+plt.figure(figsize=(10, 6))
+sns.kdeplot(data_a, fill=True, label="Model A (Control)", color="blue")
+sns.kdeplot(data_b, fill=True, label="Model B (Treatment)", color="green")
+plt.title("Distribution of Performance Metrics")
+plt.axvline(np.mean(data_a), color="blue", linestyle="--")
+plt.axvline(np.mean(data_b), color="green", linestyle="--")
+plt.legend()
+plt.show()
+""".strip()
+
+DESCRIPTION = "Implement A/B testing with power analysis, Welch's T-test, confidence intervals, and chi-square test for conversion rates."
diff --git a/recode/problems/automl-sklearn.py b/recode/problems/automl-sklearn.py
new file mode 100644
index 0000000..21e6380
--- /dev/null
+++ b/recode/problems/automl-sklearn.py
@@ -0,0 +1,83 @@
+SOLUTION = """
+import pandas as pd
+import numpy as np
+from sklearn.datasets import fetch_california_housing
+from sklearn.model_selection import train_test_split, GridSearchCV
+from sklearn.compose import ColumnTransformer
+from sklearn.pipeline import Pipeline
+from sklearn.impute import SimpleImputer
+from sklearn.preprocessing import StandardScaler, OneHotEncoder
+from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
+
+# 1. Data Preparation
+print("Loading dataset...")
+housing = fetch_california_housing()
+X = pd.DataFrame(housing.data, columns=housing.feature_names)
+y = housing.target
+
+# Split into training and test sets
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+# Identify feature types
+numeric_features = X.columns.tolist()
+# Note: California housing is all numeric, but we define the logic for categorical too
+categorical_features = []
+
+# 2. Building Preprocessing Transformers
+# We automate the handling of missing values and feature scaling
+numeric_transformer = Pipeline(steps=[
+ ('imputer', SimpleImputer(strategy='median')),
+ ('scaler', StandardScaler())
+])
+
+categorical_transformer = Pipeline(steps=[
+ ('imputer', SimpleImputer(strategy='most_frequent')),
+ ('onehot', OneHotEncoder(handle_unknown='ignore'))
+])
+
+# Combine transformers into a ColumnTransformer
+preprocessor = ColumnTransformer(
+ transformers=[
+ ('num', numeric_transformer, numeric_features),
+ ('cat', categorical_transformer, categorical_features)
+ ])
+
+# 3. Define the AutoML Pipeline
+# We start with a placeholder regressor that GridSearchCV will swap out
+full_pipeline = Pipeline(steps=[
+ ('preprocessor', preprocessor),
+ ('regressor', RandomForestRegressor())
+])
+
+# 4. Automating Model Selection and Hyperparameter Tuning
+# The double underscore notation (regressor__parameter) targets the specific step
+param_grid = [
+ {
+ 'regressor': [RandomForestRegressor(random_state=42)],
+ 'regressor__n_estimators': [50, 100],
+ 'regressor__max_depth': [None, 10]
+ },
+ {
+ 'regressor': [GradientBoostingRegressor(random_state=42)],
+ 'regressor__n_estimators': [100, 200],
+ 'regressor__learning_rate': [0.05, 0.1]
+ }
+]
+
+# 5. Execute the Grid Search
+print("Starting AutoML Search...")
+grid_search = GridSearchCV(full_pipeline, param_grid, cv=5, scoring='r2', n_jobs=-1, verbose=1)
+grid_search.fit(X_train, y_train)
+
+# 6. Results and Evaluation
+print("\\n" + "="*30)
+print(f"Best Model Found: {grid_search.best_params_['regressor']}")
+print(f"Best CV R2 Score: {grid_search.best_score_:.4f}")
+print("="*30)
+
+# Final test set evaluation
+final_score = grid_search.score(X_test, y_test)
+print(f"Final Test Set R2 Accuracy: {final_score:.4f}")
+""".strip()
+
+DESCRIPTION = "Build an AutoML pipeline with sklearn that automates preprocessing, model selection, and hyperparameter tuning via GridSearchCV."
diff --git a/recode/problems/cats-vs-dogs-cnn.py b/recode/problems/cats-vs-dogs-cnn.py
new file mode 100644
index 0000000..e3f10b4
--- /dev/null
+++ b/recode/problems/cats-vs-dogs-cnn.py
@@ -0,0 +1,230 @@
+SOLUTION = """
+# CNN IMAGE CLASSIFICATION: CATS VS DOGS + RESNET18 TRANSFER LEARNING
+
+# !pip install datasets torch torchvision matplotlib numpy -q
+
+import torch
+import torch.nn as nn
+import torch.optim as optim
+import torchvision.models as models
+from torch.utils.data import DataLoader, Dataset
+from torchvision import transforms
+from datasets import load_dataset
+import matplotlib.pyplot as plt
+import numpy as np
+from collections import Counter
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+print(f"Training on device: {device}\\n")
+
+
+# 1. Data Loading via Hugging Face
+
+print("Loading microsoft/cats_vs_dogs dataset...")
+dataset = load_dataset("microsoft/cats_vs_dogs", split="train")
+dataset = dataset.train_test_split(test_size=0.2, seed=42)
+train_data = dataset['train']
+val_data = dataset['test']
+
+print(f"Training samples: {len(train_data)} | Validation samples: {len(val_data)}\\n")
+
+
+# 2. Data Augmentation and Pre-processing
+
+train_transforms = transforms.Compose([
+ transforms.RandomResizedCrop(128, scale=(0.8, 1.0)),
+ transforms.RandomHorizontalFlip(p=0.5),
+ transforms.ColorJitter(brightness=0.2, contrast=0.2),
+ transforms.ToTensor(),
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+])
+
+val_transforms = transforms.Compose([
+ transforms.Resize((128, 128)),
+ transforms.ToTensor(),
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
+])
+
+class HFVisionDataset(Dataset):
+ def __init__(self, hf_dataset, transform=None):
+ self.hf_dataset = hf_dataset
+ self.transform = transform
+
+ def __len__(self):
+ return len(self.hf_dataset)
+
+ def __getitem__(self, idx):
+ item = self.hf_dataset[idx]
+ image = item['image'].convert("RGB")
+ label = item['labels']
+ if self.transform:
+ image = self.transform(image)
+ return image, label
+
+train_dataset = HFVisionDataset(train_data, transform=train_transforms)
+val_dataset = HFVisionDataset(val_data, transform=val_transforms)
+
+
+# 3. Handling Class Imbalances
+
+print("Calculating class weights for imbalance handling...")
+train_labels = [item['labels'] for item in train_data]
+class_counts = Counter(train_labels)
+num_samples = len(train_labels)
+class_weights = {cls: num_samples / (len(class_counts) * count) for cls, count in class_counts.items()}
+print(f"Class Weights: {class_weights}")
+
+weights_tensor = torch.tensor([class_weights[0], class_weights[1]], dtype=torch.float32).to(device)
+
+
+# 4. DataLoaders
+
+batch_size = 64
+train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)
+val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=2)
+
+
+# 5. CNN Architecture
+
+class SimpleCNN(nn.Module):
+ def __init__(self):
+ super(SimpleCNN, self).__init__()
+ self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1)
+ self.relu1 = nn.ReLU()
+ self.pool1 = nn.MaxPool2d(2, 2)
+
+ self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
+ self.relu2 = nn.ReLU()
+ self.pool2 = nn.MaxPool2d(2, 2)
+
+ self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
+ self.relu3 = nn.ReLU()
+ self.pool3 = nn.MaxPool2d(2, 2)
+
+ self.flatten = nn.Flatten()
+ self.fc1 = nn.Linear(128 * 16 * 16, 512)
+ self.dropout = nn.Dropout(0.5)
+ self.fc2 = nn.Linear(512, 2)
+
+ def forward(self, x):
+ x = self.pool1(self.relu1(self.conv1(x)))
+ x = self.pool2(self.relu2(self.conv2(x)))
+ x = self.pool3(self.relu3(self.conv3(x)))
+ x = self.flatten(x)
+ x = self.dropout(torch.relu(self.fc1(x)))
+ x = self.fc2(x)
+ return x
+
+
+def run_training_loop(model, criterion, optimizer, train_loader, val_loader, epochs, title):
+ train_losses, val_losses, train_accs, val_accs = [], [], [], []
+
+ print(f"\\nStarting {title} Training...\\n")
+ for epoch in range(epochs):
+ model.train()
+ running_loss, correct, total = 0.0, 0, 0
+
+ for images, labels in train_loader:
+ images, labels = images.to(device), labels.to(device)
+ optimizer.zero_grad()
+ outputs = model(images)
+ loss = criterion(outputs, labels)
+ loss.backward()
+ optimizer.step()
+
+ running_loss += loss.item() * images.size(0)
+ _, predicted = torch.max(outputs, 1)
+ total += labels.size(0)
+ correct += (predicted == labels).sum().item()
+
+ epoch_train_loss = running_loss / total
+ epoch_train_acc = correct / total
+ train_losses.append(epoch_train_loss)
+ train_accs.append(epoch_train_acc)
+
+ model.eval()
+ val_loss, correct, total = 0.0, 0, 0
+ with torch.no_grad():
+ for images, labels in val_loader:
+ images, labels = images.to(device), labels.to(device)
+ outputs = model(images)
+ loss = criterion(outputs, labels)
+ val_loss += loss.item() * images.size(0)
+ _, predicted = torch.max(outputs, 1)
+ total += labels.size(0)
+ correct += (predicted == labels).sum().item()
+
+ epoch_val_loss = val_loss / total
+ epoch_val_acc = correct / total
+ val_losses.append(epoch_val_loss)
+ val_accs.append(epoch_val_acc)
+
+ print(f"Epoch {epoch+1}/{epochs} | Train Loss: {epoch_train_loss:.4f} Acc: {epoch_train_acc:.4f} | Val Loss: {epoch_val_loss:.4f} Acc: {epoch_val_acc:.4f}")
+
+ return train_losses, val_losses, train_accs, val_accs
+
+
+# 6. Train Custom CNN
+
+cnn_model = SimpleCNN().to(device)
+criterion = nn.CrossEntropyLoss(weight=weights_tensor)
+optimizer = optim.Adam(cnn_model.parameters(), lr=0.001)
+
+epochs = 5
+cnn_train_losses, cnn_val_losses, cnn_train_accs, cnn_val_accs = run_training_loop(
+ cnn_model, criterion, optimizer, train_loader, val_loader, epochs, "Custom CNN"
+)
+
+
+# 7. Transfer Learning Architecture (ResNet18)
+
+print("\\nLoading pre-trained ResNet18 model...")
+weights = models.ResNet18_Weights.DEFAULT
+resnet_model = models.resnet18(weights=weights)
+
+for param in resnet_model.parameters():
+ param.requires_grad = False
+
+num_ftrs = resnet_model.fc.in_features
+resnet_model.fc = nn.Linear(num_ftrs, 2)
+resnet_model = resnet_model.to(device)
+
+criterion = nn.CrossEntropyLoss(weight=weights_tensor)
+optimizer = optim.Adam(resnet_model.fc.parameters(), lr=0.001)
+
+print("Architecture updated to ResNet18. Ready for training.")
+
+resnet_train_losses, resnet_val_losses, resnet_train_accs, resnet_val_accs = run_training_loop(
+ resnet_model, criterion, optimizer, train_loader, val_loader, epochs, "ResNet18 Transfer Learning"
+)
+
+
+# 8. Plot Results
+
+fig, axes = plt.subplots(2, 2, figsize=(14, 10))
+
+axes[0, 0].plot(range(1, epochs+1), cnn_train_losses, label='Train', color='#e74c3c', linewidth=2)
+axes[0, 0].plot(range(1, epochs+1), cnn_val_losses, label='Validation', color='#2ecc71', linewidth=2)
+axes[0, 0].set_title('Custom CNN: Loss')
+axes[0, 0].legend()
+
+axes[0, 1].plot(range(1, epochs+1), cnn_train_accs, label='Train', color='#e74c3c', linewidth=2)
+axes[0, 1].plot(range(1, epochs+1), cnn_val_accs, label='Validation', color='#2ecc71', linewidth=2)
+axes[0, 1].set_title('Custom CNN: Accuracy')
+axes[0, 1].legend()
+
+axes[1, 0].plot(range(1, epochs+1), resnet_train_losses, label='Train', color='#2c3e50', linewidth=2)
+axes[1, 0].plot(range(1, epochs+1), resnet_val_losses, label='Validation', color='#e74c3c', linewidth=2, linestyle='--')
+axes[1, 0].set_title('ResNet18: Loss')
+axes[1, 0].legend()
+
+axes[1, 1].plot(range(1, epochs+1), resnet_train_accs, label='Train', color='#2c3e50', linewidth=2)
+axes[1, 1].plot(range(1, epochs+1), resnet_val_accs, label='Validation', color='#e74c3c', linewidth=2, linestyle='--')
+axes[1, 1].set_title('ResNet18: Accuracy')
+axes[1, 1].legend()
+
+plt.tight_layout()
+plt.show()
+""".strip()
+
+DESCRIPTION = "Train a custom CNN and fine-tune ResNet18 via transfer learning for binary cats vs dogs image classification."
diff --git a/recode/problems/churn-eda.py b/recode/problems/churn-eda.py
new file mode 100644
index 0000000..27ed362
--- /dev/null
+++ b/recode/problems/churn-eda.py
@@ -0,0 +1,74 @@
+SOLUTION = """
+import pandas as pd
+import numpy as np
+import seaborn as sns
+import matplotlib.pyplot as plt
+import plotly.express as px
+from datasets import load_dataset
+
+sns.set_theme(style="whitegrid", palette="muted")
+
+print("Loading dataset from Hugging Face...")
+dataset = load_dataset("scikit-learn/churn-prediction", split="train")
+df = dataset.to_pandas()
+
+print(f"Dataset loaded successfully with {df.shape[0]} rows and {df.shape[1]} columns.\\n")
+
+print("Cleaning data and handling missing values...")
+
+df['TotalCharges'] = pd.to_numeric(df['TotalCharges'].replace(' ', np.nan))
+
+missing_initial = df.isnull().sum().sum()
+df.dropna(inplace=True)
+
+df['SeniorCitizen'] = df['SeniorCitizen'].map({0: 'No', 1: 'Yes'})
+
+print(f"Data cleaned. Addressed {missing_initial} missing values. Current shape: {df.shape}\\n")
+
+print("Generating Visualizations...\\n")
+
+# Visualization A: Churn Rate (Plotly)
+churn_counts = df['Churn'].value_counts().reset_index()
+churn_counts.columns = ['Churn', 'Count']
+
+fig1 = px.pie(
+ churn_counts,
+ names='Churn',
+ values='Count',
+ hole=0.4,
+ title='Current Customer Churn Rate',
+ color='Churn',
+ color_discrete_map={'Yes': '#ef553b', 'No': '#00cc96'}
+)
+fig1.update_traces(textposition='inside', textinfo='percent+label')
+fig1.show()
+
+
+# Visualization B: Revenue Impact (Seaborn)
+plt.figure(figsize=(10, 6))
+sns.boxplot(x='Churn', y='MonthlyCharges', data=df, palette={'Yes': '#ef553b', 'No': '#00cc96'})
+plt.title('Monthly Revenue per Customer by Churn Status', fontsize=14, pad=15)
+plt.xlabel('Did the Customer Churn?', fontsize=12)
+plt.ylabel('Monthly Charges ($)', fontsize=12)
+sns.despine()
+plt.show()
+
+
+# Visualization C: Tenure Distribution (Plotly)
+fig2 = px.histogram(
+ df,
+ x="tenure",
+ color="Churn",
+ barmode="group",
+ title='Customer Retention Journey',
+ labels={'tenure': 'Months with Company', 'count': 'Number of Customers'},
+ color_discrete_map={'Yes': '#ef553b', 'No': '#00cc96'},
+ opacity=0.85
+)
+fig2.update_layout(bargap=0.1)
+fig2.show()
+
+print("EDA complete.")
+""".strip()
+
+DESCRIPTION = "Perform exploratory data analysis on a customer churn dataset with Plotly and Seaborn visualizations."
diff --git a/recode/problems/churn-prediction-lgbm.py b/recode/problems/churn-prediction-lgbm.py
new file mode 100644
index 0000000..80f0c31
--- /dev/null
+++ b/recode/problems/churn-prediction-lgbm.py
@@ -0,0 +1,113 @@
+SOLUTION = """
+# CHURN PREDICTION: CLASS IMBALANCE (SMOTE) & LIGHTGBM
+
+# !pip install datasets imbalanced-learn lightgbm scikit-learn pandas matplotlib seaborn -q
+
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import seaborn as sns
+from datasets import load_dataset
+from sklearn.model_selection import train_test_split
+from sklearn.preprocessing import StandardScaler
+from sklearn.metrics import classification_report, precision_recall_curve, auc, confusion_matrix
+from imblearn.over_sampling import SMOTE
+import lightgbm as lgb
+
+sns.set_theme(style="whitegrid")
+
+
+# 1. Data Ingestion & Cleaning
+
+print("Loading and cleaning dataset...")
+dataset = load_dataset("scikit-learn/churn-prediction", split="train")
+df = dataset.to_pandas()
+
+df['TotalCharges'] = pd.to_numeric(df['TotalCharges'].replace(' ', np.nan))
+df.dropna(inplace=True)
+df.drop('customerID', axis=1, inplace=True)
+
+print(f"Data ready. Shape: {df.shape}")
+
+
+# 2. Feature Engineering & Encoding
+
+print("Encoding categorical variables...")
+X = df.drop('Churn', axis=1)
+y = df['Churn'].map({'No': 0, 'Yes': 1})
+
+X = pd.get_dummies(X, drop_first=True)
+
+
+# 3. Train/Test Split & SMOTE
+
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
+
+print(f"\\nBefore SMOTE - Training Churn Counts: \\n{y_train.value_counts()}")
+
+smote = SMOTE(random_state=42)
+X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)
+
+print(f"After SMOTE - Training Churn Counts: \\n{y_train_smote.value_counts()}\\n")
+
+scaler = StandardScaler()
+X_train_smote = scaler.fit_transform(X_train_smote)
+X_test = scaler.transform(X_test)
+
+
+# 4. Model Training (LightGBM)
+
+print("Training LightGBM Classifier...")
+lgb_model = lgb.LGBMClassifier(
+ n_estimators=200,
+ learning_rate=0.05,
+ max_depth=5,
+ random_state=42,
+ n_jobs=-1
+)
+
+lgb_model.fit(X_train_smote, y_train_smote)
+
+
+# 5. Model Evaluation
+
+print("\\nGenerating predictions and evaluating...")
+y_pred = lgb_model.predict(X_test)
+y_prob = lgb_model.predict_proba(X_test)[:, 1]
+
+print("=" * 50)
+print("CLASSIFICATION REPORT")
+print("=" * 50)
+print(classification_report(y_test, y_pred, target_names=['Stayed (0)', 'Churned (1)']))
+
+
+# 6. Precision-Recall Curve Visualization
+
+precision, recall, thresholds = precision_recall_curve(y_test, y_prob)
+pr_auc = auc(recall, precision)
+
+plt.figure(figsize=(12, 5))
+
+plt.subplot(1, 2, 1)
+plt.plot(recall, precision, color='purple', lw=2, label=f'PR Curve (AUC = {pr_auc:.2f})')
+plt.xlabel('Recall')
+plt.ylabel('Precision')
+plt.title('Precision-Recall Curve')
+plt.legend(loc="lower left")
+
+plt.subplot(1, 2, 2)
+cm = confusion_matrix(y_test, y_pred)
+sns.heatmap(cm, annot=True, fmt='d', cmap='Purples', cbar=False,
+ xticklabels=['Predicted Stay', 'Predicted Churn'],
+ yticklabels=['Actual Stay', 'Actual Churn'])
+plt.title('Confusion Matrix')
+plt.ylabel('True Label')
+plt.xlabel('Predicted Label')
+
+plt.tight_layout()
+plt.show()
+
+print("\\nAnalysis Complete.")
+""".strip()
+
+DESCRIPTION = "Predict customer churn using SMOTE for class imbalance handling and LightGBM, evaluated with a precision-recall curve."
diff --git a/recode/problems/face-recognition-yunet.py b/recode/problems/face-recognition-yunet.py
new file mode 100644
index 0000000..7ae1deb
--- /dev/null
+++ b/recode/problems/face-recognition-yunet.py
@@ -0,0 +1,153 @@
+SOLUTION = """
+# REAL-TIME FACE DETECTION WITH YUNET AND SFACE IDENTITY VERIFICATION
+
+import cv2
+import numpy as np
+import os
+import urllib.request
+import matplotlib.pyplot as plt
+
+# =============================================================================
+# 1. Download Pre-trained YuNet Model (ONNX format)
+# =============================================================================
+model_url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx"
+model_path = "face_detection_yunet.onnx"
+
+if not os.path.exists(model_path):
+ print("Downloading YuNet ONNX model...")
+ urllib.request.urlretrieve(model_url, model_path)
+ print("Download complete.")
+
+yunet = cv2.FaceDetectorYN.create(
+ model=model_path,
+ config="",
+ input_size=(320, 320),
+ score_threshold=0.6,
+ nms_threshold=0.3,
+ top_k=5000,
+ backend_id=cv2.dnn.DNN_BACKEND_OPENCV,
+ target_id=cv2.dnn.DNN_TARGET_CPU
+)
+
+# =============================================================================
+# 2. Static Image Demo (load from disk or use a generated test image)
+# =============================================================================
+# To test with a real face image: replace this path with your own image file.
+# e.g. img = cv2.imread("my_photo.jpg")
+# For demonstration we use a synthetic placeholder image.
+
+# Attempt to load a test image; fall back to a blank placeholder if not found
+test_image_path = "test_face.jpg"
+if os.path.exists(test_image_path):
+ img = cv2.imread(test_image_path)
+ print(f"Loaded image: {test_image_path}")
+else:
+ print("No test image found. Using a 480x640 blank placeholder (no faces will be detected).")
+ img = np.zeros((480, 640, 3), dtype=np.uint8)
+
+h, w, _ = img.shape
+yunet.setInputSize((w, h))
+
+# Detect faces
+_, faces = yunet.detect(img)
+
+# Draw bounding boxes and landmarks on a copy
+annotated = img.copy()
+if faces is not None:
+ print(f"Detected {len(faces)} face(s).")
+ for face in faces:
+ box = list(map(int, face[:4]))
+ cv2.rectangle(annotated, (box[0], box[1]), (box[0]+box[2], box[1]+box[3]), (0, 255, 0), 2)
+ landmarks = list(map(int, face[4:14]))
+ for i in range(5):
+ cv2.circle(annotated, (landmarks[2*i], landmarks[2*i+1]), 3, (0, 0, 255), -1)
+else:
+ print("No faces detected in this image.")
+
+plt.figure(figsize=(8, 6))
+plt.imshow(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB))
+plt.axis('off')
+plt.title("YuNet Face Detection")
+plt.show()
+
+# =============================================================================
+# FACE ALIGNMENT AND EXTRACTION (AFFINE TRANSFORMATION)
+# =============================================================================
+
+standard_landmarks = np.array([
+ [38.2946, 51.6963],
+ [73.5318, 51.5014],
+ [56.0252, 71.7366],
+ [41.5493, 92.3655],
+ [70.7299, 92.2041]
+], dtype=np.float32)
+
+def align_face(image, face_data):
+ detected_landmarks = np.array(face_data[4:14]).reshape(5, 2).astype(np.float32)
+ M, _ = cv2.estimateAffinePartial2D(detected_landmarks, standard_landmarks)
+ aligned_face = cv2.warpAffine(image, M, (112, 112), borderValue=0.0)
+ return aligned_face
+
+if faces is not None and len(faces) > 0:
+ target_face = faces[0]
+ cropped_aligned_face = align_face(img, target_face)
+ display_img = cv2.cvtColor(cropped_aligned_face, cv2.COLOR_BGR2RGB)
+
+ print("Face aligned and extracted successfully. Shape:", cropped_aligned_face.shape)
+
+ plt.figure(figsize=(3, 3))
+ plt.imshow(display_img)
+ plt.axis('off')
+ plt.title("Aligned Face (112x112)")
+ plt.show()
+else:
+ print("No faces to align. Skipping alignment step.")
+ cropped_aligned_face = np.zeros((112, 112, 3), dtype=np.uint8)
+
+# =============================================================================
+# FACE EMBEDDING EXTRACTION & IDENTITY VERIFICATION
+# =============================================================================
+
+recognizer_url = "https://github.com/opencv/opencv_zoo/raw/main/models/face_recognition_sface/face_recognition_sface_2021dec.onnx"
+recognizer_path = "face_recognition_sface.onnx"
+
+if not os.path.exists(recognizer_path):
+ print("Downloading SFace ONNX Recognition model...")
+ urllib.request.urlretrieve(recognizer_url, recognizer_path)
+ print("Download complete.\\n")
+
+face_recognizer = cv2.FaceRecognizerSF.create(
+ model=recognizer_path,
+ config="",
+ backend_id=cv2.dnn.DNN_BACKEND_OPENCV,
+ target_id=cv2.dnn.DNN_TARGET_CPU
+)
+
+print("Extracting facial features...")
+user_embedding = face_recognizer.feature(cropped_aligned_face)
+
+print(f"Embedding generated! Shape: {user_embedding.shape}")
+print(f"First 5 values: {user_embedding[0][:5]}\\n")
+
+def calculate_cosine_similarity(feature1, feature2):
+ score = cv2.FaceRecognizerSF.match(
+ face_recognizer, feature1, feature2, cv2.FaceRecognizerSF_FR_COSINE
+ )
+ return score
+
+print("=" * 45)
+print("IDENTITY VERIFICATION TESTS")
+print("=" * 45)
+
+score_self = calculate_cosine_similarity(user_embedding, user_embedding)
+print(f"Test A (Self vs Self) : {score_self:.4f} (Perfect Match)")
+
+fake_face = np.random.randint(0, 255, (112, 112, 3), dtype=np.uint8)
+fake_embedding = face_recognizer.feature(fake_face)
+score_fake = calculate_cosine_similarity(user_embedding, fake_embedding)
+print(f"Test B (Self vs Random Noise): {score_fake:.4f} (Different Identity)")
+print("=" * 45)
+print("Standard SFace Threshold: >= 0.363 indicates the same person.")
+""".strip()
+
+DESCRIPTION = "Detect faces with YuNet, align them via affine transformation, and verify identity using SFace 128-D embeddings and cosine similarity."
diff --git a/recode/problems/flatten-list.py b/recode/problems/flatten-list.py
new file mode 100644
index 0000000..102d6c0
--- /dev/null
+++ b/recode/problems/flatten-list.py
@@ -0,0 +1,40 @@
+SOLUTION = """
+def flatten(lst):
+ \"\"\"Flatten a nested list of arbitrary depth.\"\"\"
+ result = []
+ for item in lst:
+ if isinstance(item, list):
+ result.extend(flatten(item))
+ else:
+ result.append(item)
+ return result
+""".strip()
+
+DESCRIPTION = "Implement a recursive function to flatten a nested list."
+
+# ── Test cases ──
+
+def _test_simple(ns):
+ fn = ns.get("flatten")
+ assert fn is not None, "flatten function not found"
+ assert fn([1, [2, 3], 4]) == [1, 2, 3, 4], f"got {fn([1, [2, 3], 4])}"
+
+def _test_deep(ns):
+ fn = ns["flatten"]
+ assert fn([1, [2, [3, [4]]]]) == [1, 2, 3, 4], "deeply nested failed"
+
+def _test_empty(ns):
+ fn = ns["flatten"]
+ assert fn([]) == [], "empty list should return []"
+ assert fn([[], [[]]]) == [], "nested empty lists"
+
+def _test_mixed(ns):
+ fn = ns["flatten"]
+ assert fn([1, "a", [2, ["b", [3]]]]) == [1, "a", 2, "b", 3], "mixed types failed"
+
+def _test_single(ns):
+ fn = ns["flatten"]
+ assert fn([1]) == [1], "single element"
+ assert fn([[1]]) == [1], "single nested element"
+
+TEST_CASES = [_test_simple, _test_deep, _test_empty, _test_mixed, _test_single]
diff --git a/recode/problems/gpt-character-level.py b/recode/problems/gpt-character-level.py
new file mode 100644
index 0000000..4c2da59
--- /dev/null
+++ b/recode/problems/gpt-character-level.py
@@ -0,0 +1,179 @@
+SOLUTION = """
+import torch
+import torch.nn as nn
+from torch.nn import functional as F
+import requests
+
+# Hyperparameters
+batch_size = 32
+block_size = 64 # Maximum context length
+max_iters = 3000
+eval_interval = 300
+learning_rate = 1e-3
+device = 'cuda' if torch.cuda.is_available() else 'cpu'
+eval_iters = 200
+n_embd = 128
+n_head = 4
+n_layer = 4
+dropout = 0.2
+
+# 2. Multi-Head Attention Mechanism
+class Head(nn.Module):
+ def __init__(self, head_size):
+ super().__init__()
+ self.key = nn.Linear(n_embd, head_size, bias=False)
+ self.query = nn.Linear(n_embd, head_size, bias=False)
+ self.value = nn.Linear(n_embd, head_size, bias=False)
+ self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
+ self.dropout = nn.Dropout(dropout)
+
+ def forward(self, x):
+ B,T,C = x.shape
+ k = self.key(x)
+ q = self.query(x)
+ wei = q @ k.transpose(-2,-1) * C**-0.5
+ wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf'))
+ wei = F.softmax(wei, dim=-1)
+ wei = self.dropout(wei)
+ v = self.value(x)
+ out = wei @ v
+ return out
+
+class MultiHeadAttention(nn.Module):
+ def __init__(self, num_heads, head_size):
+ super().__init__()
+ self.heads = nn.ModuleList([Head(head_size) for _ in range(num_heads)])
+ self.proj = nn.Linear(n_embd, n_embd)
+ self.dropout = nn.Dropout(dropout)
+
+ def forward(self, x):
+ out = torch.cat([h(x) for h in self.heads], dim=-1)
+ out = self.dropout(self.proj(out))
+ return out
+
+# 3. Feed Forward Network
+class FeedForward(nn.Module):
+ def __init__(self, n_embd):
+ super().__init__()
+ self.net = nn.Sequential(
+ nn.Linear(n_embd, 4 * n_embd),
+ nn.ReLU(),
+ nn.Linear(4 * n_embd, n_embd),
+ nn.Dropout(dropout),
+ )
+
+ def forward(self, x):
+ return self.net(x)
+
+# 4. Transformer Block
+class Block(nn.Module):
+ def __init__(self, n_embd, n_head):
+ super().__init__()
+ head_size = n_embd // n_head
+ self.sa = MultiHeadAttention(n_head, head_size)
+ self.ffwd = FeedForward(n_embd)
+ self.ln1 = nn.LayerNorm(n_embd)
+ self.ln2 = nn.LayerNorm(n_embd)
+
+ def forward(self, x):
+ x = x + self.sa(self.ln1(x))
+ x = x + self.ffwd(self.ln2(x))
+ return x
+
+# 5. The Language Model
+class NanoGPT(nn.Module):
+ def __init__(self):
+ super().__init__()
+ self.token_embedding_table = nn.Embedding(vocab_size, n_embd)
+ self.position_embedding_table = nn.Embedding(block_size, n_embd)
+ self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])
+ self.ln_f = nn.LayerNorm(n_embd)
+ self.lm_head = nn.Linear(n_embd, vocab_size)
+
+ def forward(self, idx, targets=None):
+ B, T = idx.shape
+ tok_emb = self.token_embedding_table(idx)
+ pos_emb = self.position_embedding_table(torch.arange(T, device=device))
+ x = tok_emb + pos_emb
+ x = self.blocks(x)
+ x = self.ln_f(x)
+ logits = self.lm_head(x)
+
+ if targets is None:
+ loss = None
+ else:
+ B, T, C = logits.shape
+ logits = logits.view(B*T, C)
+ targets = targets.view(B*T)
+ loss = F.cross_entropy(logits, targets)
+
+ return logits, loss
+
+# Generation function
+def generate(model, idx, max_new_tokens):
+ for _ in range(max_new_tokens):
+ idx_cond = idx[:, -block_size:]
+ logits, loss = model(idx_cond)
+ logits = logits[:, -1, :]
+ probs = F.softmax(logits, dim=-1)
+ idx_next = torch.multinomial(probs, num_samples=1)
+ idx = torch.cat((idx, idx_next), dim=1)
+ return idx
+
+# 1. Data Preparation
+url = "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt"
+response = requests.get(url)
+response.raise_for_status()
+text = response.text
+
+print(f"Loaded dataset with total characters: {len(text)}")
+
+chars = sorted(list(set(text)))
+vocab_size = len(chars)
+
+stoi = { ch:i for i,ch in enumerate(chars) }
+itos = { i:ch for i,ch in enumerate(chars) }
+encode = lambda s: [stoi[c] for c in s]
+decode = lambda l: ''.join([itos[i] for i in l])
+
+print(f"Vocabulary size: {vocab_size}")
+
+data = torch.tensor(encode(text), dtype=torch.long)
+
+n = int(0.9 * len(data))
+train_data = data[:n]
+val_data = data[n:]
+
+def get_batch(split):
+ data = train_data if split == 'train' else val_data
+ ix = torch.randint(len(data) - block_size, (batch_size,))
+ x = torch.stack([data[i:i+block_size] for i in ix])
+ y = torch.stack([data[i+1:i+block_size+1] for i in ix])
+ x, y = x.to(device), y.to(device)
+ return x, y
+
+model = NanoGPT().to(device)
+optimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)
+
+print("Starting training...")
+
+for iter in range(max_iters):
+ xb, yb = get_batch('train')
+ logits, loss = model(xb, yb)
+ optimizer.zero_grad(set_to_none=True)
+ loss.backward()
+ optimizer.step()
+
+ if iter % eval_interval == 0:
+ print(f"step {iter}: train loss {loss.item():.4f}")
+
+print(f"Final loss: {loss.item():.4f}")
+
+# Text Generation
+print("\\nGenerating text from the trained model:")
+context = torch.zeros((1, 1), dtype=torch.long, device=device)
+generated_text_indices = generate(model, context, max_new_tokens=500)[0].tolist()
+print(decode(generated_text_indices))
+""".strip()
+
+DESCRIPTION = "Implement a character-level NanoGPT with multi-head self-attention, transformer blocks, and train it on the TinyShakespeare dataset."
diff --git a/recode/problems/housing-price-xgboost.py b/recode/problems/housing-price-xgboost.py
new file mode 100644
index 0000000..6d5ded7
--- /dev/null
+++ b/recode/problems/housing-price-xgboost.py
@@ -0,0 +1,96 @@
+SOLUTION = """
+# HOUSING PRICE REGRESSION & FEATURE SELECTION WITH XGBOOST
+
+# Run this cell to install xgboost if it is not already in the environment:
+# !pip install xgboost pandas matplotlib seaborn scikit-learn -q
+
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import seaborn as sns
+import xgboost as xgb
+from sklearn.datasets import fetch_california_housing
+from sklearn.model_selection import train_test_split
+from sklearn.metrics import mean_squared_error, r2_score
+
+sns.set_theme(style="whitegrid")
+
+
+# 1. Data Loading and Initial Exploration
+
+print("Loading California Housing dataset...")
+california = fetch_california_housing()
+df = pd.DataFrame(california.data, columns=california.feature_names)
+df['MedHouseVal'] = california.target
+
+print(f"Dataset loaded: {df.shape[0]} rows, {df.shape[1]} columns.\\n")
+
+
+# 2. Pre-Modeling Feature Selection (Correlation Matrix)
+
+print("Analyzing linear correlations...")
+plt.figure(figsize=(10, 8))
+correlation_matrix = df.corr()
+sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', fmt=".2f", vmin=-1, vmax=1)
+plt.title('Feature Correlation Matrix')
+plt.show()
+
+
+# 3. Data Preprocessing
+
+X = df.drop('MedHouseVal', axis=1)
+y = df['MedHouseVal']
+
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+
+# 4. Model Training (XGBoost Regressor)
+
+print("\\nTraining XGBoost Regressor...")
+xg_reg = xgb.XGBRegressor(
+ objective='reg:squarederror',
+ n_estimators=150,
+ learning_rate=0.1,
+ max_depth=5,
+ random_state=42
+)
+
+xg_reg.fit(X_train, y_train)
+
+
+# 5. Evaluation Metrics
+
+y_pred = xg_reg.predict(X_test)
+rmse = np.sqrt(mean_squared_error(y_test, y_pred))
+r2 = r2_score(y_test, y_pred)
+
+print("-" * 40)
+print("MODEL PERFORMANCE:")
+print(f"Root Mean Squared Error (RMSE): {rmse:.4f}")
+print(f"R-squared (R2): {r2:.4f}")
+print("-" * 40 + "\\n")
+
+
+# 6. Feature Importance & Selection
+
+print("Generating Feature Importance plot...")
+
+importance_type = 'gain'
+importances = xg_reg.get_booster().get_score(importance_type=importance_type)
+
+importance_df = pd.DataFrame({
+ 'Feature': list(importances.keys()),
+ 'Importance (Gain)': list(importances.values())
+}).sort_values(by='Importance (Gain)', ascending=True)
+
+plt.figure(figsize=(10, 6))
+plt.barh(importance_df['Feature'], importance_df['Importance (Gain)'], color='#3498db')
+plt.xlabel('F-Score (Gain)')
+plt.ylabel('Features')
+plt.title('XGBoost Feature Importance (By Information Gain)')
+plt.show()
+
+print("\\nAnalysis Complete.")
+""".strip()
+
+DESCRIPTION = "Train an XGBoost regressor on the California Housing dataset and visualize feature importance by information gain."
diff --git a/recode/problems/iris-classification.py b/recode/problems/iris-classification.py
new file mode 100644
index 0000000..976f4da
--- /dev/null
+++ b/recode/problems/iris-classification.py
@@ -0,0 +1,76 @@
+SOLUTION = """
+# IRIS DATASET CLASSIFICATION & ALGORITHM COMPARISON
+import pandas as pd
+import seaborn as sns
+import matplotlib.pyplot as plt
+from sklearn.datasets import load_iris
+from sklearn.model_selection import train_test_split
+from sklearn.tree import DecisionTreeClassifier
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.svm import SVC
+from sklearn.metrics import accuracy_score
+
+sns.set_theme(style="ticks")
+
+
+# Data Loading
+
+print("Loading the Iris dataset...")
+iris = load_iris()
+
+df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
+df['species'] = pd.Categorical.from_codes(iris.target, iris.target_names)
+
+print(f"Dataset loaded successfully with {df.shape[0]} samples.")
+print(f"Features: {', '.join(iris.feature_names)}\\n")
+
+# Feature Visualization
+
+print("Generating feature scatter plots...")
+g = sns.pairplot(df, hue="species", palette="colorblind", markers=["o", "s", "D"])
+g.fig.suptitle("Scatter Plots of Iris Features by Species", y=1.02)
+plt.show()
+
+
+X = iris.data
+y = iris.target
+
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
+
+print("Data split into training and testing sets.\\n")
+
+
+# Model Training and Evaluation
+
+print("Training models and evaluating accuracy...\\n")
+
+models = {
+ "Decision Tree": DecisionTreeClassifier(random_state=42),
+ "Random Forest": RandomForestClassifier(random_state=42, n_estimators=100),
+ "Support Vector Machine (SVM)": SVC(random_state=42, kernel='linear')
+}
+
+results = {}
+
+for name, model in models.items():
+ model.fit(X_train, y_train)
+ predictions = model.predict(X_test)
+ accuracy = accuracy_score(y_test, predictions)
+ results[name] = accuracy
+ print(f"{name} trained.")
+
+
+# Final Comparison
+
+print("\\n" + "="*40)
+print("FINAL ACCURACY COMPARISON")
+print("="*40)
+
+sorted_results = dict(sorted(results.items(), key=lambda item: item[1], reverse=True))
+
+for name, acc in sorted_results.items():
+ print(f"{name:<30}: {acc * 100:.2f}%")
+print("="*40)
+""".strip()
+
+DESCRIPTION = "Compare Decision Tree, Random Forest, and SVM classifiers on the Iris dataset with pairplot visualization."
diff --git a/recode/problems/linear-regression-numpy.py b/recode/problems/linear-regression-numpy.py
new file mode 100644
index 0000000..b6f6d0b
--- /dev/null
+++ b/recode/problems/linear-regression-numpy.py
@@ -0,0 +1,85 @@
+SOLUTION = """
+# LINEAR REGRESSION FROM SCRATCH USING NUMPY
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+# 1. Generate Sample Data
+
+np.random.seed(42)
+N = 100
+
+X = 10 * np.random.rand(N)
+
+true_m = 3.0
+true_b = 5.0
+noise = np.random.randn(N) * 2.5
+Y = true_m * X + true_b + noise
+
+
+# 2. Initialize Parameters and Hyperparameters
+
+m = 0.0
+b = 0.0
+
+learning_rate = 0.01
+epochs = 1000
+
+loss_history = []
+
+print("Starting Gradient Descent...\\n")
+
+
+# 3. Training Loop (Gradient Descent)
+
+for epoch in range(epochs):
+ # Forward pass
+ Y_pred = m * X + b
+
+ # Error
+ error = Y - Y_pred
+
+ # Loss (MSE)
+ mse = (1/N) * np.sum(error**2)
+ loss_history.append(mse)
+
+ # Gradients
+ dm = -(2/N) * np.sum(X * error)
+ db = -(2/N) * np.sum(error)
+
+ # Parameter update
+ m = m - learning_rate * dm
+ b = b - learning_rate * db
+
+ if epoch % 100 == 0 or epoch == epochs - 1:
+ print(f"Epoch {epoch:4d} | MSE Loss: {mse:.4f} | m: {m:.4f}, b: {b:.4f}")
+
+print("\\nTraining Complete.")
+print(f"Target parameters : m = {true_m}, b = {true_b}")
+print(f"Learned parameters: m = {m:.4f}, b = {b:.4f}\\n")
+
+
+# 4. Visualization
+
+plt.figure(figsize=(12, 5))
+
+plt.subplot(1, 2, 1)
+plt.scatter(X, Y, color='blue', alpha=0.6, label='Data Points')
+plt.plot(X, m * X + b, color='red', linewidth=2, label=f'Best Fit: y={m:.2f}x+{b:.2f}')
+plt.title('Linear Regression Fit')
+plt.xlabel('X')
+plt.ylabel('Y')
+plt.legend()
+
+plt.subplot(1, 2, 2)
+plt.plot(range(epochs), loss_history, color='green', linewidth=2)
+plt.title('Mean Squared Error Loss over Epochs')
+plt.xlabel('Epoch')
+plt.ylabel('MSE Loss')
+
+plt.tight_layout()
+plt.show()
+""".strip()
+
+DESCRIPTION = "Implement linear regression from scratch in NumPy using gradient descent, then plot the fit and loss curve."
diff --git a/recode/problems/matrix-multiply-numpy.py b/recode/problems/matrix-multiply-numpy.py
new file mode 100644
index 0000000..2554337
--- /dev/null
+++ b/recode/problems/matrix-multiply-numpy.py
@@ -0,0 +1,53 @@
+SOLUTION = """
+import numpy as np
+
+def matrix_multiply(A: np.ndarray, B: np.ndarray) -> np.ndarray:
+ \"\"\"Multiply two matrices using NumPy.\"\"\"
+ return np.matmul(A, B)
+""".strip()
+
+DESCRIPTION = "Implement matrix multiplication using NumPy's matmul."
+
+# ── Test cases (exec-based, works without marimo) ──
+# Each test receives the user's exec'd namespace and should
+# raise AssertionError on failure or return True/False.
+
+def _test_basic_mult(ns):
+ """2x2 * 2x2"""
+ fn = ns.get("matrix_multiply")
+ assert fn is not None, "matrix_multiply not found"
+ A = np.array([[1, 2], [3, 4]])
+ B = np.array([[5, 6], [7, 8]])
+ result = fn(A, B)
+ expected = np.array([[19, 22], [43, 50]])
+ assert np.allclose(result, expected), f"got {result}, expected {expected}"
+
+def _test_identity(ns):
+ """A * I = A"""
+ fn = ns["matrix_multiply"]
+ A = np.array([[1, 2, 3], [4, 5, 6]])
+ I = np.eye(3)
+ result = fn(A, I)
+ assert np.allclose(result, A), "A * I should equal A"
+
+def _test_rectangular(ns):
+ """3x2 * 2x4"""
+ fn = ns["matrix_multiply"]
+ A = np.random.randn(3, 2)
+ B = np.random.randn(2, 4)
+ result = fn(A, B)
+ assert result.shape == (3, 4), f"wrong shape: {result.shape}"
+ assert np.allclose(result, np.matmul(A, B))
+
+def _test_type_error(ns):
+ """Incompatible shapes should raise"""
+ fn = ns["matrix_multiply"]
+ A = np.array([[1, 2]])
+ B = np.array([[1, 2, 3]])
+ try:
+ fn(A, B)
+ assert False, "should have raised an error for incompatible shapes"
+ except (ValueError, RuntimeError):
+ pass # expected
+
+TEST_CASES = [_test_basic_mult, _test_identity, _test_rectangular, _test_type_error]
diff --git a/recode/problems/multilingual-nlp.py b/recode/problems/multilingual-nlp.py
new file mode 100644
index 0000000..726ea7a
--- /dev/null
+++ b/recode/problems/multilingual-nlp.py
@@ -0,0 +1,112 @@
+SOLUTION = """
+import torch
+import numpy as np
+from datasets import load_dataset
+from transformers import (
+ AutoTokenizer,
+ AutoModelForSequenceClassification,
+ TrainingArguments,
+ Trainer,
+ DataCollatorWithPadding
+)
+import evaluate
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+print(f"Executing on device: {device}\\n")
+
+# 1. Loading Multilingual Data
+print("Loading papluca/language-identification dataset...")
+train_dataset = load_dataset("papluca/language-identification", split="train").shuffle(seed=42).select(range(1000))
+val_dataset = load_dataset("papluca/language-identification", split="validation").shuffle(seed=42).select(range(400))
+
+unique_labels = sorted(train_dataset.unique("labels"))
+label2id = {label: i for i, label in enumerate(unique_labels)}
+id2label = {i: label for label, i in label2id.items()}
+
+def adjust_labels(example):
+ return {'label': label2id[example['labels']]}
+
+train_dataset = train_dataset.map(adjust_labels)
+test_dataset = val_dataset.map(adjust_labels)
+
+train_dataset = train_dataset.remove_columns(["labels"])
+test_dataset = test_dataset.remove_columns(["labels"])
+
+# 2. Tokenization with XLM-RoBERTa
+model_checkpoint = "xlm-roberta-base"
+tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
+
+def tokenize_function(examples):
+ return tokenizer(examples["text"], truncation=True, max_length=128)
+
+print("Tokenizing multilingual text...")
+tokenized_train = train_dataset.map(tokenize_function, batched=True)
+tokenized_test = test_dataset.map(tokenize_function, batched=True)
+
+columns_to_keep = ['input_ids', 'attention_mask', 'label']
+tokenized_train = tokenized_train.remove_columns([col for col in tokenized_train.column_names if col not in columns_to_keep])
+tokenized_test = tokenized_test.remove_columns([col for col in tokenized_test.column_names if col not in columns_to_keep])
+
+data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
+
+# 3. Multilingual Model Initialization
+model = AutoModelForSequenceClassification.from_pretrained(
+ model_checkpoint,
+ num_labels=len(unique_labels),
+ id2label=id2label,
+ label2id=label2id
+)
+model.to(device)
+
+# 4. Training Setup
+metric = evaluate.load("accuracy")
+
+def compute_metrics(eval_pred):
+ logits, labels = eval_pred
+ predictions = np.argmax(logits, axis=-1)
+ return metric.compute(predictions=predictions, references=labels)
+
+training_args = TrainingArguments(
+ output_dir="./xlm-roberta-multilingual-langid",
+ learning_rate=2e-5,
+ per_device_train_batch_size=16,
+ num_train_epochs=2,
+ weight_decay=0.01,
+ eval_strategy="epoch",
+ save_strategy="epoch",
+ load_best_model_at_end=True,
+ report_to="none"
+)
+
+trainer = Trainer(
+ model=model,
+ args=training_args,
+ train_dataset=tokenized_train,
+ eval_dataset=tokenized_test,
+ data_collator=data_collator,
+ compute_metrics=compute_metrics,
+)
+
+# 5. Training and Multilingual Inference
+print("\\nTraining for Language Identification...")
+trainer.train()
+
+print("\\nTesting Multilingual Inference:")
+samples = [
+ "Hello, how are you?",
+ "Hola, ¿cómo estás?",
+ "Bonjour, comment allez-vous?",
+ "Guten Tag, wie geht es Ihnen?",
+ "こんにちは、お元気ですか?"
+]
+
+model.eval()
+with torch.no_grad():
+ for text in samples:
+ inputs = tokenizer(text, return_tensors="pt", truncation=True).to(device)
+ outputs = model(**inputs)
+ prediction = torch.argmax(outputs.logits, dim=-1).item()
+ print(f"Input: {text} --> Predicted Language Code: {model.config.id2label[prediction]}")
+""".strip()
+
+DESCRIPTION = "Fine-tune XLM-RoBERTa for multilingual language identification using the HuggingFace Trainer API."
diff --git a/recode/problems/neural-net-numpy.py b/recode/problems/neural-net-numpy.py
new file mode 100644
index 0000000..d0a32f3
--- /dev/null
+++ b/recode/problems/neural-net-numpy.py
@@ -0,0 +1,149 @@
+SOLUTION = """
+import numpy as np
+import matplotlib.pyplot as plt
+from datasets import load_dataset
+
+np.random.seed(42)
+
+
+# 1. Data Loading: Hugging Face Iris Dataset
+
+def load_hf_iris_dataset():
+ dataset = load_dataset('scikit-learn/iris', split='train')
+
+ X_features_raw = (
+ dataset.data.column(1).to_numpy(),
+ dataset.data.column(3).to_numpy()
+ )
+ X_features = np.array(list(zip(*X_features_raw)))
+
+ target_strings = dataset.data.column(5).to_numpy()
+ Y_labels = (target_strings != 'Iris-setosa').astype(int)
+
+ X = X_features.T
+ Y = Y_labels.reshape(1, -1)
+
+ return X, Y
+
+X, Y = load_hf_iris_dataset()
+print(f"Data shapes - X: {X.shape}, Y: {Y.shape}")
+
+
+# 2. Activation Functions and Derivatives
+
+def sigmoid(Z):
+ return 1 / (1 + np.exp(-Z))
+
+def relu(Z):
+ return np.maximum(0, Z)
+
+def relu_backward(Z):
+ return (Z > 0).astype(int)
+
+
+# 3. Neural Network Architecture
+
+def initialize_parameters(n_x, n_h, n_y):
+ W1 = np.random.randn(n_h, n_x) * 0.01
+ b1 = np.zeros((n_h, 1))
+ W2 = np.random.randn(n_y, n_h) * 0.01
+ b2 = np.zeros((n_y, 1))
+ return {"W1": W1, "b1": b1, "W2": W2, "b2": b2}
+
+
+# 4. Forward Propagation
+
+def forward_propagation(X, parameters):
+ W1, b1, W2, b2 = parameters["W1"], parameters["b1"], parameters["W2"], parameters["b2"]
+
+ Z1 = np.dot(W1, X) + b1
+ A1 = relu(Z1)
+
+ Z2 = np.dot(W2, A1) + b2
+ A2 = sigmoid(Z2)
+
+ cache = {"Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2}
+ return A2, cache
+
+def compute_cost(A2, Y):
+ m = Y.shape[1]
+ epsilon = 1e-10
+ A2_clipped = np.clip(A2, epsilon, 1 - epsilon)
+ logprobs = np.multiply(np.log(A2_clipped), Y) + np.multiply(np.log(1 - A2_clipped), 1 - Y)
+ cost = -np.sum(logprobs) / m
+ return np.squeeze(cost)
+
+
+# 5. Backpropagation
+
+def backward_propagation(parameters, cache, X, Y):
+ m = X.shape[1]
+ W1, W2 = parameters["W1"], parameters["W2"]
+ A1, A2, Z1 = cache["A1"], cache["A2"], cache["Z1"]
+
+ dZ2 = A2 - Y
+ dW2 = (1 / m) * np.dot(dZ2, A1.T)
+ db2 = (1 / m) * np.sum(dZ2, axis=1, keepdims=True)
+
+ dZ1 = np.dot(W2.T, dZ2) * relu_backward(Z1)
+ dW1 = (1 / m) * np.dot(dZ1, X.T)
+ db1 = (1 / m) * np.sum(dZ1, axis=1, keepdims=True)
+
+ return {"dW1": dW1, "db1": db1, "dW2": dW2, "db2": db2}
+
+def update_parameters(parameters, grads, learning_rate=0.05):
+ W1 = parameters["W1"] - learning_rate * grads["dW1"]
+ b1 = parameters["b1"] - learning_rate * grads["db1"]
+ W2 = parameters["W2"] - learning_rate * grads["dW2"]
+ b2 = parameters["b2"] - learning_rate * grads["db2"]
+ return {"W1": W1, "b1": b1, "W2": W2, "b2": b2}
+
+
+# 6. Training Loop
+
+print("\\nCommencing Network Training...")
+n_x = X.shape[0]
+n_h = 4
+n_y = Y.shape[0]
+
+parameters = initialize_parameters(n_x, n_h, n_y)
+epochs = 10000
+
+for i in range(epochs):
+ A2, cache = forward_propagation(X, parameters)
+ cost = compute_cost(A2, Y)
+ grads = backward_propagation(parameters, cache, X, Y)
+ parameters = update_parameters(parameters, grads, learning_rate=0.05)
+
+ if i % 1000 == 0:
+ print(f"Epoch {i:5d} | Cost: {cost:.6f}")
+
+print("\\nTraining Complete.")
+
+
+# 7. Visualization of Decision Boundary
+
+def predict(parameters, X):
+ A2, _ = forward_propagation(X, parameters)
+ return (A2 > 0.5).astype(int)
+
+def plot_decision_boundary(model, X, y):
+ x_min, x_max = X[0, :].min() - 1, X[0, :].max() + 1
+ y_min, y_max = X[1, :].min() - 1, X[1, :].max() + 1
+ h = 0.01
+ xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
+ Z = model(np.c_[xx.ravel(), yy.ravel()].T)
+ Z = Z.reshape(xx.shape)
+
+ plt.figure(figsize=(8, 6))
+ plt.contourf(xx, yy, Z, cmap=plt.cm.coolwarm, alpha=0.5)
+ plt.scatter(X[0, :], X[1, :], c=y.ravel(), cmap=plt.cm.coolwarm, edgecolors='k')
+ plt.title("Neural Network Decision Boundary")
+ plt.xlabel('Feature 1 (Sepal Length)')
+ plt.ylabel('Feature 2 (Petal Length)')
+ plt.show()
+
+plot_decision_boundary(lambda x: predict(parameters, x), X, Y)
+""".strip()
+
+DESCRIPTION = "Implement a 2-layer neural network from scratch in NumPy with ReLU/sigmoid activations, backprop, and decision boundary visualization."
diff --git a/recode/problems/sentiment-analysis-bert.py b/recode/problems/sentiment-analysis-bert.py
new file mode 100644
index 0000000..9d6fc06
--- /dev/null
+++ b/recode/problems/sentiment-analysis-bert.py
@@ -0,0 +1,138 @@
+SOLUTION = """
+# BERT FINE-TUNING FOR SENTIMENT ANALYSIS
+
+# !pip install transformers datasets evaluate torch emoji -q
+
+import torch
+import emoji
+import numpy as np
+import evaluate
+from datasets import load_dataset
+from transformers import (
+ AutoTokenizer,
+ AutoModelForSequenceClassification,
+ TrainingArguments,
+ Trainer,
+ DataCollatorWithPadding
+)
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+print(f"Executing on device: {device}\\n")
+
+
+# 1. Data Loading (Tweet Sentiment)
+
+print("Loading tweet_eval sentiment dataset from Hugging Face...")
+dataset = load_dataset("tweet_eval", "sentiment")
+
+small_train_dataset = dataset["train"].shuffle(seed=42).select(range(2000))
+small_eval_dataset = dataset["validation"].shuffle(seed=42).select(range(500))
+
+print(f"Training subset: {len(small_train_dataset)} rows")
+print(f"Validation subset: {len(small_eval_dataset)} rows\\n")
+
+
+# 2. Text Pre-processing (Handling Emojis)
+
+print("Applying text pre-processing (Demojization)...")
+
+def preprocess_text(example):
+ example['text'] = emoji.demojize(example['text'], language='en')
+ return example
+
+small_train_dataset = small_train_dataset.map(preprocess_text)
+small_eval_dataset = small_eval_dataset.map(preprocess_text)
+
+
+# 3. Tokenization
+
+print("Loading BERT tokenizer...")
+model_checkpoint = "bert-base-uncased"
+tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
+
+def tokenize_function(examples):
+ return tokenizer(examples["text"], truncation=True, max_length=128)
+
+print("Tokenizing datasets...")
+tokenized_train = small_train_dataset.map(tokenize_function, batched=True)
+tokenized_eval = small_eval_dataset.map(tokenize_function, batched=True)
+
+data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
+
+
+# 4. Model Initialization
+
+print("\\nInitializing pre-trained BERT model...")
+model = AutoModelForSequenceClassification.from_pretrained(
+ model_checkpoint,
+ num_labels=3
+)
+model.to(device)
+
+
+# 5. Training Setup
+
+metric = evaluate.load("accuracy")
+
+def compute_metrics(eval_pred):
+ logits, labels = eval_pred
+ predictions = np.argmax(logits, axis=-1)
+ return metric.compute(predictions=predictions, references=labels)
+
+training_args = TrainingArguments(
+ output_dir="./bert-sentiment-results",
+ learning_rate=2e-5,
+ per_device_train_batch_size=16,
+ per_device_eval_batch_size=16,
+ num_train_epochs=3,
+ weight_decay=0.01,
+ eval_strategy="epoch",
+ save_strategy="epoch",
+ load_best_model_at_end=True,
+ logging_dir='./logs',
+ logging_steps=50,
+ report_to="none"
+)
+
+trainer = Trainer(
+ model=model,
+ args=training_args,
+ train_dataset=tokenized_train,
+ eval_dataset=tokenized_eval,
+ data_collator=data_collator,
+ compute_metrics=compute_metrics,
+)
+
+
+# 6. Model Training & Evaluation
+
+print("\\nCommencing Fine-Tuning Process...")
+trainer.train()
+
+print("\\nEvaluating the best model on the validation set...")
+eval_results = trainer.evaluate()
+print(f"Final Validation Accuracy: {eval_results['eval_accuracy'] * 100:.2f}%")
+
+
+# 7. Inference Example
+
+print("\\nTesting the model with custom text:")
+test_sentences = [
+ "I absolutely love the new design, it works perfectly! :fire:",
+ "This was a terrible waste of my time, the product arrived broken.",
+ "It is okay, nothing special but it gets the job done."
+]
+
+model.eval()
+with torch.no_grad():
+ for text in test_sentences:
+ processed_text = emoji.demojize(text, language='en')
+ inputs = tokenizer(processed_text, return_tensors="pt", truncation=True, max_length=128).to(device)
+ outputs = model(**inputs)
+ probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
+ prediction = torch.argmax(probs, dim=-1).item()
+ labels_map = {0: "Negative", 1: "Neutral", 2: "Positive"}
+ print(f"Text: '{text}' --> Prediction: {labels_map[prediction]}")
+""".strip()
+
+DESCRIPTION = "Fine-tune BERT for 3-class tweet sentiment analysis using HuggingFace Trainer with emoji demojization preprocessing."
diff --git a/recode/problems/sigmoid.mo.py b/recode/problems/sigmoid.mo.py
new file mode 100644
index 0000000..f5ee95e
--- /dev/null
+++ b/recode/problems/sigmoid.mo.py
@@ -0,0 +1,120 @@
+"""
+Sigmoid function — marimo notebook format for Recode.
+
+This is a Recode problem with reactive test execution.
+The user's code is injected into `user_attempt` and all test cells
+re-run automatically when it changes.
+"""
+import marimo
+
+app = marimo.App()
+
+
+# === Reference Solution ===
+@app.cell
+def solution_cell():
+ """Reference solution — what the student is trying to remember."""
+ SOLUTION = '''
+import numpy as np
+
+def sigmoid(x):
+ """Compute the sigmoid function."""
+ return 1.0 / (1.0 + np.exp(-x))
+'''
+ DESCRIPTION = "Implement the sigmoid activation function using NumPy."
+ return SOLUTION, DESCRIPTION
+
+
+# === User's Code ===
+@app.cell
+def user_code_cell(SOLUTION):
+ """
+ The user's attempt. At runtime, Recode overrides `user_attempt`
+ with the student's code via app.run(defs={"user_attempt": user_code}).
+ """
+ user_attempt = SOLUTION # default: reference solution
+ return user_attempt,
+
+
+# === Test Execution ===
+@app.cell
+def test_runner(user_attempt):
+ """Execute user's code and run tests."""
+ import numpy as np
+
+ # Execute user's code in isolated namespace
+ ns = {}
+ try:
+ exec(user_attempt, ns)
+ except SyntaxError as e:
+ test_results = [("syntax check", False, f"Syntax error: {e}")]
+ except Exception as e:
+ test_results = [("exec", False, f"Runtime error: {e}")]
+ else:
+ test_results = []
+ sigmoid = ns.get("sigmoid")
+
+ if sigmoid is None:
+ test_results.append(("sigmoid function exists", False, "Function not found in code"))
+ else:
+ # Test 1: sigmoid(0) == 0.5
+ try:
+ out = sigmoid(0)
+ ok = abs(float(out) - 0.5) < 1e-6
+ test_results.append(("sigmoid(0) == 0.5", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(0) == 0.5", False, str(e)))
+
+ # Test 2: sigmoid(large positive) → 1
+ try:
+ out = float(sigmoid(1000))
+ ok = abs(out - 1.0) < 1e-4
+ test_results.append(("sigmoid(1000) ≈ 1.0", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(1000) ≈ 1.0", False, str(e)))
+
+ # Test 3: sigmoid(large negative) → 0
+ try:
+ out = float(sigmoid(-1000))
+ ok = abs(out - 0.0) < 1e-4
+ test_results.append(("sigmoid(-1000) ≈ 0.0", ok, f"got {out}"))
+ except Exception as e:
+ test_results.append(("sigmoid(-1000) ≈ 0.0", False, str(e)))
+
+ # Test 4: symmetry: sigmoid(-x) = 1 - sigmoid(x)
+ try:
+ x = 2.5
+ ok = abs(float(sigmoid(-x)) - (1 - float(sigmoid(x)))) < 1e-6
+ test_results.append(("sigmoid(-x) == 1 - sigmoid(x)", ok, ""))
+ except Exception as e:
+ test_results.append(("sigmoid(-x) == 1 - sigmoid(x)", False, str(e)))
+
+ # Test 5: vectorized input
+ try:
+ out = sigmoid(np.array([0, 1, -1]))
+ ok = hasattr(out, "__len__") and len(out) == 3
+ test_results.append(("accepts array input", ok, f"shape: {getattr(out, 'shape', 'N/A')}"))
+ except Exception as e:
+ test_results.append(("accepts array input", False, str(e)))
+
+ return test_results,
+
+
+# === Test Summary Display (marimo UI — optional) ===
+@app.cell
+def display_cell(test_results):
+ """Show test results in the notebook UI when viewed in marimo."""
+ import marimo as mo
+
+ passed = sum(1 for _, p, _ in test_results if p)
+ total = len(test_results)
+
+ rows = []
+ for name, passed_flag, detail in test_results:
+ icon = "✅" if passed_flag else "❌"
+ detail_str = f" — {detail}" if detail else ""
+ rows.append(f"{icon} **{name}**{detail_str}")
+
+ status = "🟢 All passed!" if passed == total else f"🔴 {passed}/{total} passed"
+ mo.md(f"## Test Results\n{status}\n\n" + "\n".join(rows))
+ return
diff --git a/recode/problems/stock-price-lstm.py b/recode/problems/stock-price-lstm.py
new file mode 100644
index 0000000..926eebb
--- /dev/null
+++ b/recode/problems/stock-price-lstm.py
@@ -0,0 +1,167 @@
+SOLUTION = """
+# TIME SERIES FORECASTING: NVIDIA (NVDA) WITH PYTORCH LSTM & DIRECTIONAL METRICS
+
+# !pip install yfinance torch numpy pandas matplotlib scikit-learn -q
+
+import yfinance as yf
+import torch
+import torch.nn as nn
+import numpy as np
+import pandas as pd
+import matplotlib.pyplot as plt
+from sklearn.preprocessing import MinMaxScaler
+from torch.utils.data import DataLoader, TensorDataset
+
+device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
+print(f"Executing on device: {device}\\n")
+
+
+# 1. Data Ingestion via yfinance
+
+ticker = "NVDA"
+print(f"Downloading historical data for {ticker}...")
+df = yf.download(ticker, start="2019-01-01", end="2026-01-01")
+
+print(f"Dataset loaded: {df.shape[0]} trading days.\\n")
+
+
+# 2. Time Series Feature Engineering
+
+print("Engineering temporal features...")
+
+df['MA_20'] = df['Close'].rolling(window=20).mean()
+df['MA_50'] = df['Close'].rolling(window=50).mean()
+df['Daily_Return'] = df['Close'].pct_change()
+df['DayOfYear'] = df.index.dayofyear
+df['DayOfYear_Sin'] = np.sin(2 * np.pi * df['DayOfYear'] / 365.25)
+df['DayOfYear_Cos'] = np.cos(2 * np.pi * df['DayOfYear'] / 365.25)
+
+df.dropna(inplace=True)
+
+features = ['Close', 'MA_20', 'MA_50', 'Daily_Return', 'DayOfYear_Sin', 'DayOfYear_Cos']
+data_subset = df[features].values
+
+
+# 3. Data Scaling and Sequence Generation
+
+scaler = MinMaxScaler(feature_range=(0, 1))
+scaled_data = scaler.fit_transform(data_subset)
+
+close_scaler = MinMaxScaler(feature_range=(0, 1))
+close_scaler.fit(df[['Close']])
+
+def create_sequences(data, seq_length):
+ xs, ys = [], []
+ for i in range(len(data) - seq_length):
+ x = data[i:(i + seq_length)]
+ y = data[i + seq_length, 0]
+ xs.append(x)
+ ys.append(y)
+ return np.array(xs), np.array(ys)
+
+seq_length = 60
+X, y = create_sequences(scaled_data, seq_length)
+
+train_size = int(len(X) * 0.8)
+X_train, y_train = X[:train_size], y[:train_size]
+X_test, y_test = X[train_size:], y[train_size:]
+
+X_train_tensor = torch.tensor(X_train, dtype=torch.float32).to(device)
+y_train_tensor = torch.tensor(y_train, dtype=torch.float32).unsqueeze(1).to(device)
+X_test_tensor = torch.tensor(X_test, dtype=torch.float32).to(device)
+y_test_tensor = torch.tensor(y_test, dtype=torch.float32).unsqueeze(1).to(device)
+
+train_dataset = TensorDataset(X_train_tensor, y_train_tensor)
+train_loader = DataLoader(train_dataset, batch_size=32, shuffle=False)
+
+
+# 4. PyTorch LSTM Architecture
+
+class StockLSTM(nn.Module):
+ def __init__(self, input_dim, hidden_dim, num_layers, output_dim):
+ super(StockLSTM, self).__init__()
+ self.hidden_dim = hidden_dim
+ self.num_layers = num_layers
+ self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True, dropout=0.2)
+ self.fc = nn.Linear(hidden_dim, output_dim)
+
+ def forward(self, x):
+ h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_().to(device)
+ c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_dim).requires_grad_().to(device)
+ out, _ = self.lstm(x, (h0.detach(), c0.detach()))
+ out = self.fc(out[:, -1, :])
+ return out
+
+input_dim = len(features)
+hidden_dim = 64
+num_layers = 2
+output_dim = 1
+
+model = StockLSTM(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim).to(device)
+
+criterion = nn.MSELoss()
+optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
+
+
+# 5. Training Loop
+
+print("\\nCommencing LSTM Training...")
+epochs = 50
+
+for epoch in range(epochs):
+ model.train()
+ epoch_loss = 0.0
+ for seqs, labels in train_loader:
+ optimizer.zero_grad()
+ outputs = model(seqs)
+ loss = criterion(outputs, labels)
+ loss.backward()
+ optimizer.step()
+ epoch_loss += loss.item()
+
+ if (epoch+1) % 10 == 0:
+ print(f'Epoch [{epoch+1}/{epochs}], MSE Loss: {epoch_loss/len(train_loader):.6f}')
+
+
+# 6. Evaluation, Directional Accuracy & Visualization
+
+print("\\nEvaluating model performance...")
+model.eval()
+with torch.no_grad():
+ predictions = model(X_test_tensor).cpu().numpy()
+ actuals = y_test_tensor.cpu().numpy()
+
+predictions_dollar = close_scaler.inverse_transform(predictions)
+actuals_dollar = close_scaler.inverse_transform(actuals)
+
+actual_deltas = actuals_dollar[1:] - actuals_dollar[:-1]
+predicted_deltas = predictions_dollar[1:] - actuals_dollar[:-1]
+
+actual_direction = np.sign(actual_deltas)
+predicted_direction = np.sign(predicted_deltas)
+
+correct_directions = np.sum(actual_direction == predicted_direction)
+directional_accuracy = correct_directions / len(actual_direction)
+
+print("=" * 45)
+print("FINAL MODEL METRICS")
+print("=" * 45)
+print(f"Directional Accuracy: {directional_accuracy * 100:.2f}%")
+print("Note: A random guess sits at ~50%.")
+print("=" * 45 + "\\n")
+
+plt.figure(figsize=(12, 6))
+test_dates = df.index[-len(actuals):]
+
+plt.plot(test_dates, actuals_dollar, color='#2c3e50', label='Actual NVDA Price', linewidth=2)
+plt.plot(test_dates, predictions_dollar, color='#e74c3c', label='Predicted NVDA Price', linewidth=2, linestyle='--')
+plt.title(f'NVIDIA (NVDA) Price Prediction (Directional Acc: {directional_accuracy * 100:.1f}%)', fontsize=14, pad=15)
+plt.xlabel('Date', fontsize=12)
+plt.ylabel('Price (USD)', fontsize=12)
+plt.legend()
+plt.grid(True, alpha=0.3)
+plt.tight_layout()
+plt.show()
+""".strip()
+
+DESCRIPTION = "Forecast NVIDIA stock prices using a multi-feature PyTorch LSTM with directional accuracy evaluation."
diff --git a/recode/problems/titanic-random-forest.py b/recode/problems/titanic-random-forest.py
new file mode 100644
index 0000000..72708d2
--- /dev/null
+++ b/recode/problems/titanic-random-forest.py
@@ -0,0 +1,131 @@
+SOLUTION = """
+# TITANIC SURVIVAL PREDICTION: FEATURE ENGINEERING & RANDOM FOREST
+import pandas as pd
+import numpy as np
+import seaborn as sns
+import matplotlib.pyplot as plt
+from sklearn.model_selection import train_test_split, cross_val_score
+from sklearn.ensemble import RandomForestClassifier
+from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
+from sklearn.preprocessing import LabelEncoder
+
+sns.set_theme(style="whitegrid")
+
+
+# 1. Data Loading
+
+print("Loading Titanic dataset from seaborn...")
+df = sns.load_dataset('titanic')
+print(f"Dataset loaded: {df.shape[0]} passengers, {df.shape[1]} columns.\\n")
+
+
+# 2. Exploratory Data Analysis
+
+print("Survival breakdown by class:")
+print(df.groupby(['pclass', 'sex'])['survived'].mean().unstack(), "\\n")
+
+plt.figure(figsize=(12, 4))
+plt.subplot(1, 3, 1)
+df['survived'].value_counts().plot(kind='bar', color=['#e74c3c', '#2ecc71'])
+plt.title('Overall Survival Counts')
+plt.xticks([0, 1], ['Did Not Survive', 'Survived'], rotation=0)
+
+plt.subplot(1, 3, 2)
+sns.barplot(x='pclass', y='survived', data=df, palette='Blues_d')
+plt.title('Survival Rate by Class')
+
+plt.subplot(1, 3, 3)
+sns.barplot(x='sex', y='survived', data=df, palette='Set2')
+plt.title('Survival Rate by Gender')
+
+plt.tight_layout()
+plt.show()
+
+
+# 3. Feature Engineering
+
+print("Engineering features...")
+
+df_clean = df.copy()
+
+# Family size as a single feature
+df_clean['family_size'] = df_clean['sibsp'] + df_clean['parch'] + 1
+df_clean['is_alone'] = (df_clean['family_size'] == 1).astype(int)
+
+# Extract title from name
+df_clean['title'] = df_clean['who'].map({'man': 0, 'woman': 1, 'child': 2})
+
+# Fill missing age with median by class and sex
+df_clean['age'] = df_clean.groupby(['pclass', 'sex'])['age'].transform(lambda x: x.fillna(x.median()))
+
+# Age bins
+df_clean['age_group'] = pd.cut(df_clean['age'], bins=[0, 12, 18, 35, 60, 100],
+ labels=['child', 'teen', 'adult', 'middle_age', 'senior'])
+
+# Fare bins
+df_clean['fare_group'] = pd.qcut(df_clean['fare'], q=4, labels=['low', 'mid', 'high', 'premium'])
+
+# Encode categoricals
+le = LabelEncoder()
+df_clean['sex_enc'] = le.fit_transform(df_clean['sex'])
+df_clean['embarked_enc'] = le.fit_transform(df_clean['embarked'].fillna('S'))
+df_clean['age_group_enc'] = le.fit_transform(df_clean['age_group'].astype(str))
+df_clean['fare_group_enc'] = le.fit_transform(df_clean['fare_group'].astype(str))
+
+
+# 4. Model Training
+
+features = ['pclass', 'sex_enc', 'age', 'family_size', 'is_alone',
+ 'fare', 'embarked_enc', 'title', 'age_group_enc', 'fare_group_enc']
+
+X = df_clean[features].fillna(0)
+y = df_clean['survived']
+
+X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
+
+print("\\nTraining Random Forest classifier...")
+rf_model = RandomForestClassifier(
+ n_estimators=200,
+ max_depth=8,
+ min_samples_split=5,
+ random_state=42,
+ n_jobs=-1
+)
+
+rf_model.fit(X_train, y_train)
+
+
+# 5. Evaluation
+
+y_pred = rf_model.predict(X_test)
+accuracy = accuracy_score(y_test, y_pred)
+cv_scores = cross_val_score(rf_model, X, y, cv=5)
+
+print("\\n" + "="*50)
+print("MODEL PERFORMANCE")
+print("="*50)
+print(f"Test Accuracy: {accuracy:.4f}")
+print(f"5-Fold CV: {cv_scores.mean():.4f} (+/- {cv_scores.std() * 2:.4f})")
+print("\\nClassification Report:")
+print(classification_report(y_test, y_pred, target_names=['Did Not Survive', 'Survived']))
+
+# Confusion matrix
+plt.figure(figsize=(6, 5))
+cm = confusion_matrix(y_test, y_pred)
+sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
+ xticklabels=['Predicted Not Survived', 'Predicted Survived'],
+ yticklabels=['Actual Not Survived', 'Actual Survived'])
+plt.title('Confusion Matrix')
+plt.tight_layout()
+plt.show()
+
+# Feature importance
+print("\\nTop Feature Importances:")
+importance_df = pd.DataFrame({
+ 'Feature': features,
+ 'Importance': rf_model.feature_importances_
+}).sort_values(by='Importance', ascending=False)
+print(importance_df.to_string(index=False))
+""".strip()
+
+DESCRIPTION = "Predict Titanic survival with Random Forest using feature engineering (family size, title, age/fare bins) and 5-fold cross-validation."
diff --git a/recode/problems/two-sum.py b/recode/problems/two-sum.py
new file mode 100644
index 0000000..47aec3c
--- /dev/null
+++ b/recode/problems/two-sum.py
@@ -0,0 +1,40 @@
+# ---
+# description: "Given an array of integers and a target, return indices of two numbers that add up to target"
+# difficulty: easy
+# tags: [arrays, hash-table]
+# source: leetcode/1
+# ---
+
+SOLUTION = """
+def two_sum(nums: list[int], target: int) -> list[int]:
+ \"\"\"Return indices of two numbers that add up to target.\"\"\"
+ seen = {}
+ for i, num in enumerate(nums):
+ complement = target - num
+ if complement in seen:
+ return [seen[complement], i]
+ seen[num] = i
+ return []
+""".strip()
+
+DESCRIPTION = "Given an array of integers and a target, return indices of two numbers that add up to target."
+
+# ── Test cases ──
+
+def _test_basic(ns):
+ fn = ns.get("two_sum")
+ assert fn is not None, "two_sum not found"
+ result = fn([2, 7, 11, 15], 9)
+ assert result == [0, 1], f"got {result}"
+
+def _test_different_order(ns):
+ fn = ns["two_sum"]
+ result = fn([3, 2, 4], 6)
+ assert sorted(result) == [1, 2], f"got {result}"
+
+def _test_duplicates(ns):
+ fn = ns["two_sum"]
+ result = fn([3, 3], 6)
+ assert sorted(result) == [0, 1], f"got {result}"
+
+TEST_CASES = [_test_basic, _test_different_order, _test_duplicates]
diff --git a/recode/runtime.py b/recode/runtime.py
new file mode 100644
index 0000000..e1c0910
--- /dev/null
+++ b/recode/runtime.py
@@ -0,0 +1,152 @@
+from __future__ import annotations
+
+import os
+import shutil
+from dataclasses import dataclass
+from pathlib import Path
+
+from dotenv import load_dotenv
+from platformdirs import user_config_dir, user_data_dir, user_state_dir
+
+from recode import __version__
+
+APP_NAME = "recode"
+APP_AUTHOR = "Ever"
+CODE_EXTENSIONS = {".py", ".jl", ".R"}
+
+
+@dataclass(frozen=True)
+class RuntimePaths:
+ config_dir: Path
+ data_dir: Path
+ state_dir: Path
+ bundled_problems_dir: Path
+ problems_dir: Path
+ db_path: Path
+ editor: str
+
+
+_ACTIVE_RUNTIME: RuntimePaths | None = None
+
+
+def _package_root() -> Path:
+ return Path(__file__).resolve().parent
+
+
+def _default_dirs() -> tuple[Path, Path, Path]:
+ root_override = os.environ.get("RECODE_HOME")
+ if root_override:
+ root = Path(root_override).expanduser().resolve()
+ return (root / "config", root / "data", root / "state")
+
+ return (
+ Path(user_config_dir(APP_NAME, APP_AUTHOR)).expanduser().resolve(),
+ Path(user_data_dir(APP_NAME, APP_AUTHOR)).expanduser().resolve(),
+ Path(user_state_dir(APP_NAME, APP_AUTHOR)).expanduser().resolve(),
+ )
+
+
+def _has_problem_files(path: Path) -> bool:
+ if not path.exists():
+ return False
+ return any(p.is_file() and p.suffix in CODE_EXTENSIONS for p in path.rglob("*"))
+
+
+def _seed_problem_bundle(source: Path, target: Path) -> None:
+ if not source.exists() or _has_problem_files(target):
+ return
+ shutil.copytree(
+ source,
+ target,
+ dirs_exist_ok=True,
+ ignore=shutil.ignore_patterns("__pycache__", ".DS_Store", "*.pyc"),
+ )
+
+
+def build_runtime(
+ *,
+ problems_dir: str | Path | None = None,
+ db_path: str | Path | None = None,
+ editor: str | None = None,
+) -> RuntimePaths:
+ load_dotenv(override=False)
+
+ default_config_dir, default_data_dir, default_state_dir = _default_dirs()
+
+ config_dir = Path(os.environ.get("RECODE_CONFIG_DIR", default_config_dir)).expanduser().resolve()
+ data_dir = Path(os.environ.get("RECODE_DATA_DIR", default_data_dir)).expanduser().resolve()
+ state_dir = Path(os.environ.get("RECODE_STATE_DIR", default_state_dir)).expanduser().resolve()
+
+ config_env = config_dir / ".env"
+ if config_env.exists():
+ load_dotenv(config_env, override=False)
+
+ bundled_problems_dir = _package_root() / "problems"
+ resolved_problems_dir = Path(
+ problems_dir
+ or os.environ.get("PROBLEMS_DIR")
+ or data_dir / "problems"
+ ).expanduser().resolve()
+ resolved_db_path = Path(
+ db_path
+ or os.environ.get("DB_PATH")
+ or state_dir / "study_data.db"
+ ).expanduser().resolve()
+
+ return RuntimePaths(
+ config_dir=config_dir,
+ data_dir=data_dir,
+ state_dir=state_dir,
+ bundled_problems_dir=bundled_problems_dir,
+ problems_dir=resolved_problems_dir,
+ db_path=resolved_db_path,
+ editor=editor or os.environ.get("EDITOR", "hx"),
+ )
+
+
+def prepare_runtime(
+ *,
+ problems_dir: str | Path | None = None,
+ db_path: str | Path | None = None,
+ editor: str | None = None,
+) -> RuntimePaths:
+ global _ACTIVE_RUNTIME
+
+ runtime = build_runtime(problems_dir=problems_dir, db_path=db_path, editor=editor)
+ runtime.config_dir.mkdir(parents=True, exist_ok=True)
+ runtime.data_dir.mkdir(parents=True, exist_ok=True)
+ runtime.state_dir.mkdir(parents=True, exist_ok=True)
+ runtime.db_path.parent.mkdir(parents=True, exist_ok=True)
+ runtime.problems_dir.parent.mkdir(parents=True, exist_ok=True)
+ _seed_problem_bundle(runtime.bundled_problems_dir, runtime.problems_dir)
+
+ _ACTIVE_RUNTIME = runtime
+ return runtime
+
+
+def get_runtime() -> RuntimePaths:
+ return _ACTIVE_RUNTIME or prepare_runtime()
+
+
+def doctor_report(runtime: RuntimePaths | None = None) -> str:
+ active = runtime or get_runtime()
+ checks = {
+ "Gemini key": bool(os.environ.get("GEMINI_API_KEY")),
+ "OpenRouter key": bool(os.environ.get("OPENROUTER_API_KEY")),
+ "OpenCode CLI": shutil.which("opencode") is not None,
+ "Editor": shutil.which(active.editor) is not None,
+ }
+
+ lines = [
+ f"recode {__version__}",
+ f"config_dir={active.config_dir}",
+ f"data_dir={active.data_dir}",
+ f"state_dir={active.state_dir}",
+ f"problems_dir={active.problems_dir}",
+ f"bundled_problems_dir={active.bundled_problems_dir}",
+ f"db_path={active.db_path}",
+ f"editor={active.editor}",
+ ]
+ for label, ok in checks.items():
+ lines.append(f"{label}={'ok' if ok else 'missing'}")
+ return "\n".join(lines)
diff --git a/requirements.txt b/requirements.txt
index 6d3db80..9dfd4ce 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,6 @@
-textual>=0.61.0
google-genai>=0.8.0
-python-dotenv>=1.0.0
+marimo>=0.11.0
+platformdirs>=4.2.0
pylatexenc>=2.10
+python-dotenv>=1.0.0
+textual>=8.0.0
diff --git a/test_runner.py b/test_runner.py
new file mode 100644
index 0000000..5ec4a33
--- /dev/null
+++ b/test_runner.py
@@ -0,0 +1,216 @@
+"""
+test_runner.py — Execute problem test cases via marimo or direct exec.
+
+Supports two test formats:
+1. Legacy: TEST_CASES list in regular .py problems (exec-based)
+2. Marimo: .mo.py marimo notebook problems with reactive test cells
+
+Both produce a list of (name: str, passed: bool, detail: str) tuples
+that the UI can display alongside the diff.
+"""
+from __future__ import annotations
+
+import importlib.util
+import sys
+import traceback
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Callable
+
+
+@dataclass
+class TestResult:
+ name: str
+ passed: bool
+ detail: str = ""
+
+
+def run_tests_exec(problem_path: Path, user_code: str) -> list[TestResult]:
+ """
+ Run tests from a regular .py problem file that defines TEST_CASES.
+
+ TEST_CASES is a list of dicts:
+ {"name": "sigmoid(0) == 0.5", "fn": lambda ns: abs(ns["sigmoid"](0) - 0.5) < 1e-6}
+ Or a list of callables that raise AssertionError on failure:
+ TEST_CASES = [test_sigmoid_zero, test_sigmoid_large]
+
+ The test functions receive a namespace dict with the user's exec'd code.
+ """
+ results: list[TestResult] = []
+
+ # Load the problem module to get TEST_CASES
+ spec = importlib.util.spec_from_file_location("_prob", problem_path)
+ if spec is None or spec.loader is None:
+ return [TestResult("load problem", False, "Could not load problem file")]
+
+ mod = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
+ except Exception as e:
+ return [TestResult("load problem", False, f"Import error: {e}")]
+
+ test_cases = getattr(mod, "TEST_CASES", None)
+ if not test_cases:
+ return [] # No tests defined
+
+ # Execute user's code in a clean namespace
+ user_ns: dict = {}
+ try:
+ exec(user_code, user_ns)
+ except Exception as e:
+ return [TestResult("exec user code", False, f"Syntax/runtime error: {e}")]
+
+ # Run each test
+ for i, tc in enumerate(test_cases):
+ if isinstance(tc, dict):
+ name = tc.get("name", f"test_{i}")
+ fn = tc.get("fn")
+ if fn is None:
+ results.append(TestResult(name, False, "No test function provided"))
+ continue
+ try:
+ passed = fn(user_ns)
+ results.append(TestResult(name, bool(passed), "" if passed else "assertion failed"))
+ except AssertionError as e:
+ results.append(TestResult(name, False, str(e)))
+ except Exception as e:
+ results.append(TestResult(name, False, f"{type(e).__name__}: {e}"))
+ elif callable(tc):
+ name = getattr(tc, "__name__", f"test_{i}")
+ try:
+ tc(user_ns)
+ results.append(TestResult(name, True, ""))
+ except AssertionError as e:
+ results.append(TestResult(name, False, str(e)))
+ except Exception as e:
+ results.append(TestResult(name, False, f"{type(e).__name__}: {e}"))
+ else:
+ results.append(TestResult(f"test_{i}", False, f"Unknown test case type: {type(tc)}"))
+
+ return results
+
+
+def run_tests_marimo(notebook_path: Path, user_code: str) -> list[TestResult]:
+ """
+ Run tests from a marimo notebook (.mo.py) by injecting user_code
+ and capturing test cell outputs.
+
+ The notebook must define an `app` (marimo.App) with a `user_attempt`
+ variable that defaults to the solution. We override it with user_code.
+ """
+ try:
+ import marimo
+ except ImportError:
+ return [TestResult("marimo", False, "marimo not installed: pip install marimo")]
+
+ # Import the notebook as a module to get the app
+ spec = importlib.util.spec_from_file_location("_marimo_nb", notebook_path)
+ if spec is None or spec.loader is None:
+ return [TestResult("load notebook", False, "Could not load notebook file")]
+
+ mod = importlib.util.module_from_spec(spec)
+ try:
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
+ except Exception as e:
+ return [TestResult("load notebook", False, f"Import error: {e}")]
+
+ app = getattr(mod, "app", None)
+ if app is None:
+ return [TestResult("marimo app", False, "No marimo.App found in notebook")]
+
+ # Run with user's code injected
+ try:
+ outputs, defs = app.run(defs={"user_attempt": user_code})
+ except Exception as e:
+ return [TestResult("marimo run", False, f"Execution error: {e}")]
+
+ # Extract test results from defs
+ # The notebook should define a `test_results` variable
+ test_results = defs.get("test_results", [])
+
+ if not test_results:
+ # Fallback: check if there's a single pass/fail
+ if "tests_passed" in defs:
+ passed = defs["tests_passed"]
+ return [TestResult("all tests", bool(passed), "")]
+ return [TestResult("tests", False, "No test_results or tests_passed found in notebook")]
+
+ # Convert to TestResult objects
+ results = []
+ for tr in test_results:
+ if isinstance(tr, (list, tuple)) and len(tr) >= 2:
+ results.append(TestResult(
+ name=str(tr[0]),
+ passed=bool(tr[1]),
+ detail=str(tr[2]) if len(tr) > 2 else "",
+ ))
+ elif isinstance(tr, dict):
+ results.append(TestResult(
+ name=tr.get("name", "unnamed"),
+ passed=bool(tr.get("passed", False)),
+ detail=tr.get("detail", ""),
+ ))
+ else:
+ results.append(TestResult("unknown", bool(tr), ""))
+
+ return results
+
+
+def run_tests(problem_path: Path, user_code: str) -> list[TestResult]:
+ """
+ Auto-detect test format and run appropriate test runner.
+
+ - .mo.py files → marimo runner
+ - .py files with TEST_CASES → exec runner
+ - otherwise → no tests
+ """
+ if problem_path.suffix == ".py" and ".mo" in problem_path.stem:
+ return run_tests_marimo(problem_path, user_code)
+ elif problem_path.suffix == ".py":
+ # Check if TEST_CASES is defined
+ try:
+ spec = importlib.util.spec_from_file_location("_prob_check", problem_path)
+ if spec and spec.loader:
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
+ if hasattr(mod, "TEST_CASES"):
+ return run_tests_exec(problem_path, user_code)
+ except Exception:
+ pass
+ return []
+
+
+def format_test_results(results: list[TestResult]) -> str:
+ """Format test results for display in the TUI."""
+ if not results:
+ return ""
+
+ lines = ["[bold]Test Results[/bold]", "─" * 40]
+ passed = sum(1 for r in results if r.passed)
+ total = len(results)
+
+ for r in results:
+ icon = "[green]✓[/green]" if r.passed else "[red]✗[/red]"
+ detail = f" [dim]{r.detail}[/dim]" if r.detail else ""
+ lines.append(f" {icon} {r.name}{detail}")
+
+ lines.append("─" * 40)
+ color = "green" if passed == total else "yellow" if passed > 0 else "red"
+ lines.append(f"[bold {color}]{passed}/{total} passed[/bold {color}]")
+
+ return "\n".join(lines)
+
+
+def has_tests(problem_path: Path) -> bool:
+ """Check if a problem file has test cases defined."""
+ if ".mo" in problem_path.stem:
+ return True
+ try:
+ spec = importlib.util.spec_from_file_location("_prob_check", problem_path)
+ if spec and spec.loader:
+ mod = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(mod) # type: ignore[union-attr]
+ return hasattr(mod, "TEST_CASES")
+ except Exception:
+ pass
+ return False
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..be9caa6
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,20 @@
+from __future__ import annotations
+
+from recode import __version__
+from recode.cli import main
+
+
+def test_cli_version(capsys):
+ assert main(["--version"]) == 0
+ captured = capsys.readouterr()
+ assert captured.out.strip() == __version__
+
+
+def test_cli_paths(capsys, monkeypatch, tmp_path):
+ monkeypatch.setenv("RECODE_HOME", str(tmp_path))
+
+ assert main(["--paths"]) == 0
+ captured = capsys.readouterr()
+
+ assert "problems_dir=" in captured.out
+ assert "db_path=" in captured.out
diff --git a/tests/test_db.py b/tests/test_db.py
new file mode 100644
index 0000000..b224b82
--- /dev/null
+++ b/tests/test_db.py
@@ -0,0 +1,21 @@
+from __future__ import annotations
+
+from db import get_db, get_row, log_mistake, recent_mistakes, reset_progress, sm2_update
+
+
+def test_sm2_update_and_reset_progress(tmp_path):
+ conn = get_db(tmp_path / "study.db")
+
+ sm2_update(conn, "two-sum", 3)
+ row = get_row(conn, "two-sum")
+
+ assert row is not None
+ assert row["reps"] == 1
+ assert row["last_rating"] == 3
+
+ log_mistake(conn, "two-sum", "missed hash map case")
+ assert recent_mistakes(conn, "two-sum") == ["missed hash map case"]
+
+ reset_progress(conn, "two-sum")
+ assert get_row(conn, "two-sum") is None
+ assert recent_mistakes(conn, "two-sum") == []
diff --git a/tests/test_problems_utils.py b/tests/test_problems_utils.py
new file mode 100644
index 0000000..4e95ed8
--- /dev/null
+++ b/tests/test_problems_utils.py
@@ -0,0 +1,37 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+from problems_utils import get_problem_id, load_problem_meta, scan_collections, scan_problems
+
+
+def test_scan_and_load_problem_meta(tmp_path):
+ root = tmp_path / "problems"
+ root.mkdir()
+ nested = root / "generated"
+ nested.mkdir()
+
+ problem = nested / "adder.py"
+ problem.write_text(
+ '# ---\n'
+ '# description: "Add two numbers."\n'
+ '# difficulty: easy\n'
+ '# tags: [arrays, math]\n'
+ '# ---\n'
+ "SOLUTION = '''\n"
+ "def add(a, b):\n"
+ " return a + b\n"
+ "'''.strip()\n"
+ )
+
+ assert scan_problems(nested) == [problem]
+ collections = scan_collections(root)
+ assert root not in collections
+ assert nested in collections
+ assert get_problem_id(problem, root) == "generated/adder"
+
+ meta = load_problem_meta(problem)
+ assert meta["description"] == "Add two numbers."
+ assert meta["difficulty"] == "easy"
+ assert meta["tags"] == ["arrays", "math"]
+ assert "def add" in meta["solution"]
diff --git a/tests/test_runtime.py b/tests/test_runtime.py
new file mode 100644
index 0000000..48c77f8
--- /dev/null
+++ b/tests/test_runtime.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+from recode import runtime
+
+
+def test_prepare_runtime_uses_recode_home_and_seeds_problems(monkeypatch, tmp_path):
+ monkeypatch.setenv("RECODE_HOME", str(tmp_path))
+ monkeypatch.delenv("RECODE_CONFIG_DIR", raising=False)
+ monkeypatch.delenv("RECODE_DATA_DIR", raising=False)
+ monkeypatch.delenv("RECODE_STATE_DIR", raising=False)
+ monkeypatch.delenv("PROBLEMS_DIR", raising=False)
+ monkeypatch.delenv("DB_PATH", raising=False)
+
+ configured = runtime.prepare_runtime()
+
+ assert configured.config_dir == (tmp_path / "config").resolve()
+ assert configured.data_dir == (tmp_path / "data").resolve()
+ assert configured.state_dir == (tmp_path / "state").resolve()
+ assert configured.problems_dir.exists()
+ assert configured.db_path.parent.exists()
+ assert any(configured.problems_dir.rglob("*.py"))
+
+
+def test_doctor_report_lists_key_paths(monkeypatch, tmp_path):
+ monkeypatch.setenv("RECODE_HOME", str(tmp_path))
+ configured = runtime.prepare_runtime()
+
+ report = runtime.doctor_report(configured)
+
+ assert "recode 0.1.0" in report
+ assert f"problems_dir={configured.problems_dir}" in report
+ assert f"db_path={configured.db_path}" in report
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..8c5be6d
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,1422 @@
+version = 1
+revision = 3
+requires-python = ">=3.10"
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.13.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "idna" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.2.25"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
+]
+
+[[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 = "charset-normalizer"
+version = "3.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/8c/2c56124c6dc53a774d435f985b5973bc592f42d437be58c0c92d65ae7296/charset_normalizer-3.4.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e1d8ca8611099001949d1cdfaefc510cf0f212484fe7c565f735b68c78c3c95", size = 298751, upload-time = "2026-03-15T18:50:00.003Z" },
+ { url = "https://files.pythonhosted.org/packages/86/2a/2a7db6b314b966a3bcad8c731c0719c60b931b931de7ae9f34b2839289ee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e25369dc110d58ddf29b949377a93e0716d72a24f62bad72b2b39f155949c1fd", size = 200027, upload-time = "2026-03-15T18:50:01.702Z" },
+ { url = "https://files.pythonhosted.org/packages/68/f2/0fe775c74ae25e2a3b07b01538fc162737b3e3f795bada3bc26f4d4d495c/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:259695e2ccc253feb2a016303543d691825e920917e31f894ca1a687982b1de4", size = 220741, upload-time = "2026-03-15T18:50:03.194Z" },
+ { url = "https://files.pythonhosted.org/packages/10/98/8085596e41f00b27dd6aa1e68413d1ddda7e605f34dd546833c61fddd709/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dda86aba335c902b6149a02a55b38e96287157e609200811837678214ba2b1db", size = 215802, upload-time = "2026-03-15T18:50:05.859Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/ce/865e4e09b041bad659d682bbd98b47fb490b8e124f9398c9448065f64fee/charset_normalizer-3.4.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51fb3c322c81d20567019778cb5a4a6f2dc1c200b886bc0d636238e364848c89", size = 207908, upload-time = "2026-03-15T18:50:07.676Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/54/8c757f1f7349262898c2f169e0d562b39dcb977503f18fdf0814e923db78/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:4482481cb0572180b6fd976a4d5c72a30263e98564da68b86ec91f0fe35e8565", size = 194357, upload-time = "2026-03-15T18:50:09.327Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/29/e88f2fac9218907fc7a70722b393d1bbe8334c61fe9c46640dba349b6e66/charset_normalizer-3.4.6-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39f5068d35621da2881271e5c3205125cc456f54e9030d3f723288c873a71bf9", size = 205610, upload-time = "2026-03-15T18:50:10.732Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/c5/21d7bb0cb415287178450171d130bed9d664211fdd59731ed2c34267b07d/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bea55c4eef25b0b19a0337dc4e3f9a15b00d569c77211fa8cde38684f234fb7", size = 203512, upload-time = "2026-03-15T18:50:12.535Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/be/ce52f3c7fdb35cc987ad38a53ebcef52eec498f4fb6c66ecfe62cfe57ba2/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f0cdaecd4c953bfae0b6bb64910aaaca5a424ad9c72d85cb88417bb9814f7550", size = 195398, upload-time = "2026-03-15T18:50:14.236Z" },
+ { url = "https://files.pythonhosted.org/packages/81/a0/3ab5dd39d4859a3555e5dadfc8a9fa7f8352f8c183d1a65c90264517da0e/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:150b8ce8e830eb7ccb029ec9ca36022f756986aaaa7956aad6d9ec90089338c0", size = 221772, upload-time = "2026-03-15T18:50:15.581Z" },
+ { url = "https://files.pythonhosted.org/packages/04/6e/6a4e41a97ba6b2fa87f849c41e4d229449a586be85053c4d90135fe82d26/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e68c14b04827dd76dcbd1aeea9e604e3e4b78322d8faf2f8132c7138efa340a8", size = 205759, upload-time = "2026-03-15T18:50:17.047Z" },
+ { url = "https://files.pythonhosted.org/packages/db/3b/34a712a5ee64a6957bf355b01dc17b12de457638d436fdb05d01e463cd1c/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3778fd7d7cd04ae8f54651f4a7a0bd6e39a0cf20f801720a4c21d80e9b7ad6b0", size = 216938, upload-time = "2026-03-15T18:50:18.44Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/05/5bd1e12da9ab18790af05c61aafd01a60f489778179b621ac2a305243c62/charset_normalizer-3.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dad6e0f2e481fffdcf776d10ebee25e0ef89f16d691f1e5dee4b586375fdc64b", size = 210138, upload-time = "2026-03-15T18:50:19.852Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/8e/3cb9e2d998ff6b21c0a1860343cb7b83eba9cdb66b91410e18fc4969d6ab/charset_normalizer-3.4.6-cp310-cp310-win32.whl", hash = "sha256:74a2e659c7ecbc73562e2a15e05039f1e22c75b7c7618b4b574a3ea9118d1557", size = 144137, upload-time = "2026-03-15T18:50:21.505Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/8f/78f5489ffadb0db3eb7aff53d31c24531d33eb545f0c6f6567c25f49a5ff/charset_normalizer-3.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:aa9cccf4a44b9b62d8ba8b4dd06c649ba683e4bf04eea606d2e94cfc2d6ff4d6", size = 154244, upload-time = "2026-03-15T18:50:22.81Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/74/e472659dffb0cadb2f411282d2d76c60da1fc94076d7fffed4ae8a93ec01/charset_normalizer-3.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:e985a16ff513596f217cee86c21371b8cd011c0f6f056d0920aa2d926c544058", size = 143312, upload-time = "2026-03-15T18:50:24.074Z" },
+ { url = "https://files.pythonhosted.org/packages/62/28/ff6f234e628a2de61c458be2779cb182bc03f6eec12200d4a525bbfc9741/charset_normalizer-3.4.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82060f995ab5003a2d6e0f4ad29065b7672b6593c8c63559beefe5b443242c3e", size = 293582, upload-time = "2026-03-15T18:50:25.454Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/b7/b1a117e5385cbdb3205f6055403c2a2a220c5ea80b8716c324eaf75c5c95/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60c74963d8350241a79cb8feea80e54d518f72c26db618862a8f53e5023deaf9", size = 197240, upload-time = "2026-03-15T18:50:27.196Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/5f/2574f0f09f3c3bc1b2f992e20bce6546cb1f17e111c5be07308dc5427956/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6e4333fb15c83f7d1482a76d45a0818897b3d33f00efd215528ff7c51b8e35d", size = 217363, upload-time = "2026-03-15T18:50:28.601Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d1/0ae20ad77bc949ddd39b51bf383b6ca932f2916074c95cad34ae465ab71f/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bc72863f4d9aba2e8fd9085e63548a324ba706d2ea2c83b260da08a59b9482de", size = 212994, upload-time = "2026-03-15T18:50:30.102Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ac/3233d262a310c1b12633536a07cde5ddd16985e6e7e238e9f3f9423d8eb9/charset_normalizer-3.4.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cc4fc6c196d6a8b76629a70ddfcd4635a6898756e2d9cac5565cf0654605d73", size = 204697, upload-time = "2026-03-15T18:50:31.654Z" },
+ { url = "https://files.pythonhosted.org/packages/25/3c/8a18fc411f085b82303cfb7154eed5bd49c77035eb7608d049468b53f87c/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:0c173ce3a681f309f31b87125fecec7a5d1347261ea11ebbb856fa6006b23c8c", size = 191673, upload-time = "2026-03-15T18:50:33.433Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/a7/11cfe61d6c5c5c7438d6ba40919d0306ed83c9ab957f3d4da2277ff67836/charset_normalizer-3.4.6-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c907cdc8109f6c619e6254212e794d6548373cc40e1ec75e6e3823d9135d29cc", size = 201120, upload-time = "2026-03-15T18:50:35.105Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/10/cf491fa1abd47c02f69687046b896c950b92b6cd7337a27e6548adbec8e4/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:404a1e552cf5b675a87f0651f8b79f5f1e6fd100ee88dc612f89aa16abd4486f", size = 200911, upload-time = "2026-03-15T18:50:36.819Z" },
+ { url = "https://files.pythonhosted.org/packages/28/70/039796160b48b18ed466fde0af84c1b090c4e288fae26cd674ad04a2d703/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e3c701e954abf6fc03a49f7c579cc80c2c6cc52525340ca3186c41d3f33482ef", size = 192516, upload-time = "2026-03-15T18:50:38.228Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/34/c56f3223393d6ff3124b9e78f7de738047c2d6bc40a4f16ac0c9d7a1cb3c/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7a6967aaf043bceabab5412ed6bd6bd26603dae84d5cb75bf8d9a74a4959d398", size = 218795, upload-time = "2026-03-15T18:50:39.664Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/3b/ce2d4f86c5282191a041fdc5a4ce18f1c6bd40a5bd1f74cf8625f08d51c1/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5feb91325bbceade6afab43eb3b508c63ee53579fe896c77137ded51c6b6958e", size = 201833, upload-time = "2026-03-15T18:50:41.552Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/9b/b6a9f76b0fd7c5b5ec58b228ff7e85095370282150f0bd50b3126f5506d6/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f820f24b09e3e779fe84c3c456cb4108a7aa639b0d1f02c28046e11bfcd088ed", size = 213920, upload-time = "2026-03-15T18:50:43.33Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/98/7bc23513a33d8172365ed30ee3a3b3fe1ece14a395e5fc94129541fc6003/charset_normalizer-3.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b35b200d6a71b9839a46b9b7fff66b6638bb52fc9658aa58796b0326595d3021", size = 206951, upload-time = "2026-03-15T18:50:44.789Z" },
+ { url = "https://files.pythonhosted.org/packages/32/73/c0b86f3d1458468e11aec870e6b3feac931facbe105a894b552b0e518e79/charset_normalizer-3.4.6-cp311-cp311-win32.whl", hash = "sha256:9ca4c0b502ab399ef89248a2c84c54954f77a070f28e546a85e91da627d1301e", size = 143703, upload-time = "2026-03-15T18:50:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/e3/76f2facfe8eddee0bbd38d2594e709033338eae44ebf1738bcefe0a06185/charset_normalizer-3.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:a9e68c9d88823b274cf1e72f28cb5dc89c990edf430b0bfd3e2fb0785bfeabf4", size = 153857, upload-time = "2026-03-15T18:50:47.563Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/dc/9abe19c9b27e6cd3636036b9d1b387b78c40dedbf0b47f9366737684b4b0/charset_normalizer-3.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:97d0235baafca5f2b09cf332cc275f021e694e8362c6bb9c96fc9a0eb74fc316", size = 142751, upload-time = "2026-03-15T18:50:49.234Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" },
+ { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" },
+ { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" },
+ { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" },
+ { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" },
+ { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" },
+ { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" },
+ { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" },
+ { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" },
+ { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" },
+ { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" },
+ { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" },
+ { url = "https://files.pythonhosted.org/packages/25/6f/ffe1e1259f384594063ea1869bfb6be5cdb8bc81020fc36c3636bc8302a1/charset_normalizer-3.4.6-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9cc6e6d9e571d2f863fa77700701dae73ed5f78881efc8b3f9a4398772ff53e8", size = 294458, upload-time = "2026-03-15T18:51:41.134Z" },
+ { url = "https://files.pythonhosted.org/packages/56/60/09bb6c13a8c1016c2ed5c6a6488e4ffef506461aa5161662bd7636936fb1/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5960d965e67165d75b7c7ffc60a83ec5abfc5c11b764ec13ea54fbef8b4421", size = 199277, upload-time = "2026-03-15T18:51:42.953Z" },
+ { url = "https://files.pythonhosted.org/packages/00/50/dcfbb72a5138bbefdc3332e8d81a23494bf67998b4b100703fd15fa52d81/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3694e3f87f8ac7ce279d4355645b3c878d24d1424581b46282f24b92f5a4ae2", size = 218758, upload-time = "2026-03-15T18:51:44.339Z" },
+ { url = "https://files.pythonhosted.org/packages/03/b3/d79a9a191bb75f5aa81f3aaaa387ef29ce7cb7a9e5074ba8ea095cc073c2/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d11595abf8dd942a77883a39d81433739b287b6aa71620f15164f8096221b30", size = 215299, upload-time = "2026-03-15T18:51:45.871Z" },
+ { url = "https://files.pythonhosted.org/packages/76/7e/bc8911719f7084f72fd545f647601ea3532363927f807d296a8c88a62c0d/charset_normalizer-3.4.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bda6eebafd42133efdca535b04ccb338ab29467b3f7bf79569883676fc628db", size = 206811, upload-time = "2026-03-15T18:51:47.308Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/40/c430b969d41dda0c465aa36cc7c2c068afb67177bef50905ac371b28ccc7/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:bbc8c8650c6e51041ad1be191742b8b421d05bbd3410f43fa2a00c8db87678e8", size = 193706, upload-time = "2026-03-15T18:51:48.849Z" },
+ { url = "https://files.pythonhosted.org/packages/48/15/e35e0590af254f7df984de1323640ef375df5761f615b6225ba8deb9799a/charset_normalizer-3.4.6-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22c6f0c2fbc31e76c3b8a86fba1a56eda6166e238c29cdd3d14befdb4a4e4815", size = 202706, upload-time = "2026-03-15T18:51:50.257Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/bd/f736f7b9cc5e93a18b794a50346bb16fbfd6b37f99e8f306f7951d27c17c/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7edbed096e4a4798710ed6bc75dcaa2a21b68b6c356553ac4823c3658d53743a", size = 202497, upload-time = "2026-03-15T18:51:52.012Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/ba/2cc9e3e7dfdf7760a6ed8da7446d22536f3d0ce114ac63dee2a5a3599e62/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7f9019c9cb613f084481bd6a100b12e1547cf2efe362d873c2e31e4035a6fa43", size = 193511, upload-time = "2026-03-15T18:51:53.723Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/cb/5be49b5f776e5613be07298c80e1b02a2d900f7a7de807230595c85a8b2e/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:58c948d0d086229efc484fe2f30c2d382c86720f55cd9bc33591774348ad44e0", size = 220133, upload-time = "2026-03-15T18:51:55.333Z" },
+ { url = "https://files.pythonhosted.org/packages/83/43/99f1b5dad345accb322c80c7821071554f791a95ee50c1c90041c157ae99/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:419a9d91bd238052642a51938af8ac05da5b3343becde08d5cdeab9046df9ee1", size = 203035, upload-time = "2026-03-15T18:51:56.736Z" },
+ { url = "https://files.pythonhosted.org/packages/87/9a/62c2cb6a531483b55dddff1a68b3d891a8b498f3ca555fbcf2978e804d9d/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5273b9f0b5835ff0350c0828faea623c68bfa65b792720c453e22b25cc72930f", size = 216321, upload-time = "2026-03-15T18:51:58.17Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/79/94a010ff81e3aec7c293eb82c28f930918e517bc144c9906a060844462eb/charset_normalizer-3.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0e901eb1049fdb80f5bd11ed5ea1e498ec423102f7a9b9e4645d5b8204ff2815", size = 208973, upload-time = "2026-03-15T18:51:59.998Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/57/4ecff6d4ec8585342f0c71bc03efaa99cb7468f7c91a57b105bcd561cea8/charset_normalizer-3.4.6-cp314-cp314-win32.whl", hash = "sha256:b4ff1d35e8c5bd078be89349b6f3a845128e685e751b6ea1169cf2160b344c4d", size = 144610, upload-time = "2026-03-15T18:52:02.213Z" },
+ { url = "https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:74119174722c4349af9708993118581686f343adc1c8c9c007d59be90d077f3f", size = 154962, upload-time = "2026-03-15T18:52:03.658Z" },
+ { url = "https://files.pythonhosted.org/packages/46/4c/48f2cdbfd923026503dfd67ccea45c94fd8fe988d9056b468579c66ed62b/charset_normalizer-3.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:e5bcc1a1ae744e0bb59641171ae53743760130600da8db48cbb6e4918e186e4e", size = 143595, upload-time = "2026-03-15T18:52:05.123Z" },
+ { url = "https://files.pythonhosted.org/packages/31/93/8878be7569f87b14f1d52032946131bcb6ebbd8af3e20446bc04053dc3f1/charset_normalizer-3.4.6-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ad8faf8df23f0378c6d527d8b0b15ea4a2e23c89376877c598c4870d1b2c7866", size = 314828, upload-time = "2026-03-15T18:52:06.831Z" },
+ { url = "https://files.pythonhosted.org/packages/06/b6/fae511ca98aac69ecc35cde828b0a3d146325dd03d99655ad38fc2cc3293/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5ea69428fa1b49573eef0cc44a1d43bebd45ad0c611eb7d7eac760c7ae771bc", size = 208138, upload-time = "2026-03-15T18:52:08.239Z" },
+ { url = "https://files.pythonhosted.org/packages/54/57/64caf6e1bf07274a1e0b7c160a55ee9e8c9ec32c46846ce59b9c333f7008/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:06a7e86163334edfc5d20fe104db92fcd666e5a5df0977cb5680a506fe26cc8e", size = 224679, upload-time = "2026-03-15T18:52:10.043Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/cb/9ff5a25b9273ef160861b41f6937f86fae18b0792fe0a8e75e06acb08f1d/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e1f6e2f00a6b8edb562826e4632e26d063ac10307e80f7461f7de3ad8ef3f077", size = 223475, upload-time = "2026-03-15T18:52:11.854Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/97/440635fc093b8d7347502a377031f9605a1039c958f3cd18dcacffb37743/charset_normalizer-3.4.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b52c68d64c1878818687a473a10547b3292e82b6f6fe483808fb1468e2f52f", size = 215230, upload-time = "2026-03-15T18:52:13.325Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/24/afff630feb571a13f07c8539fbb502d2ab494019492aaffc78ef41f1d1d0/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:7504e9b7dc05f99a9bbb4525c67a2c155073b44d720470a148b34166a69c054e", size = 199045, upload-time = "2026-03-15T18:52:14.752Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/17/d1399ecdaf7e0498c327433e7eefdd862b41236a7e484355b8e0e5ebd64b/charset_normalizer-3.4.6-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:172985e4ff804a7ad08eebec0a1640ece87ba5041d565fff23c8f99c1f389484", size = 211658, upload-time = "2026-03-15T18:52:16.278Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/38/16baa0affb957b3d880e5ac2144caf3f9d7de7bc4a91842e447fbb5e8b67/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4be9f4830ba8741527693848403e2c457c16e499100963ec711b1c6f2049b7c7", size = 210769, upload-time = "2026-03-15T18:52:17.782Z" },
+ { url = "https://files.pythonhosted.org/packages/05/34/c531bc6ac4c21da9ddfddb3107be2287188b3ea4b53b70fc58f2a77ac8d8/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:79090741d842f564b1b2827c0b82d846405b744d31e84f18d7a7b41c20e473ff", size = 201328, upload-time = "2026-03-15T18:52:19.553Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/73/a5a1e9ca5f234519c1953608a03fe109c306b97fdfb25f09182babad51a7/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:87725cfb1a4f1f8c2fc9890ae2f42094120f4b44db9360be5d99a4c6b0e03a9e", size = 225302, upload-time = "2026-03-15T18:52:21.043Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/f6/cd782923d112d296294dea4bcc7af5a7ae0f86ab79f8fefbda5526b6cfc0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fcce033e4021347d80ed9c66dcf1e7b1546319834b74445f561d2e2221de5659", size = 211127, upload-time = "2026-03-15T18:52:22.491Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/c5/0b6898950627af7d6103a449b22320372c24c6feda91aa24e201a478d161/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ca0276464d148c72defa8bb4390cce01b4a0e425f3b50d1435aa6d7a18107602", size = 222840, upload-time = "2026-03-15T18:52:24.113Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/25/c4bba773bef442cbdc06111d40daa3de5050a676fa26e85090fc54dd12f0/charset_normalizer-3.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:197c1a244a274bb016dd8b79204850144ef77fe81c5b797dc389327adb552407", size = 216890, upload-time = "2026-03-15T18:52:25.541Z" },
+ { url = "https://files.pythonhosted.org/packages/35/1a/05dacadb0978da72ee287b0143097db12f2e7e8d3ffc4647da07a383b0b7/charset_normalizer-3.4.6-cp314-cp314t-win32.whl", hash = "sha256:2a24157fa36980478dd1770b585c0f30d19e18f4fb0c47c13aa568f871718579", size = 155379, upload-time = "2026-03-15T18:52:27.05Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7a/d269d834cb3a76291651256f3b9a5945e81d0a49ab9f4a498964e83c0416/charset_normalizer-3.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:cd5e2801c89992ed8c0a3f0293ae83c159a60d9a5d685005383ef4caca77f2c4", size = 169043, upload-time = "2026-03-15T18:52:28.502Z" },
+ { url = "https://files.pythonhosted.org/packages/23/06/28b29fba521a37a8932c6a84192175c34d49f84a6d4773fa63d05f9aff22/charset_normalizer-3.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:47955475ac79cc504ef2704b192364e51d0d473ad452caedd0002605f780101c", size = 148523, upload-time = "2026-03-15T18:52:29.956Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+]
+
+[[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 = "cryptography"
+version = "46.0.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a4/ba/04b1bd4218cbc58dc90ce967106d51582371b898690f3ae0402876cc4f34/cryptography-46.0.6.tar.gz", hash = "sha256:27550628a518c5c6c903d84f637fbecf287f6cb9ced3804838a1295dc1fd0759", size = 750542, upload-time = "2026-03-25T23:34:53.396Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/23/9285e15e3bc57325b0a72e592921983a701efc1ee8f91c06c5f0235d86d9/cryptography-46.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:64235194bad039a10bb6d2d930ab3323baaec67e2ce36215fd0952fad0930ca8", size = 7176401, upload-time = "2026-03-25T23:33:22.096Z" },
+ { url = "https://files.pythonhosted.org/packages/60/f8/e61f8f13950ab6195b31913b42d39f0f9afc7d93f76710f299b5ec286ae6/cryptography-46.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:26031f1e5ca62fcb9d1fcb34b2b60b390d1aacaa15dc8b895a9ed00968b97b30", size = 4275275, upload-time = "2026-03-25T23:33:23.844Z" },
+ { url = "https://files.pythonhosted.org/packages/19/69/732a736d12c2631e140be2348b4ad3d226302df63ef64d30dfdb8db7ad1c/cryptography-46.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9a693028b9cbe51b5a1136232ee8f2bc242e4e19d456ded3fa7c86e43c713b4a", size = 4425320, upload-time = "2026-03-25T23:33:25.703Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/12/123be7292674abf76b21ac1fc0e1af50661f0e5b8f0ec8285faac18eb99e/cryptography-46.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:67177e8a9f421aa2d3a170c3e56eca4e0128883cf52a071a7cbf53297f18b175", size = 4278082, upload-time = "2026-03-25T23:33:27.423Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ba/d5e27f8d68c24951b0a484924a84c7cdaed7502bac9f18601cd357f8b1d2/cryptography-46.0.6-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d9528b535a6c4f8ff37847144b8986a9a143585f0540fbcb1a98115b543aa463", size = 4926514, upload-time = "2026-03-25T23:33:29.206Z" },
+ { url = "https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97", size = 4457766, upload-time = "2026-03-25T23:33:30.834Z" },
+ { url = "https://files.pythonhosted.org/packages/01/59/562be1e653accee4fdad92c7a2e88fced26b3fdfce144047519bbebc299e/cryptography-46.0.6-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:760997a4b950ff00d418398ad73fbc91aa2894b5c1db7ccb45b4f68b42a63b3c", size = 3986535, upload-time = "2026-03-25T23:33:33.02Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/8b/b1ebfeb788bf4624d36e45ed2662b8bd43a05ff62157093c1539c1288a18/cryptography-46.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3dfa6567f2e9e4c5dceb8ccb5a708158a2a871052fa75c8b78cb0977063f1507", size = 4277618, upload-time = "2026-03-25T23:33:34.567Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/52/a005f8eabdb28df57c20f84c44d397a755782d6ff6d455f05baa2785bd91/cryptography-46.0.6-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:cdcd3edcbc5d55757e5f5f3d330dd00007ae463a7e7aa5bf132d1f22a4b62b19", size = 4890802, upload-time = "2026-03-25T23:33:37.034Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/4d/8e7d7245c79c617d08724e2efa397737715ca0ec830ecb3c91e547302555/cryptography-46.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d4e4aadb7fc1f88687f47ca20bb7227981b03afaae69287029da08096853b738", size = 4457425, upload-time = "2026-03-25T23:33:38.904Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/5c/f6c3596a1430cec6f949085f0e1a970638d76f81c3ea56d93d564d04c340/cryptography-46.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2b417edbe8877cda9022dde3a008e2deb50be9c407eef034aeeb3a8b11d9db3c", size = 4405530, upload-time = "2026-03-25T23:33:40.842Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/c9/9f9cea13ee2dbde070424e0c4f621c091a91ffcc504ffea5e74f0e1daeff/cryptography-46.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:380343e0653b1c9d7e1f55b52aaa2dbb2fdf2730088d48c43ca1c7c0abb7cc2f", size = 4667896, upload-time = "2026-03-25T23:33:42.781Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/b5/1895bc0821226f129bc74d00eccfc6a5969e2028f8617c09790bf89c185e/cryptography-46.0.6-cp311-abi3-win32.whl", hash = "sha256:bcb87663e1f7b075e48c3be3ecb5f0b46c8fc50b50a97cf264e7f60242dca3f2", size = 3026348, upload-time = "2026-03-25T23:33:45.021Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f8/c9bcbf0d3e6ad288b9d9aa0b1dee04b063d19e8c4f871855a03ab3a297ab/cryptography-46.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:6739d56300662c468fddb0e5e291f9b4d084bead381667b9e654c7dd81705124", size = 3483896, upload-time = "2026-03-25T23:33:46.649Z" },
+ { url = "https://files.pythonhosted.org/packages/01/41/3a578f7fd5c70611c0aacba52cd13cb364a5dee895a5c1d467208a9380b0/cryptography-46.0.6-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:2ef9e69886cbb137c2aef9772c2e7138dc581fad4fcbcf13cc181eb5a3ab6275", size = 7117147, upload-time = "2026-03-25T23:33:48.249Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/87/887f35a6fca9dde90cad08e0de0c89263a8e59b2d2ff904fd9fcd8025b6f/cryptography-46.0.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7f417f034f91dcec1cb6c5c35b07cdbb2ef262557f701b4ecd803ee8cefed4f4", size = 4266221, upload-time = "2026-03-25T23:33:49.874Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/a8/0a90c4f0b0871e0e3d1ed126aed101328a8a57fd9fd17f00fb67e82a51ca/cryptography-46.0.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d24c13369e856b94892a89ddf70b332e0b70ad4a5c43cf3e9cb71d6d7ffa1f7b", size = 4408952, upload-time = "2026-03-25T23:33:52.128Z" },
+ { url = "https://files.pythonhosted.org/packages/16/0b/b239701eb946523e4e9f329336e4ff32b1247e109cbab32d1a7b61da8ed7/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:aad75154a7ac9039936d50cf431719a2f8d4ed3d3c277ac03f3339ded1a5e707", size = 4270141, upload-time = "2026-03-25T23:33:54.11Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/a8/976acdd4f0f30df7b25605f4b9d3d89295351665c2091d18224f7ad5cdbf/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3c21d92ed15e9cfc6eb64c1f5a0326db22ca9c2566ca46d845119b45b4400361", size = 4904178, upload-time = "2026-03-25T23:33:55.725Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/1b/bf0e01a88efd0e59679b69f42d4afd5bced8700bb5e80617b2d63a3741af/cryptography-46.0.6-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:4668298aef7cddeaf5c6ecc244c2302a2b8e40f384255505c22875eebb47888b", size = 4441812, upload-time = "2026-03-25T23:33:57.364Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/8b/11df86de2ea389c65aa1806f331cae145f2ed18011f30234cc10ca253de8/cryptography-46.0.6-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8ce35b77aaf02f3b59c90b2c8a05c73bac12cea5b4e8f3fbece1f5fddea5f0ca", size = 3963923, upload-time = "2026-03-25T23:33:59.361Z" },
+ { url = "https://files.pythonhosted.org/packages/91/e0/207fb177c3a9ef6a8108f234208c3e9e76a6aa8cf20d51932916bd43bda0/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c89eb37fae9216985d8734c1afd172ba4927f5a05cfd9bf0e4863c6d5465b013", size = 4269695, upload-time = "2026-03-25T23:34:00.909Z" },
+ { url = "https://files.pythonhosted.org/packages/21/5e/19f3260ed1e95bced52ace7501fabcd266df67077eeb382b79c81729d2d3/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:ed418c37d095aeddf5336898a132fba01091f0ac5844e3e8018506f014b6d2c4", size = 4869785, upload-time = "2026-03-25T23:34:02.796Z" },
+ { url = "https://files.pythonhosted.org/packages/10/38/cd7864d79aa1d92ef6f1a584281433419b955ad5a5ba8d1eb6c872165bcb/cryptography-46.0.6-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:69cf0056d6947edc6e6760e5f17afe4bea06b56a9ac8a06de9d2bd6b532d4f3a", size = 4441404, upload-time = "2026-03-25T23:34:04.35Z" },
+ { url = "https://files.pythonhosted.org/packages/09/0a/4fe7a8d25fed74419f91835cf5829ade6408fd1963c9eae9c4bce390ecbb/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e7304c4f4e9490e11efe56af6713983460ee0780f16c63f219984dab3af9d2d", size = 4397549, upload-time = "2026-03-25T23:34:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/a0/7d738944eac6513cd60a8da98b65951f4a3b279b93479a7e8926d9cd730b/cryptography-46.0.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b928a3ca837c77a10e81a814a693f2295200adb3352395fad024559b7be7a736", size = 4651874, upload-time = "2026-03-25T23:34:07.916Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/f1/c2326781ca05208845efca38bf714f76939ae446cd492d7613808badedf1/cryptography-46.0.6-cp314-cp314t-win32.whl", hash = "sha256:97c8115b27e19e592a05c45d0dd89c57f81f841cc9880e353e0d3bf25b2139ed", size = 3001511, upload-time = "2026-03-25T23:34:09.892Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/57/fe4a23eb549ac9d903bd4698ffda13383808ef0876cc912bcb2838799ece/cryptography-46.0.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c797e2517cb7880f8297e2c0f43bb910e91381339336f75d2c1c2cbf811b70b4", size = 3471692, upload-time = "2026-03-25T23:34:11.613Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/cc/f330e982852403da79008552de9906804568ae9230da8432f7496ce02b71/cryptography-46.0.6-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:12cae594e9473bca1a7aceb90536060643128bb274fcea0fc459ab90f7d1ae7a", size = 7162776, upload-time = "2026-03-25T23:34:13.308Z" },
+ { url = "https://files.pythonhosted.org/packages/49/b3/dc27efd8dcc4bff583b3f01d4a3943cd8b5821777a58b3a6a5f054d61b79/cryptography-46.0.6-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:639301950939d844a9e1c4464d7e07f902fe9a7f6b215bb0d4f28584729935d8", size = 4270529, upload-time = "2026-03-25T23:34:15.019Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/05/e8d0e6eb4f0d83365b3cb0e00eb3c484f7348db0266652ccd84632a3d58d/cryptography-46.0.6-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ed3775295fb91f70b4027aeba878d79b3e55c0b3e97eaa4de71f8f23a9f2eb77", size = 4414827, upload-time = "2026-03-25T23:34:16.604Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/97/daba0f5d2dc6d855e2dcb70733c812558a7977a55dd4a6722756628c44d1/cryptography-46.0.6-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8927ccfbe967c7df312ade694f987e7e9e22b2425976ddbf28271d7e58845290", size = 4271265, upload-time = "2026-03-25T23:34:18.586Z" },
+ { url = "https://files.pythonhosted.org/packages/89/06/fe1fce39a37ac452e58d04b43b0855261dac320a2ebf8f5260dd55b201a9/cryptography-46.0.6-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b12c6b1e1651e42ab5de8b1e00dc3b6354fdfd778e7fa60541ddacc27cd21410", size = 4916800, upload-time = "2026-03-25T23:34:20.561Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/8a/b14f3101fe9c3592603339eb5d94046c3ce5f7fc76d6512a2d40efd9724e/cryptography-46.0.6-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:063b67749f338ca9c5a0b7fe438a52c25f9526b851e24e6c9310e7195aad3b4d", size = 4448771, upload-time = "2026-03-25T23:34:22.406Z" },
+ { url = "https://files.pythonhosted.org/packages/01/b3/0796998056a66d1973fd52ee89dc1bb3b6581960a91ad4ac705f182d398f/cryptography-46.0.6-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:02fad249cb0e090b574e30b276a3da6a149e04ee2f049725b1f69e7b8351ec70", size = 3978333, upload-time = "2026-03-25T23:34:24.281Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/3d/db200af5a4ffd08918cd55c08399dc6c9c50b0bc72c00a3246e099d3a849/cryptography-46.0.6-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:7e6142674f2a9291463e5e150090b95a8519b2fb6e6aaec8917dd8d094ce750d", size = 4271069, upload-time = "2026-03-25T23:34:25.895Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/18/61acfd5b414309d74ee838be321c636fe71815436f53c9f0334bf19064fa/cryptography-46.0.6-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:456b3215172aeefb9284550b162801d62f5f264a081049a3e94307fe20792cfa", size = 4878358, upload-time = "2026-03-25T23:34:27.67Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/65/5bf43286d566f8171917cae23ac6add941654ccf085d739195a4eacf1674/cryptography-46.0.6-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:341359d6c9e68834e204ceaf25936dffeafea3829ab80e9503860dcc4f4dac58", size = 4448061, upload-time = "2026-03-25T23:34:29.375Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/25/7e49c0fa7205cf3597e525d156a6bce5b5c9de1fd7e8cb01120e459f205a/cryptography-46.0.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9a9c42a2723999a710445bc0d974e345c32adfd8d2fac6d8a251fa829ad31cfb", size = 4399103, upload-time = "2026-03-25T23:34:32.036Z" },
+ { url = "https://files.pythonhosted.org/packages/44/46/466269e833f1c4718d6cd496ffe20c56c9c8d013486ff66b4f69c302a68d/cryptography-46.0.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6617f67b1606dfd9fe4dbfa354a9508d4a6d37afe30306fe6c101b7ce3274b72", size = 4659255, upload-time = "2026-03-25T23:34:33.679Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/09/ddc5f630cc32287d2c953fc5d32705e63ec73e37308e5120955316f53827/cryptography-46.0.6-cp38-abi3-win32.whl", hash = "sha256:7f6690b6c55e9c5332c0b59b9c8a3fb232ebf059094c17f9019a51e9827df91c", size = 3010660, upload-time = "2026-03-25T23:34:35.418Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/82/ca4893968aeb2709aacfb57a30dec6fa2ab25b10fa9f064b8882ce33f599/cryptography-46.0.6-cp38-abi3-win_amd64.whl", hash = "sha256:79e865c642cfc5c0b3eb12af83c35c5aeff4fa5c672dc28c43721c2c9fdd2f0f", size = 3471160, upload-time = "2026-03-25T23:34:37.191Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/84/7ccff00ced5bac74b775ce0beb7d1be4e8637536b522b5df9b73ada42da2/cryptography-46.0.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:2ea0f37e9a9cf0df2952893ad145fd9627d326a59daec9b0802480fa3bcd2ead", size = 3475444, upload-time = "2026-03-25T23:34:38.944Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/1f/4c926f50df7749f000f20eede0c896769509895e2648db5da0ed55db711d/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a3e84d5ec9ba01f8fd03802b2147ba77f0c8f2617b2aff254cedd551844209c8", size = 4218227, upload-time = "2026-03-25T23:34:40.871Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/65/707be3ffbd5f786028665c3223e86e11c4cda86023adbc56bd72b1b6bab5/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:12f0fa16cc247b13c43d56d7b35287ff1569b5b1f4c5e87e92cc4fcc00cd10c0", size = 4381399, upload-time = "2026-03-25T23:34:42.609Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6d/73557ed0ef7d73d04d9aba745d2c8e95218213687ee5e76b7d236a5030fc/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:50575a76e2951fe7dbd1f56d181f8c5ceeeb075e9ff88e7ad997d2f42af06e7b", size = 4217595, upload-time = "2026-03-25T23:34:44.205Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/c5/e1594c4eec66a567c3ac4400008108a415808be2ce13dcb9a9045c92f1a0/cryptography-46.0.6-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:90e5f0a7b3be5f40c3a0a0eafb32c681d8d2c181fc2a1bdabe9b3f611d9f6b1a", size = 4380912, upload-time = "2026-03-25T23:34:46.328Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/89/843b53614b47f97fe1abc13f9a86efa5ec9e275292c457af1d4a60dc80e0/cryptography-46.0.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6728c49e3b2c180ef26f8e9f0a883a2c585638db64cf265b49c9ba10652d430e", size = 3409955, upload-time = "2026-03-25T23:34:48.465Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
+[[package]]
+name = "docutils"
+version = "0.22.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" },
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+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 = "google-auth"
+version = "2.49.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cryptography" },
+ { name = "pyasn1-modules" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ea/80/6a696a07d3d3b0a92488933532f03dbefa4a24ab80fb231395b9a2a1be77/google_auth-2.49.1.tar.gz", hash = "sha256:16d40da1c3c5a0533f57d268fe72e0ebb0ae1cc3b567024122651c045d879b64", size = 333825, upload-time = "2026-03-12T19:30:58.135Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/eb/c6c2478d8a8d633460be40e2a8a6f8f429171997a35a96f81d3b680dec83/google_auth-2.49.1-py3-none-any.whl", hash = "sha256:195ebe3dca18eddd1b3db5edc5189b76c13e96f29e73043b923ebcf3f1a860f7", size = 240737, upload-time = "2026-03-12T19:30:53.159Z" },
+]
+
+[package.optional-dependencies]
+requests = [
+ { name = "requests" },
+]
+
+[[package]]
+name = "google-genai"
+version = "1.69.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "google-auth", extra = ["requests"] },
+ { name = "httpx" },
+ { name = "pydantic" },
+ { name = "requests" },
+ { name = "sniffio" },
+ { name = "tenacity" },
+ { name = "typing-extensions" },
+ { name = "websockets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/00/5e/c0a5e6ff60d18d3f19819a9b1fbd6a1ef2162d025696d8660550739168dc/google_genai-1.69.0.tar.gz", hash = "sha256:5f1a6a478e0c5851506a3d337534bab27b3c33120e27bf9174507ea79dfb8673", size = 519538, upload-time = "2026-03-28T15:33:27.308Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/42/58/ef0586019f54b2ebb36deed7608ccb5efe1377564d2aaea6b1e295d1fadc/google_genai-1.69.0-py3-none-any.whl", hash = "sha256:252e714d724aba74949647b9de511a6a6f7804b3b317ab39ddee9cc2f001cacc", size = 760551, upload-time = "2026-03-28T15:33:24.957Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "itsdangerous"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
+]
+
+[[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 = "linkify-it-py"
+version = "2.0.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "uc-micro-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2a/ae/bb56c6828e4797ba5a4821eec7c43b8bf40f69cda4d4f5f8c8a2810ec96a/linkify-it-py-2.0.3.tar.gz", hash = "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", size = 27946, upload-time = "2024-02-04T14:48:04.179Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/1e/b832de447dee8b582cac175871d2f6c3d5077cc56d5575cadba1fd1cccfa/linkify_it_py-2.0.3-py3-none-any.whl", hash = "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79", size = 19820, upload-time = "2024-02-04T14:48:02.496Z" },
+]
+
+[[package]]
+name = "loro"
+version = "1.10.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/27/ea6f3298fc87ea5f2d60ebfbca088e7d9b2ceb3993f67c83bfb81778ec01/loro-1.10.3.tar.gz", hash = "sha256:68184ab1c2ab94af6ad4aaba416d22f579cabee0b26cbb09a1f67858207bbce8", size = 68833, upload-time = "2025-12-09T10:14:06.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/af/517956be7153d3450263f35ca70b1d7845b404e197045274db07b869e26f/loro-1.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7e7e3461439c57efaadfd364a5a504a849653cf408c97086033004dffb3f2857", size = 3258650, upload-time = "2025-12-09T10:11:29.657Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/a4/8a44499630922af97359971ab01738f568319cbfa5045830eda7393cc758/loro-1.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed91dae34236f888c357b367d37b050ac4fa21ff30ab0231122f580ca87f46ba", size = 3061526, upload-time = "2025-12-09T10:11:14.823Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/93/2088ca72f21fbf59bd31a847a6fd989038dcf4179166e829631482410336/loro-1.10.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5d417a99bae161ecb1250f3272a80c87f2ae546dfb705cadac3ebbc623b7382", size = 3287817, upload-time = "2025-12-09T10:08:11.002Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/72/136fbb2077a0fc92f97e94dc88f48bf515fab034b218d007afcede08eed5/loro-1.10.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a9b821925c9051ee2653a519a99b1d2fc1177a4bac1f02b1f8eaec491f6d43b", size = 3349471, upload-time = "2025-12-09T10:08:45.441Z" },
+ { url = "https://files.pythonhosted.org/packages/91/ab/6b484590ffcb2997a5f163ff26641c8ea9738cacb883f4aa3669dd720433/loro-1.10.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ee8982a6b82660165e516932cda0e5fd7065023f35ae5e2d17562cf14969e87", size = 3708083, upload-time = "2025-12-09T10:09:23.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/7f/b44b0a6228d8f2aad70d8d93c4dc29d72ff4da223cd054c56dbdde9cada5/loro-1.10.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd391a27550dcf837c82d8ae4e420b4d3b16bdc5a698c3862540803a16bf52dd", size = 3416777, upload-time = "2025-12-09T10:09:57.794Z" },
+ { url = "https://files.pythonhosted.org/packages/53/ad/df58cc6c7168fa4859ba16a447131a0212a07b68fa0250898be132fef365/loro-1.10.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e74235d480c6e9b362c6f2265a7d28dd848e6a6142a3c9d0831b82cf3776efee", size = 3347414, upload-time = "2025-12-09T10:10:51.95Z" },
+ { url = "https://files.pythonhosted.org/packages/78/90/3d5bb124d4d333824779fd09b25026876b9670c09e5a384760abc7bc863a/loro-1.10.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df7baf726db4e82f411f7a0454500047812f41bef9552109cb738b8f6ee89c9f", size = 3688343, upload-time = "2025-12-09T10:10:30.393Z" },
+ { url = "https://files.pythonhosted.org/packages/74/01/c78b11ef4ecdbffb1236cdf2f010f89b4a9ad77554e67513aa88cd2280f4/loro-1.10.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:112d5eeaf76ca6dfbe811e6f6d18649ceeb7697626288ed1185bd1a7d4aae182", size = 3468739, upload-time = "2025-12-09T10:11:46.654Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/26/27123477c458c7e2f26da58d346efab87bb1dbf8f082ed3663cdb8b87581/loro-1.10.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a2cbc231a07f11b82099b76386b1e5659687f4415d6f111699bbd4f291c945a4", size = 3618995, upload-time = "2025-12-09T10:12:22.466Z" },
+ { url = "https://files.pythonhosted.org/packages/15/de/41d21b38d55685715ae6dd7c390dcd29521669ee7e7b8246e6cec71f480d/loro-1.10.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dbf31ae00bae9c76a4429f73cec3fb3000f1b4d41603244793c660e17747ce1f", size = 3666508, upload-time = "2025-12-09T10:12:57.538Z" },
+ { url = "https://files.pythonhosted.org/packages/38/94/4a8016e5d6400994a82834369aabfaa40cfb62b1f8f40c17bfc3e76ecff7/loro-1.10.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:98d8855a94e2123dab0e40fb5ac7760edbb9b87cd4b29608327899874721ed0b", size = 3558656, upload-time = "2025-12-09T10:13:32.685Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/f7/85bb7f6c953b078d74bbb0ec9bb161482c27dde49ed979ddea55c40aafd8/loro-1.10.3-cp310-cp310-win32.whl", hash = "sha256:b539f86cf5e44ad7eefd05772ec637985fddd31137deadca508cd8f3bad211a9", size = 2722340, upload-time = "2025-12-09T10:14:25.47Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/94/d7ef82e9698671f7529ba56b447b546312edcb40dadd4c71af25ea499033/loro-1.10.3-cp310-cp310-win_amd64.whl", hash = "sha256:a5da9963be9a323424695c04d9be836577705077a359d1bb4cabd43963ed2600", size = 2952931, upload-time = "2025-12-09T10:14:07.521Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/bb/61f36aac7981f84ffba922ac1220505365df3e064bc91c015790bff92007/loro-1.10.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7ee0e1c9a6d0e4a1df4f1847d3b31cef8088860c1193442f131936d084bd3fe1", size = 3254532, upload-time = "2025-12-09T10:11:31.215Z" },
+ { url = "https://files.pythonhosted.org/packages/15/28/5708da252eb6be90131338b104e5030c9b815c41f9e97647391206bec092/loro-1.10.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7225471b29a892a10589d7cf59c70b0e4de502fa20da675e9aaa1060c7703ae", size = 3055231, upload-time = "2025-12-09T10:11:16.111Z" },
+ { url = "https://files.pythonhosted.org/packages/16/b6/68c350a39fd96f24c55221f883230aa83db0bb5f5d8e9776ccdb25ea1f7b/loro-1.10.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc04a714e0a604e191279501fa4d2db3b39cee112275f31e87d95ecfbafdfb6c", size = 3286945, upload-time = "2025-12-09T10:08:12.633Z" },
+ { url = "https://files.pythonhosted.org/packages/23/af/8245b8a20046423e035cd17de9811ab1b27fc9e73425394c34387b41cc13/loro-1.10.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:375c888a4ddf758b034eb6ebd093348547d17364fae72aa7459d1358e4843b1f", size = 3349533, upload-time = "2025-12-09T10:08:46.754Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/8c/d764c60914e45a2b8c562e01792172e3991430103c019cc129d56c24c868/loro-1.10.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2020d9384a426e91a7d38c9d0befd42e8ad40557892ed50d47aad79f8d92b654", size = 3704622, upload-time = "2025-12-09T10:09:25.068Z" },
+ { url = "https://files.pythonhosted.org/packages/54/cc/ebdbdf0b1c7a223fe84fc0de78678904ed6424b426f90b98503b95b1dff9/loro-1.10.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95afacd832dce152700c2bc643f7feb27d5611fc97b5141684b5831b22845380", size = 3416659, upload-time = "2025-12-09T10:09:59.107Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/bc/db7f3fc619483b60c03d85b4f9bb5812b2229865b574c8802b46a578f545/loro-1.10.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c95868bcf6361d700e215f33a88b8f51d7bc3ae7bbe3d35998148932e23d3fa", size = 3345007, upload-time = "2025-12-09T10:10:53.327Z" },
+ { url = "https://files.pythonhosted.org/packages/91/65/bcd3b1d3a3615e679177c1256f2e0ff7ee242c3d5d1b9cb725b0ec165b51/loro-1.10.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68f5c7fad09d8937ef4b55e7dd4a0f9f175f026369b3f55a5b054d3513f6846d", size = 3687874, upload-time = "2025-12-09T10:10:31.674Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/e4/0d51e2da2ae6143bfd03f7127b9daf58a3f8dae9d5ca7740ccba63a04de4/loro-1.10.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:740bb548139d71eccd6317f3df40a0dc5312e98bbb2be09a6e4aaddcaf764206", size = 3467200, upload-time = "2025-12-09T10:11:47.994Z" },
+ { url = "https://files.pythonhosted.org/packages/06/99/ada2baeaf6496e34962fe350cd41129e583219bf4ce5e680c37baa0613a8/loro-1.10.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c756a6ee37ed851e9cf91e5fedbc68ca21e05969c4e2ec6531c15419a4649b58", size = 3618468, upload-time = "2025-12-09T10:12:24.182Z" },
+ { url = "https://files.pythonhosted.org/packages/87/ec/83335935959c5e3946e02b748af71d801412b2aa3876f870beae1cd56d4d/loro-1.10.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3553390518e188c055b56bcbae76bf038329f9c3458cb1d69068c55b3f8f49f1", size = 3666852, upload-time = "2025-12-09T10:12:59.117Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/53/1bd455b3254afa35638d617e06c65a22e604b1fae2f494abb9a621c8e69b/loro-1.10.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0885388c0c2b53f5140229921bd64c7838827e3101a05d4d53346191ba76b15d", size = 3556829, upload-time = "2025-12-09T10:13:34.002Z" },
+ { url = "https://files.pythonhosted.org/packages/66/30/6f48726ef50f911751c6b69d7fa81482cac70d4ed817216f846776fec28c/loro-1.10.3-cp311-cp311-win32.whl", hash = "sha256:764b68c4ff0411399c9cf936d8b6db1161ec445388ff2944a25bbdeb2bbac15c", size = 2723776, upload-time = "2025-12-09T10:14:27.261Z" },
+ { url = "https://files.pythonhosted.org/packages/69/39/0b08203d94a6f200bbfefa8025a1b825c8cfb30e8cc8b2a1224629150d08/loro-1.10.3-cp311-cp311-win_amd64.whl", hash = "sha256:9e583e6aabd6f9b2bdf3ff3f6e0de10c3f7f8ab9d4c05c01a9ecca309c969017", size = 2950529, upload-time = "2025-12-09T10:14:08.857Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/b6/cfbf8088e8ca07d66e6c1eccde42e00bd61708f28e8ea0936f9582306323/loro-1.10.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:028948b48dcc5c2127f974dae4ad466ab69f0d1eeaf367a8145eb6501fb988f2", size = 3239592, upload-time = "2025-12-09T10:11:32.505Z" },
+ { url = "https://files.pythonhosted.org/packages/78/e4/7b614260bf16c5e33c0bea6ac47ab0284efd21f89f2e5e4e15cd93bead40/loro-1.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5253b8f436d90412b373c583f22ac9539cfb495bf88f78d4bb41daafef0830b7", size = 3045107, upload-time = "2025-12-09T10:11:17.481Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/17/0a78ec341ca69d376629ff2a1b9b3511ee7dd54f2b018616ef03328024f7/loro-1.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14be8a5539d49468c94d65742355dbe79745123d78bf769a23e53bf9b60dd46a", size = 3292720, upload-time = "2025-12-09T10:08:14.027Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/9b/f36a4654508e9b8ddbe08a62a0ce8b8e7fd511a39b161821917530cffd8e/loro-1.10.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91b2b9139dfc5314a0197132a53b6673fddb63738380a522d12a05cec7ad76b4", size = 3353260, upload-time = "2025-12-09T10:08:48.251Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/0e/7d441ddecc7695153dbe68af4067d62e8d7607fce3747a184878456a91f6/loro-1.10.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:247897288911c712ee7746965573299fc23ce091e94456da8da371e6adae30f4", size = 3712354, upload-time = "2025-12-09T10:09:26.38Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/33/10e66bb84599e61df124f76c00c5398eb59cbb6f69755f81c40f65a18344/loro-1.10.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:835abc6025eb5b6a0fe22c808472affc95e9a661b212400cfd88ba186b0d304c", size = 3422926, upload-time = "2025-12-09T10:10:00.347Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/70/00dc4246d9f3c69ecbb9bc36d5ad1a359884464a44711c665cb0afb1e9de/loro-1.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e660853617fc29e71bb7b796e6f2c21f7722c215f593a89e95cd4d8d5a32aca0", size = 3353092, upload-time = "2025-12-09T10:10:55.786Z" },
+ { url = "https://files.pythonhosted.org/packages/19/37/60cc0353c5702e1e469b5d49d1762e782af5d5bd5e7c4e8c47556335b4c6/loro-1.10.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8059063cab57ca521012ed315a454784c20b0a86653e9014795e804e0a333659", size = 3687798, upload-time = "2025-12-09T10:10:33.253Z" },
+ { url = "https://files.pythonhosted.org/packages/88/c4/4db1887eb08dfbb305d9424fdf1004c0edf147fd53ab0aaf64a90450567a/loro-1.10.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9748359343b5fd7019ab3c2d1d583a0c13c633a4dd21d75e50e3815ab479f493", size = 3474451, upload-time = "2025-12-09T10:11:49.489Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/66/10d2e00c43b05f56e96e62100f86a1261f8bbd6422605907f118a752fe61/loro-1.10.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:def7c9c2e16ad5470c9c56f096ac649dd4cd42d5936a32bb0817509a92d82467", size = 3621647, upload-time = "2025-12-09T10:12:25.536Z" },
+ { url = "https://files.pythonhosted.org/packages/47/f0/ef8cd6654b09a03684195c650b1fba00f42791fa4844ea400d94030c5615/loro-1.10.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:34b223fab58591a823f439d9a13d1a1ddac18dc4316866503c588ae8a9147cb1", size = 3667946, upload-time = "2025-12-09T10:13:00.711Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/5d/960b62bf85c38d6098ea067438f037a761958f3a17ba674db0cf316b0f60/loro-1.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d5fa4baceb248d771897b76d1426c7656176e82e770f6790940bc3e3812436d", size = 3565866, upload-time = "2025-12-09T10:13:35.401Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/d4/0d499a5e00df13ce497263aef2494d9de9e9d1f11d8ab68f89328203befb/loro-1.10.3-cp312-cp312-win32.whl", hash = "sha256:f25ab769b84a5fbeb1f9a1111f5d28927eaeaa8f5d2d871e237f80eaca5c684e", size = 2720785, upload-time = "2025-12-09T10:14:28.79Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/9b/2b5be23f1da4cf20c6ce213cfffc66bdab2ea012595abc9e3383103793d0/loro-1.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:3b73b7a3a32e60c3424fc7deaf8b127af7580948e27d8bbe749e3f43508aa0a2", size = 2954650, upload-time = "2025-12-09T10:14:10.235Z" },
+ { url = "https://files.pythonhosted.org/packages/75/67/8467cc1c119149ada86903b67ce10fc4b47fb6eb2a8ca5f94c0938fd010f/loro-1.10.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:380ef692c5272e8b607be2ee6a8eef5113e65dc38e6739526c30e3db6abc3fbc", size = 3239527, upload-time = "2025-12-09T10:11:33.884Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/3b/d1a01af3446cb98890349215bea7e71ba49dc3e50ffbfb90c5649657a8b8/loro-1.10.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed966ce6ff1fb3787b3f6c4ed6dd036baa5fb738b84a466a5e764f2ab534ccc2", size = 3044767, upload-time = "2025-12-09T10:11:18.777Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/93/37f891fa46767001ae2518697fb01fc187497e3a5238fe28102be626055d/loro-1.10.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d7c8d2f3d88578fdf69845a9ae16fc5ea3ac54aa838a6bf43a24ce11908220", size = 3292648, upload-time = "2025-12-09T10:08:15.404Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/67/82273eeba2416b0410595071eda1eefcdf4072c014d44d2501b660aa7145/loro-1.10.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62283c345bfeedef19c8a6d029cd8830e5d2c20b5fb45975d8a70a8a30a7944b", size = 3353181, upload-time = "2025-12-09T10:08:50.144Z" },
+ { url = "https://files.pythonhosted.org/packages/82/33/894dccf132bece82168dfbe61fad25a13ed89d18f20649f99e87c38f9228/loro-1.10.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e7e6ae091179fa5f0fca1f8612fde20236ee0a678744bf51ff7d26103ea04f", size = 3712583, upload-time = "2025-12-09T10:09:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/99292729d8b271bcc4bff5faa20b33e4c749173af4c9cb9d34880ae3b4c8/loro-1.10.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6abc6de4876aa205498cef52a002bc38662fbd8d742351ea0f535479208b8b1c", size = 3421491, upload-time = "2025-12-09T10:10:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/fb/188b808ef1d9b6d842d53969b99a16afb1b71f04739150959c8946345d0e/loro-1.10.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acbbfd24cf28a71bbdad8544852e9bbba0ba8535f8221f8859b2693555fa8356", size = 3352623, upload-time = "2025-12-09T10:10:57.361Z" },
+ { url = "https://files.pythonhosted.org/packages/53/cc/e2d008cc24bddcf05d1a15b8907a73b1731921ab40897f73a3385fdd274a/loro-1.10.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5faf4ebbe8ca39605024f16dbbbde354365f4e2dcfda82c753797461b504bbd3", size = 3687687, upload-time = "2025-12-09T10:10:34.453Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/b6/4251822674230027103caa4fd46a1e83c4d676500074e7ab297468bf8f40/loro-1.10.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e049c21b292c4ff992b23a98812840735db84620721c10ae7f047a921202d090", size = 3474316, upload-time = "2025-12-09T10:11:51.207Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/54/ecff3ec08d814f3b9ec1c78a14ecf2e7ff132a71b8520f6aa6ad1ace0056/loro-1.10.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:20e8dacfb827c1f7ffb73e127029d7995a9ab2c3b7b7bc3ecc91d22ee32d78d0", size = 3622069, upload-time = "2025-12-09T10:12:27.059Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/84/c1b8251000f46df5f4d043af8c711bdbff9818727d26429378e0f3a5115e/loro-1.10.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1b743c1c4f93f5b4f0e12efbb352d26e9f80bcbf20f45d9c70f3d0b522f42060", size = 3667722, upload-time = "2025-12-09T10:13:02.012Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/13/c5c02776f4ad52c6361b95e1d7396c29071533cef45e3861a2e35745be27/loro-1.10.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:446d67bc9e28036a5a5e03526d28a1559ef2a47b3ccad6b07820dae123cc3697", size = 3564952, upload-time = "2025-12-09T10:13:37.227Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/f1/63d4bc63a1521a9b577f6d13538ec4790865584fdf87569d5af943792406/loro-1.10.3-cp313-cp313-win32.whl", hash = "sha256:45d7d8ec683599897695bb714771baccabc1b4c4a412283cc39787c7a59f7ff0", size = 2720952, upload-time = "2025-12-09T10:14:30.17Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3c/65c8b0b7f96c9b4fbd458867cf91f30fcd58ac25449d8ba9303586061671/loro-1.10.3-cp313-cp313-win_amd64.whl", hash = "sha256:a42bf73b99b07fed11b65feb0a5362b33b19de098f2235848687f4c41204830e", size = 2953768, upload-time = "2025-12-09T10:14:11.965Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/e9/f6a242f61aa4d8b56bd11fa467be27d416401d89cc3244b58651a3a44c88/loro-1.10.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4866325b154aeebcd34be106c7597acf150c374481ac3c12035a1af715ac0f01", size = 3289791, upload-time = "2025-12-09T10:08:16.926Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/81/8f5f4d6805658c654264e99467f3f46facdbb2062cbf86743768ee4b942a/loro-1.10.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ea7b8849660a28ce8cd90a82db4f76c23453836fcbc88f5767feaaf8739045e2", size = 3348007, upload-time = "2025-12-09T10:08:53.305Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/15/bba0fad18ec5561a140e9781fd2b38672210b52e847d207c57ae85379efd/loro-1.10.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e82cdaf9a5892557d3167e07ed5093f87dfa31ef860a63b0eac6c0c2f435705", size = 3707937, upload-time = "2025-12-09T10:09:29.165Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b2/5519c92bd4f9cde068dc60ba35d7f3e4f8cce41e7bf39febd4fb08908e97/loro-1.10.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7ee99e5dc844fb20fca830906a0d721022ad1c37aad0b1a440c4ecb98d0c02f", size = 3416744, upload-time = "2025-12-09T10:10:02.956Z" },
+ { url = "https://files.pythonhosted.org/packages/81/ba/92d97c27582c0ce12bb83df19b9e080c0dfe95068966296a4fa2279c0477/loro-1.10.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:153c297672ad98d0fe6ff8985decf1e64528ad1dd01ae1452bb83bdeb31f858f", size = 3470978, upload-time = "2025-12-09T10:11:52.707Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/8b/acb39b0e74af1c317d3121e75a4bc5bc77d7fda5a79c60399746486f60d9/loro-1.10.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:0ed72f8c6a5f521252ee726954055339abba3fcf00404fb4b5c2da168f0cce79", size = 3615039, upload-time = "2025-12-09T10:12:28.631Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/c3/154e3361e5ef42012f6842dbd93f8fbace6eec06517b5a4a9f8c4a46e873/loro-1.10.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f612ab17acdac16c0139e63ff45b33175ebfb22e61a60eb7929a4583389348d6", size = 3663731, upload-time = "2025-12-09T10:13:03.557Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/dd/a283cf5b1c957e0bbc67503a10e17606a8f8c87f51d3cf3d83dc3a0ac88a/loro-1.10.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f2741db05c79f3618c954bac90f4572d28c01c243884453f379e9a8738f93d81", size = 3558807, upload-time = "2025-12-09T10:13:38.926Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/4a/a5340b6fdf4cd34d758bed23bd1f64063b3b1b41ff4ecc94ee39259ee9a7/loro-1.10.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:623cf7df17626aa55bc6ca54e89177dbe71a5f1c293e102d6153f43991a1a041", size = 3213589, upload-time = "2025-12-09T10:11:35.377Z" },
+ { url = "https://files.pythonhosted.org/packages/00/93/5164e93a77e365a92def77c1258386daef233516a29fb674a3b9d973b8b8/loro-1.10.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d8e715d475f32a1462969aca27eeb3f998f309182978f55bc37ce5c515d92e90", size = 3029557, upload-time = "2025-12-09T10:11:20.076Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/30/94592d7c01f480ce99e1783b0d9203eb20ba2eab42575dabd384e3c9d1fa/loro-1.10.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e012a80e8c9fe248b9d0a76e91664c9479a72d976eaeed78f87b15b5d1d732", size = 3282335, upload-time = "2025-12-09T10:08:18.168Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/a8/7ae3c0b955aa638fa7dbd2d194c7759749a0d0d96a94805d5dec9b30eaea/loro-1.10.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:686ece56756acbaf80c986848915e9126a29a06d7a62209747e3ef1efc0bd8f6", size = 3333071, upload-time = "2025-12-09T10:08:55.314Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/10/151edebdb2bca626ad50911b761164ced16984b25b0b37b34b674ded8b29/loro-1.10.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aa821c8871deca98f4605eb0c40fb26bcf82bd29c9e7fa33b183516c5395b11", size = 3698226, upload-time = "2025-12-09T10:09:30.474Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/ac/02a490e38466506b1003df4910d2a8ae582265023dae9e2217c98b56ea3f/loro-1.10.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:507d34137adb4148f79e1da7f89a21a4aab18565621a5dc2b389773fe98ac25b", size = 3407322, upload-time = "2025-12-09T10:10:04.199Z" },
+ { url = "https://files.pythonhosted.org/packages/81/db/da51f2bcad81ca3733bc21e83f3b6752446436b565b90f5c350ad227ad01/loro-1.10.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91d3b2e187ccfe2b14118a6e5617266fedcdf3435f6fa0a3db7b4afce8afa687", size = 3330268, upload-time = "2025-12-09T10:10:58.61Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/af/50d136c83d504a3a1f4ad33a6bf38b6933985a82741302255cf446a5f7ad/loro-1.10.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0016f834fd1626710081334400aed8494380b55ef131f7133d21c3bd22d892a", size = 3673582, upload-time = "2025-12-09T10:10:35.849Z" },
+ { url = "https://files.pythonhosted.org/packages/63/4d/53288aae777218e05c43af9c080652bcdbbc8d97c031607eedd3fc15617d/loro-1.10.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:71c4275dca5a8a86219d60545d4f60e081b4af44b490ac912c0481906934bfc6", size = 3463731, upload-time = "2025-12-09T10:11:54.102Z" },
+ { url = "https://files.pythonhosted.org/packages/75/01/2389f26ffe8bc3ffe48a0a578f610dd49c709bbcf0d5d2642c6e2b52f490/loro-1.10.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:490f12571b2ed1a8eaf1edd3a7fffc55adac5010b1875fe1bb9e9af9a3907c38", size = 3602334, upload-time = "2025-12-09T10:12:30.082Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/16/07b64af13f5fcea025e003ca27bbd6f748217abbd4803dad88ea0900526c/loro-1.10.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a374a43cadaa48528a5411496481df9ae52bf01e513f4509e37d6c986f199c0e", size = 3657896, upload-time = "2025-12-09T10:13:04.86Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2f/4050770d7675ceced71651fe76971d5c27456b7098c0de03a4ecdbb0a02d/loro-1.10.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1a93b2ee59f1fa8d98dd552211fd5693551893b34c1dd2ba0324806d6d14022f", size = 3544339, upload-time = "2025-12-09T10:13:40.396Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/21/67e27cb404c968fc19a841d5c6277f13a17c69a56f49e3c15ea1c92a28eb/loro-1.10.3-cp314-cp314-win32.whl", hash = "sha256:baa863e3d869422e3320e822c0b1f87f5dc44cda903d1bd3b7a16f8413ce3d92", size = 2706731, upload-time = "2025-12-09T10:14:31.604Z" },
+ { url = "https://files.pythonhosted.org/packages/08/54/6770cf36aeb994489375e9ab9c01201e70ab7cc286fa97e907aa41b1bae6/loro-1.10.3-cp314-cp314-win_amd64.whl", hash = "sha256:f10ed3ca89485f942b8b2de796ed9783edb990e7e570605232de77489e9f3548", size = 2933563, upload-time = "2025-12-09T10:14:13.805Z" },
+ { url = "https://files.pythonhosted.org/packages/24/f5/eb089fd25eb428709dbe79fd4d36b82a00572aa54badd1dff62511a38fe3/loro-1.10.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b4d049efb1953aebfc16fa0b445ff5a37d4d08a1ab93f3b5a577a454b7a5ded", size = 3282369, upload-time = "2025-12-09T10:08:20.011Z" },
+ { url = "https://files.pythonhosted.org/packages/30/d7/692cb87c908f6a8af6cbfc10ebab69e16780e3796e11454c2b481b5c3817/loro-1.10.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56ecad7fbac58aa8bee52bb261a764aeef6c7b39c20f0d69e8fad908ab2ca7d8", size = 3332530, upload-time = "2025-12-09T10:08:57.07Z" },
+ { url = "https://files.pythonhosted.org/packages/54/46/ed3afbf749288b6f70f3b859a6762538818bf6a557ca873b07d6b036946b/loro-1.10.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d8d1be349d08b3a95592c6a17b80b1ea6aef892b1b8e2b93b540062d04e34e0", size = 3702599, upload-time = "2025-12-09T10:09:31.779Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/30/6cb616939c12bfe96a71a01a6e3551febf1c34bf9de114fafadbcfb65064/loro-1.10.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ec0a0b9bc4e32c46f14710062ec5b536c72110318aaf85632a4f8b37e9a470a", size = 3404412, upload-time = "2025-12-09T10:10:05.448Z" },
+ { url = "https://files.pythonhosted.org/packages/02/a2/3d4006d3333589f9158ac6d403979bf5c985be8b461b18e7a2ea23b05414/loro-1.10.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c5d4437987f7a4a4ff5927f39d0f43ded5b34295dfb0a3c8e150687e25c3d6b8", size = 3462948, upload-time = "2025-12-09T10:11:55.405Z" },
+ { url = "https://files.pythonhosted.org/packages/41/30/c640ccd3e570b08770a9f459decc2d8e7ceefdc34ac28a745418fb9cb5ba/loro-1.10.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:86d4f0c631ca274ad2fa2c0bdb8e1e141882d94339b7284a8bef5bf73fa6957d", size = 3599851, upload-time = "2025-12-09T10:12:31.759Z" },
+ { url = "https://files.pythonhosted.org/packages/59/8f/062ea50554c47ae30e98b1f0442a458c0edecc6d4edc7fcfc4d901734dd0/loro-1.10.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:15e03084ff1b472e14623183ed6e1e43e0f717c2112697beda5e69b5bd0ff236", size = 3655558, upload-time = "2025-12-09T10:13:06.529Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f5/c7dd8cdbd57454b23d89799c22cd42b6d2dda283cd87d7b198dc424a462c/loro-1.10.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42d6a5ce5bc518eaa682413e82d597299650eeb03e8bc39341752d6e0d22503e", size = 3541282, upload-time = "2025-12-09T10:13:42.189Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/12/0ec38fe0a1fa6b8e76989bbbbf22bdd34f8824ce6934c97f94ca50dba49c/loro-1.10.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55214615c1cb9f727a5278f5e57b9660743e7d095e08899e8936f174a45471b9", size = 3284859, upload-time = "2025-12-09T10:08:24.621Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/26/c01691a85fe1047dcc0398054124069af92b8ce1602eaabbe9b7e0fac1f1/loro-1.10.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10591fa32dc628f770da472beac7544d2ba16a3a22d590211364331c5871b9f6", size = 3349886, upload-time = "2025-12-09T10:09:01.286Z" },
+ { url = "https://files.pythonhosted.org/packages/53/35/3fcd13a2ae7686b467b5210b991e1682576a4be7e121bc9f8690c3d59929/loro-1.10.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f18df6892097603e5bd2e149384d4bcb996be8a3b6ba10d3da74bce39e1d5093", size = 3703226, upload-time = "2025-12-09T10:09:36.464Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/47/52ce515ac76893f57ed071bb1d5cd3687a059cf1e81e66535364b9581ed6/loro-1.10.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a8911b8cd97652a04e22481dd90b3c8d286f12c8d8286a4e34a655835dd6506", size = 3413121, upload-time = "2025-12-09T10:10:09.528Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/1c/39f39e731d3af9c387e4238bd8da8e545e16922524bc0bca991d3ce475e1/loro-1.10.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:73d5737c95bccf725950555c51374e5823c9be16bfc5496d8c1fafb2bb04690f", size = 3466280, upload-time = "2025-12-09T10:11:59.889Z" },
+ { url = "https://files.pythonhosted.org/packages/54/f9/b85b76b882f1e62da461552157b061dd79c52c59afd8074969f04fb32a2c/loro-1.10.3-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:fa875a691556daaedb639dc920ee9c3743745eea2aa4c7fd914841e31b92c556", size = 3617971, upload-time = "2025-12-09T10:12:36.704Z" },
+ { url = "https://files.pythonhosted.org/packages/90/1a/ef79aa94144453157bc139e341b983640fcda70bf2e8fdc6120773f210a0/loro-1.10.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e7ddfd247fa3ae3c05d38019fb1424a903ea98e5730a10105081f5f7dc08f9c1", size = 3663111, upload-time = "2025-12-09T10:13:11.173Z" },
+ { url = "https://files.pythonhosted.org/packages/96/b4/ca47f1b4b926a4b0dd3a7d7f0edd46a63e74b64c2b628c0463f25f0f1dd2/loro-1.10.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:033a456647d487d61af82ea96aff95a789a3776441ea8af86556f2877867530d", size = 3554651, upload-time = "2025-12-09T10:13:46.496Z" },
+ { url = "https://files.pythonhosted.org/packages/43/1a/49e864102721e0e15a4e4c56d7f2dddad5cd589c2d0aceafe14990513583/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16ca42e991589ea300b59da9e98940d5ddda76275fe4363b1f1e079d244403a1", size = 3284236, upload-time = "2025-12-09T10:08:25.836Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/c6/d46b433105d8002e4c90248c07f00cd2c8ea76f1048cc5f35b733be96723/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9ca16dae359397aa7772891bb3967939ffda8da26e0b392d331b506e16afc78", size = 3348996, upload-time = "2025-12-09T10:09:03.951Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/f3/e918c7b396c547b22a7ab3cff1b570c5ce94293f0dcb17cd96cbe6ba2d50/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d87cfc0a6e119c1c8cfa93078f5d012e557c6b75edcd0977da58ec46d28dc242", size = 3701875, upload-time = "2025-12-09T10:09:37.924Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/67/140ecb65b4f436099ad674fbe7502378156f43b737cb43f5fd76c42a0da8/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4541ed987306c51e718f51196fd2b2d05e87b323da5d850b37900d2e8ac6aae6", size = 3412283, upload-time = "2025-12-09T10:10:10.946Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/93/b7b41cf8b3e591b7191494e12be24cbb101f137fe82f0a24ed7934bbacf3/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce0b0a500e08b190038380d4593efcb33c98ed4282cc8347ca6ce55d05cbdf6e", size = 3340580, upload-time = "2025-12-09T10:11:02.956Z" },
+ { url = "https://files.pythonhosted.org/packages/94/19/fdc9ea9ce6510147460200c90164a84c22b0cc9e33f7dd5c0d5f76484314/loro-1.10.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:987dbcb42b4b8d2c799660a6d8942e53ae346f51d51c9ad7ef5d7e640422fe4a", size = 3680924, upload-time = "2025-12-09T10:10:39.877Z" },
+ { url = "https://files.pythonhosted.org/packages/40/61/548491499394fe02e7451b0d7367f7eeed32f0f6dd8f1826be8b4c329f28/loro-1.10.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:f876d477cb38c6c623c4ccb5dc4b7041dbeff04167bf9c19fa461d57a3a1b916", size = 3465033, upload-time = "2025-12-09T10:12:03.122Z" },
+ { url = "https://files.pythonhosted.org/packages/26/68/d8bebb6b583fe5a3dc4da32c9070964548e3ca1d524f383c71f9becf4197/loro-1.10.3-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:641c8445bd1e4181b5b28b75a0bc544ef51f065b15746e8714f90e2e029b5202", size = 3616740, upload-time = "2025-12-09T10:12:38.187Z" },
+ { url = "https://files.pythonhosted.org/packages/52/9b/8f8ecc85eb925122a79348eb77ff7109a7ee41ee7d1a282122be2daff378/loro-1.10.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:a6ab6244472402b8d1f4f77e5210efa44dfa4914423cafcfcbd09232ea8bbff0", size = 3661160, upload-time = "2025-12-09T10:13:12.513Z" },
+ { url = "https://files.pythonhosted.org/packages/79/3c/e884d06859f9a9fc64afd21c426b9d681af0856181c1fe66571a65d35ef7/loro-1.10.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ae4c765671ee7d7618962ec11cb3bb471965d9b88c075166fe383263235d58d6", size = 3553653, upload-time = "2025-12-09T10:13:47.917Z" },
+]
+
+[[package]]
+name = "marimo"
+version = "0.22.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "docutils" },
+ { name = "itsdangerous" },
+ { name = "jedi" },
+ { name = "loro" },
+ { name = "markdown" },
+ { name = "msgspec" },
+ { name = "narwhals" },
+ { name = "packaging" },
+ { name = "psutil" },
+ { name = "pygments" },
+ { name = "pymdown-extensions" },
+ { name = "pyyaml" },
+ { name = "pyzmq", marker = "python_full_version < '3.15'" },
+ { name = "starlette" },
+ { name = "tomlkit" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+ { name = "uvicorn" },
+ { name = "websockets" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/38/d0fbc9e7d58434bc608cb769e819053daae3ba43f4b9d819011a89276eda/marimo-0.22.0-py3-none-any.whl", hash = "sha256:b5a194e4e4f731512b8c6d82801473c502a2befbe547518717077706eabb59ba", size = 38659118, upload-time = "2026-03-31T21:07:07.076Z" },
+]
+
+[[package]]
+name = "markdown"
+version = "3.10.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
+]
+
+[package.optional-dependencies]
+linkify = [
+ { name = "linkify-it-py" },
+]
+
+[[package]]
+name = "mdit-py-plugins"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "msgspec"
+version = "0.20.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ea/9c/bfbd12955a49180cbd234c5d29ec6f74fe641698f0cd9df154a854fc8a15/msgspec-0.20.0.tar.gz", hash = "sha256:692349e588fde322875f8d3025ac01689fead5901e7fb18d6870a44519d62a29", size = 317862, upload-time = "2025-11-24T03:56:28.934Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e3/5e/151883ba2047cca9db8ed2f86186b054ad200bc231352df15b0c1dd75b1f/msgspec-0.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:23a6ec2a3b5038c233b04740a545856a068bc5cb8db184ff493a58e08c994fbf", size = 195191, upload-time = "2025-11-24T03:55:08.549Z" },
+ { url = "https://files.pythonhosted.org/packages/50/88/a795647672f547c983eff0823b82aaa35db922c767e1b3693e2dcf96678d/msgspec-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cde2c41ed3eaaef6146365cb0d69580078a19f974c6cb8165cc5dcd5734f573e", size = 188513, upload-time = "2025-11-24T03:55:10.008Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/91/eb0abb0e0de142066cebfe546dc9140c5972ea824aa6ff507ad0b6a126ac/msgspec-0.20.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5da0daa782f95d364f0d95962faed01e218732aa1aa6cad56b25a5d2092e75a4", size = 216370, upload-time = "2025-11-24T03:55:11.566Z" },
+ { url = "https://files.pythonhosted.org/packages/15/2a/48e41d9ef0a24b1c6e67cbd94a676799e0561bfbc163be1aaaff5ca853f5/msgspec-0.20.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9369d5266144bef91be2940a3821e03e51a93c9080fde3ef72728c3f0a3a8bb7", size = 222653, upload-time = "2025-11-24T03:55:13.159Z" },
+ { url = "https://files.pythonhosted.org/packages/90/c9/14b825df203d980f82a623450d5f39e7f7a09e6e256c52b498ea8f29d923/msgspec-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90fb865b306ca92c03964a5f3d0cd9eb1adda14f7e5ac7943efd159719ea9f10", size = 222337, upload-time = "2025-11-24T03:55:14.777Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/d7/39a5c3ddd294f587d6fb8efccc8361b6aa5089974015054071e665c9d24b/msgspec-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e8112cd48b67dfc0cfa49fc812b6ce7eb37499e1d95b9575061683f3428975d3", size = 225565, upload-time = "2025-11-24T03:55:16.4Z" },
+ { url = "https://files.pythonhosted.org/packages/98/bd/5db3c14d675ee12842afb9b70c94c64f2c873f31198c46cbfcd7dffafab0/msgspec-0.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:666b966d503df5dc27287675f525a56b6e66a2b8e8ccd2877b0c01328f19ae6c", size = 188412, upload-time = "2025-11-24T03:55:17.747Z" },
+ { url = "https://files.pythonhosted.org/packages/76/c7/06cc218bc0c86f0c6c6f34f7eeea6cfb8b835070e8031e3b0ef00f6c7c69/msgspec-0.20.0-cp310-cp310-win_arm64.whl", hash = "sha256:099e3e85cd5b238f2669621be65f0728169b8c7cb7ab07f6137b02dc7feea781", size = 173951, upload-time = "2025-11-24T03:55:19.335Z" },
+ { url = "https://files.pythonhosted.org/packages/03/59/fdcb3af72f750a8de2bcf39d62ada70b5eb17b06d7f63860e0a679cb656b/msgspec-0.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09e0efbf1ac641fedb1d5496c59507c2f0dc62a052189ee62c763e0aae217520", size = 193345, upload-time = "2025-11-24T03:55:20.613Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/15/3c225610da9f02505d37d69a77f4a2e7daae2a125f99d638df211ba84e59/msgspec-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23ee3787142e48f5ee746b2909ce1b76e2949fbe0f97f9f6e70879f06c218b54", size = 186867, upload-time = "2025-11-24T03:55:22.4Z" },
+ { url = "https://files.pythonhosted.org/packages/81/36/13ab0c547e283bf172f45491edfdea0e2cecb26ae61e3a7b1ae6058b326d/msgspec-0.20.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:81f4ac6f0363407ac0465eff5c7d4d18f26870e00674f8fcb336d898a1e36854", size = 215351, upload-time = "2025-11-24T03:55:23.958Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/96/5c095b940de3aa6b43a71ec76275ac3537b21bd45c7499b5a17a429110fa/msgspec-0.20.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb4d873f24ae18cd1334f4e37a178ed46c9d186437733351267e0a269bdf7e53", size = 219896, upload-time = "2025-11-24T03:55:25.356Z" },
+ { url = "https://files.pythonhosted.org/packages/98/7a/81a7b5f01af300761087b114dafa20fb97aed7184d33aab64d48874eb187/msgspec-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b92b8334427b8393b520c24ff53b70f326f79acf5f74adb94fd361bcff8a1d4e", size = 220389, upload-time = "2025-11-24T03:55:26.99Z" },
+ { url = "https://files.pythonhosted.org/packages/70/c0/3d0cce27db9a9912421273d49eab79ce01ecd2fed1a2f1b74af9b445f33c/msgspec-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:562c44b047c05cc0384e006fae7a5e715740215c799429e0d7e3e5adf324285a", size = 223348, upload-time = "2025-11-24T03:55:28.311Z" },
+ { url = "https://files.pythonhosted.org/packages/89/5e/406b7d578926b68790e390d83a1165a9bfc2d95612a1a9c1c4d5c72ea815/msgspec-0.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:d1dcc93a3ce3d3195985bfff18a48274d0b5ffbc96fa1c5b89da6f0d9af81b29", size = 188713, upload-time = "2025-11-24T03:55:29.553Z" },
+ { url = "https://files.pythonhosted.org/packages/47/87/14fe2316624ceedf76a9e94d714d194cbcb699720b210ff189f89ca4efd7/msgspec-0.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:aa387aa330d2e4bd69995f66ea8fdc87099ddeedf6fdb232993c6a67711e7520", size = 174229, upload-time = "2025-11-24T03:55:31.107Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/6f/1e25eee957e58e3afb2a44b94fa95e06cebc4c236193ed0de3012fff1e19/msgspec-0.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2aba22e2e302e9231e85edc24f27ba1f524d43c223ef5765bd8624c7df9ec0a5", size = 196391, upload-time = "2025-11-24T03:55:32.677Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/ee/af51d090ada641d4b264992a486435ba3ef5b5634bc27e6eb002f71cef7d/msgspec-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:716284f898ab2547fedd72a93bb940375de9fbfe77538f05779632dc34afdfde", size = 188644, upload-time = "2025-11-24T03:55:33.934Z" },
+ { url = "https://files.pythonhosted.org/packages/49/d6/9709ee093b7742362c2934bfb1bbe791a1e09bed3ea5d8a18ce552fbfd73/msgspec-0.20.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:558ed73315efa51b1538fa8f1d3b22c8c5ff6d9a2a62eff87d25829b94fc5054", size = 218852, upload-time = "2025-11-24T03:55:35.575Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/a2/488517a43ccf5a4b6b6eca6dd4ede0bd82b043d1539dd6bb908a19f8efd3/msgspec-0.20.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:509ac1362a1d53aa66798c9b9fd76872d7faa30fcf89b2fba3bcbfd559d56eb0", size = 224937, upload-time = "2025-11-24T03:55:36.859Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/e8/49b832808aa23b85d4f090d1d2e48a4e3834871415031ed7c5fe48723156/msgspec-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1353c2c93423602e7dea1aa4c92f3391fdfc25ff40e0bacf81d34dbc68adb870", size = 222858, upload-time = "2025-11-24T03:55:38.187Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/56/1dc2fa53685dca9c3f243a6cbecd34e856858354e455b77f47ebd76cf5bf/msgspec-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb33b5eb5adb3c33d749684471c6a165468395d7aa02d8867c15103b81e1da3e", size = 227248, upload-time = "2025-11-24T03:55:39.496Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/51/aba940212c23b32eedce752896205912c2668472ed5b205fc33da28a6509/msgspec-0.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:fb1d934e435dd3a2b8cf4bbf47a8757100b4a1cfdc2afdf227541199885cdacb", size = 190024, upload-time = "2025-11-24T03:55:40.829Z" },
+ { url = "https://files.pythonhosted.org/packages/41/ad/3b9f259d94f183daa9764fef33fdc7010f7ecffc29af977044fa47440a83/msgspec-0.20.0-cp312-cp312-win_arm64.whl", hash = "sha256:00648b1e19cf01b2be45444ba9dc961bd4c056ffb15706651e64e5d6ec6197b7", size = 175390, upload-time = "2025-11-24T03:55:42.05Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/d1/b902d38b6e5ba3bdddbec469bba388d647f960aeed7b5b3623a8debe8a76/msgspec-0.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c1ff8db03be7598b50dd4b4a478d6fe93faae3bd54f4f17aa004d0e46c14c46", size = 196463, upload-time = "2025-11-24T03:55:43.405Z" },
+ { url = "https://files.pythonhosted.org/packages/57/b6/eff0305961a1d9447ec2b02f8c73c8946f22564d302a504185b730c9a761/msgspec-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f6532369ece217fd37c5ebcfd7e981f2615628c21121b7b2df9d3adcf2fd69b8", size = 188650, upload-time = "2025-11-24T03:55:44.761Z" },
+ { url = "https://files.pythonhosted.org/packages/99/93/f2ec1ae1de51d3fdee998a1ede6b2c089453a2ee82b5c1b361ed9095064a/msgspec-0.20.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9a1697da2f85a751ac3cc6a97fceb8e937fc670947183fb2268edaf4016d1ee", size = 218834, upload-time = "2025-11-24T03:55:46.441Z" },
+ { url = "https://files.pythonhosted.org/packages/28/83/36557b04cfdc317ed8a525c4993b23e43a8fbcddaddd78619112ca07138c/msgspec-0.20.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fac7e9c92eddcd24c19d9e5f6249760941485dff97802461ae7c995a2450111", size = 224917, upload-time = "2025-11-24T03:55:48.06Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/56/362037a1ed5be0b88aced59272442c4b40065c659700f4b195a7f4d0ac88/msgspec-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f953a66f2a3eb8d5ea64768445e2bb301d97609db052628c3e1bcb7d87192a9f", size = 222821, upload-time = "2025-11-24T03:55:49.388Z" },
+ { url = "https://files.pythonhosted.org/packages/92/75/fa2370ec341cedf663731ab7042e177b3742645c5dd4f64dc96bd9f18a6b/msgspec-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:247af0313ae64a066d3aea7ba98840f6681ccbf5c90ba9c7d17f3e39dbba679c", size = 227227, upload-time = "2025-11-24T03:55:51.125Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/25/5e8080fe0117f799b1b68008dc29a65862077296b92550632de015128579/msgspec-0.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:67d5e4dfad52832017018d30a462604c80561aa62a9d548fc2bd4e430b66a352", size = 189966, upload-time = "2025-11-24T03:55:52.458Z" },
+ { url = "https://files.pythonhosted.org/packages/79/b6/63363422153937d40e1cb349c5081338401f8529a5a4e216865decd981bf/msgspec-0.20.0-cp313-cp313-win_arm64.whl", hash = "sha256:91a52578226708b63a9a13de287b1ec3ed1123e4a088b198143860c087770458", size = 175378, upload-time = "2025-11-24T03:55:53.721Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/18/62dc13ab0260c7d741dda8dc7f481495b93ac9168cd887dda5929880eef8/msgspec-0.20.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:eead16538db1b3f7ec6e3ed1f6f7c5dec67e90f76e76b610e1ffb5671815633a", size = 196407, upload-time = "2025-11-24T03:55:55.001Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/1d/b9949e4ad6953e9f9a142c7997b2f7390c81e03e93570c7c33caf65d27e1/msgspec-0.20.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:703c3bb47bf47801627fb1438f106adbfa2998fe586696d1324586a375fca238", size = 188889, upload-time = "2025-11-24T03:55:56.311Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/19/f8bb2dc0f1bfe46cc7d2b6b61c5e9b5a46c62298e8f4d03bbe499c926180/msgspec-0.20.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cdb227dc585fb109305cee0fd304c2896f02af93ecf50a9c84ee54ee67dbb42", size = 219691, upload-time = "2025-11-24T03:55:57.908Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/8e/6b17e43f6eb9369d9858ee32c97959fcd515628a1df376af96c11606cf70/msgspec-0.20.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27d35044dd8818ac1bd0fedb2feb4fbdff4e3508dd7c5d14316a12a2d96a0de0", size = 224918, upload-time = "2025-11-24T03:55:59.322Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/db/0e833a177db1a4484797adba7f429d4242585980b90882cc38709e1b62df/msgspec-0.20.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4296393a29ee42dd25947981c65506fd4ad39beaf816f614146fa0c5a6c91ae", size = 223436, upload-time = "2025-11-24T03:56:00.716Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/30/d2ee787f4c918fd2b123441d49a7707ae9015e0e8e1ab51aa7967a97b90e/msgspec-0.20.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:205fbdadd0d8d861d71c8f3399fe1a82a2caf4467bc8ff9a626df34c12176980", size = 227190, upload-time = "2025-11-24T03:56:02.371Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/37/9c4b58ff11d890d788e700b827db2366f4d11b3313bf136780da7017278b/msgspec-0.20.0-cp314-cp314-win_amd64.whl", hash = "sha256:7dfebc94fe7d3feec6bc6c9df4f7e9eccc1160bb5b811fbf3e3a56899e398a6b", size = 193950, upload-time = "2025-11-24T03:56:03.668Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/4e/cab707bf2fa57408e2934e5197fc3560079db34a1e3cd2675ff2e47e07de/msgspec-0.20.0-cp314-cp314-win_arm64.whl", hash = "sha256:2ad6ae36e4a602b24b4bf4eaf8ab5a441fec03e1f1b5931beca8ebda68f53fc0", size = 179018, upload-time = "2025-11-24T03:56:05.038Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/06/3da3fc9aaa55618a8f43eb9052453cfe01f82930bca3af8cea63a89f3a11/msgspec-0.20.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f84703e0e6ef025663dd1de828ca028774797b8155e070e795c548f76dde65d5", size = 200389, upload-time = "2025-11-24T03:56:06.375Z" },
+ { url = "https://files.pythonhosted.org/packages/83/3b/cc4270a5ceab40dfe1d1745856951b0a24fd16ac8539a66ed3004a60c91e/msgspec-0.20.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7c83fc24dd09cf1275934ff300e3951b3adc5573f0657a643515cc16c7dee131", size = 193198, upload-time = "2025-11-24T03:56:07.742Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/ae/4c7905ac53830c8e3c06fdd60e3cdcfedc0bbc993872d1549b84ea21a1bd/msgspec-0.20.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f13ccb1c335a124e80c4562573b9b90f01ea9521a1a87f7576c2e281d547f56", size = 225973, upload-time = "2025-11-24T03:56:09.18Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/da/032abac1de4d0678d99eaeadb1323bd9d247f4711c012404ba77ed6f15ca/msgspec-0.20.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17c2b5ca19f19306fc83c96d85e606d2cc107e0caeea85066b5389f664e04846", size = 229509, upload-time = "2025-11-24T03:56:10.898Z" },
+ { url = "https://files.pythonhosted.org/packages/69/52/fdc7bdb7057a166f309e0b44929e584319e625aaba4771b60912a9321ccd/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d931709355edabf66c2dd1a756b2d658593e79882bc81aae5964969d5a291b63", size = 230434, upload-time = "2025-11-24T03:56:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/fe/1dfd5f512b26b53043884e4f34710c73e294e7cc54278c3fe28380e42c37/msgspec-0.20.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:565f915d2e540e8a0c93a01ff67f50aebe1f7e22798c6a25873f9fda8d1325f8", size = 231758, upload-time = "2025-11-24T03:56:13.765Z" },
+ { url = "https://files.pythonhosted.org/packages/97/f6/9ba7121b8e0c4e0beee49575d1dbc804e2e72467692f0428cf39ceba1ea5/msgspec-0.20.0-cp314-cp314t-win_amd64.whl", hash = "sha256:726f3e6c3c323f283f6021ebb6c8ccf58d7cd7baa67b93d73bfbe9a15c34ab8d", size = 206540, upload-time = "2025-11-24T03:56:15.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/3e/c5187de84bb2c2ca334ab163fcacf19a23ebb1d876c837f81a1b324a15bf/msgspec-0.20.0-cp314-cp314t-win_arm64.whl", hash = "sha256:93f23528edc51d9f686808a361728e903d6f2be55c901d6f5c92e44c6d546bfc", size = 183011, upload-time = "2025-11-24T03:56:16.442Z" },
+]
+
+[[package]]
+name = "narwhals"
+version = "2.18.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/59/96/45218c2fdec4c9f22178f905086e85ef1a6d63862dcc3cd68eb60f1867f5/narwhals-2.18.1.tar.gz", hash = "sha256:652a1fcc9d432bbf114846688884c215f17eb118aa640b7419295d2f910d2a8b", size = 620578, upload-time = "2026-03-24T15:11:25.456Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/c3/06490e98393dcb4d6ce2bf331a39335375c300afaef526897881fbeae6ab/narwhals-2.18.1-py3-none-any.whl", hash = "sha256:a0a8bb80205323851338888ba3a12b4f65d352362c8a94be591244faf36504ad", size = 444952, upload-time = "2026-03-24T15:11:23.801Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
+]
+
+[[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 = "platformdirs"
+version = "4.9.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[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 = "pyasn1"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
+]
+
+[[package]]
+name = "pyasn1-modules"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyasn1" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" },
+]
+
+[[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 = "pydantic"
+version = "2.12.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.41.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
+ { url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
+ { url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
+ { url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
+ { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
+ { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
+ { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
+ { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
+ { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
+ { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
+ { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
+ { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
+ { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
+ { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
+ { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
+ { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
+ { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
+ { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
+ { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
+ { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
+ { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
+ { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
+ { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
+ { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
+ { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
+ { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
+ { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
+ { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
+ { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
+ { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
+ { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
+ { url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
+ { url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
+ { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
+ { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
+ { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
+ { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "pylatexenc"
+version = "2.10"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" }
+
+[[package]]
+name = "pymdown-extensions"
+version = "10.21.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown" },
+ { name = "pyyaml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
+ { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[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 = "recode-cli"
+version = "0.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "google-genai" },
+ { name = "marimo" },
+ { name = "platformdirs" },
+ { name = "pylatexenc" },
+ { name = "python-dotenv" },
+ { name = "textual" },
+]
+
+[package.optional-dependencies]
+dev = [
+ { name = "pytest" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "google-genai", specifier = ">=0.8.0" },
+ { name = "marimo", specifier = ">=0.11.0" },
+ { name = "platformdirs", specifier = ">=4.2.0" },
+ { name = "pylatexenc", specifier = ">=2.10" },
+ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.5" },
+ { name = "python-dotenv", specifier = ">=1.0.0" },
+ { name = "textual", specifier = ">=8.0.0" },
+]
+provides-extras = ["dev"]
+
+[[package]]
+name = "requests"
+version = "2.33.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
+]
+
+[[package]]
+name = "rich"
+version = "14.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "starlette"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
+]
+
+[[package]]
+name = "tenacity"
+version = "9.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" },
+]
+
+[[package]]
+name = "textual"
+version = "8.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py", extra = ["linkify"] },
+ { name = "mdit-py-plugins" },
+ { name = "platformdirs" },
+ { name = "pygments" },
+ { name = "rich" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/08/1e1f705825359590ddfaeda57653bd518c4ff7a96bb2c3239ba1b6fc4c51/textual-8.0.0.tar.gz", hash = "sha256:ce48f83a3d686c0fac0e80bf9136e1f8851c653aa6a4502e43293a151df18809", size = 1595895, upload-time = "2026-02-16T17:12:14.215Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d3/be/e191c2a15da20530fde03564564e3e4b4220eb9d687d4014957e5c6a5e85/textual-8.0.0-py3-none-any.whl", hash = "sha256:8908f4ebe93a6b4f77ca7262197784a52162bc88b05f4ecf50ac93a92d49bb8f", size = 718904, upload-time = "2026-02-16T17:12:11.962Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.14.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" },
+]
+
+[[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 = "typing-inspection"
+version = "0.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
+]
+
+[[package]]
+name = "uc-micro-py"
+version = "1.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/91/7a/146a99696aee0609e3712f2b44c6274566bc368dfe8375191278045186b8/uc-micro-py-1.0.3.tar.gz", hash = "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", size = 6043, upload-time = "2024-02-09T16:52:01.654Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/37/87/1f677586e8ac487e29672e4b17455758fce261de06a0d086167bb760361a/uc_micro_py-1.0.3-py3-none-any.whl", hash = "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5", size = 6229, upload-time = "2024-02-09T16:52:00.371Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.42.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "h11" },
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e3/ad/4a96c425be6fb67e0621e62d86c402b4a17ab2be7f7c055d9bd2f638b9e2/uvicorn-0.42.0.tar.gz", hash = "sha256:9b1f190ce15a2dd22e7758651d9b6d12df09a13d51ba5bf4fc33c383a48e1775", size = 85393, upload-time = "2026-03-16T06:19:50.077Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/89/f8827ccff89c1586027a105e5630ff6139a64da2515e24dafe860bd9ae4d/uvicorn-0.42.0-py3-none-any.whl", hash = "sha256:96c30f5c7abe6f74ae8900a70e92b85ad6613b745d4879eb9b16ccad15645359", size = 68830, upload-time = "2026-03-16T06:19:48.325Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" },
+ { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" },
+ { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" },
+ { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" },
+ { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" },
+ { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
+ { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
+ { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
+ { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
+ { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
+ { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
+ { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
+ { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
+ { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" },
+ { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" },
+ { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" },
+ { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" },
+ { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" },
+ { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
+ { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
+ { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
+]