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" }, +]