diff --git a/.gitignore b/.gitignore index 6859e4e..6e18bdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,8 @@ -# Environments +# Reproducibility: results artifacts are regenerable from committed configs + seeds; only +# their hashes (in each run's manifest.json) are tracked, per the open-science contract. .venv/ __pycache__/ *.pyc .pytest_cache/ - -# Results: the large data artifact (results.parquet) is regenerable and gitignored; -# the reproducibility metadata (manifest.json with content hashes, resolved_config.yaml) -# and the figures (PNG/PDF) are tracked so the paper's figures live in the repo. -results/**/*.parquet - -# OS / editor -.DS_Store +results/**/results.parquet +models/ diff --git a/CLAUDE.md b/CLAUDE.md index 17a2a33..c255a75 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,12 +2,28 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Current state: greenfield +## Current state: Layer 1 complete; Layer 1.5 (neural) in progress -This repository currently contains **only two design documents and no code**. The task is to implement the study those documents specify. +- **Layer 1** (`src/knowledge/`) — **complete and validated.** All six experiments E1–E6, the + closed-form scientific-validation tests, figures, and reproducibility harness exist. Headline: + critical grounding `g* = 0.048 ≪ 1`; the E4 finding that mean-mixture distillation conserves + collapse while only a union-preserving max-merge realises the recombination benefit. +- **Layer 1.5** (`src/neural/`) — **in progress.** An architecture-general neural existence proof + (re-scoped Layer 2): the same Wright–Fisher abstractions realised in *real trained generative + models* (histogram bridge + RNN + MLP; VAE implemented but not fidelity-passing) on a + fully-synthetic sandbox with an exact oracle, plus real MNIST as a later secondary tier. See + `tasks/todo.md` for status and `~/.claude/plans/we-are-going-to-cheerful-fog.md` for the plan. + **Done:** scaffold, the histogram bridge gate (reproduces Layer 1 exactly), N0 (bridge, neural + g*=0.047 ≈ Layer 1), N1 (collapse in RNN weights), N2 (neural phase boundary), N5 + (architecture-generality). **Remaining:** N4 (mean-vs-max-merge, load-bearing), N3, N6, figures, + the MNIST tier. The LLM/LoRA rung and the C3 vertical claim are deferred. -- `the-lamarckian-society-v4.md` — the *perspective paper*. The conceptual thesis: a multigenerational architecture of continual-learning agents that mature, teach, and evolve. Read this for the "why." -- `lamarckian-society-technical-blueprint-v1.md` — the *technical blueprint*. A build specification written to be handed to a coding agent. Read this for the "what" and "how." **It is normative**: module names, function signatures, config schema, experiment IDs, and directory layout in it are the contract to implement against. +The two design documents are the source of truth for intent: + +- `paper/the-lamarckian-society-v4.md` — the *perspective paper* (the "why"). +- `paper/blueprint.md` — the *technical blueprint* (the "what"/"how"). **It is normative** for + Layer 1 and the LLM Layer 2; Layer 1.5 is a cost-staged intermediate the blueprint does not + cover, designed to preserve the same §1 abstractions. Everything below summarizes the blueprint so you can orient fast, but the blueprint is the source of truth. When they conflict, the blueprint wins; when the blueprint is silent, minimize decisions and match its established patterns. diff --git a/Makefile b/Makefile index dda3672..9eec5fa 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,28 @@ -# Layer 1 automation. The uv venv (built from the committed uv.lock) is the +# Layer 1 + Layer 1.5 automation. The uv venv (built from the committed uv.lock) is the # reproducibility source of truth; every target runs inside it via `uv run`. -.PHONY: env test layer1 figures clean +.PHONY: env env-neural test layer1 layer2 neural figures clean env: ## build .venv from the committed lockfile uv sync --extra dev +env-neural: ## add the Layer 1.5 torch stack (GPU; Stage C onward) + uv sync --extra dev --extra neural + test: ## correctness tests + scientific-validation tests (the spine of trust) uv run pytest layer1: ## run experiments E1-E6 for e in E1 E2 E3 E4 E5 E6; do uv run python -m knowledge.experiment configs/layer1/$$e.yaml; done +neural: ## run Layer 1.5 neural experiments (N-series) + for c in configs/neural/*.yaml; do uv run python -m neural.experiment $$c; done + +layer2: neural ## alias: Layer 1.5 is the current Layer-2 deliverable (LLM rung deferred) + figures: ## regenerate figures from committed results for e in E1 E2 E3 E4 E5 E6; do MPLBACKEND=Agg uv run python figures/plot_$$e.py; done + for p in figures/plot_N*.py; do [ -e "$$p" ] && MPLBACKEND=Agg uv run python "$$p"; done clean: ## remove caches and generated results (keeps committed manifests) rm -rf .pytest_cache **/__pycache__ diff --git a/configs/neural/N0.yaml b/configs/neural/N0.yaml new file mode 100644 index 0000000..9d6e580 --- /dev/null +++ b/configs/neural/N0.yaml @@ -0,0 +1,42 @@ +experiment: N0_bridge_histogram +kind: gen_lineage +seed: 20260704 +n_replicates: 60 +generations: 200 + +# Bridge / harness-faithfulness check (Layer 1.5 build-order Stage B): run the E2 grounding +# phase-boundary sweep through the NEURAL runner with the histogram model, which is exactly +# neutral Wright-Fisher drift with immigration. Stationary H must track the closed form +# H_eq = H* * m(2n+m-1)/(n+2nm+m^2) and reproduce a critical g* << 1 — i.e. the neural +# plumbing reproduces the analytic core before any real network is trained. R=1 so every +# grounding policy reduces to the proportional immigration model H_eq is derived for. +synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 1.0e-3 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + +model: + kind: histogram + +dynamics: + n: 200 + grounding: {m: 0, policy: proportional} # m is overwritten per g by the sweep + remint: {enabled: false, period: null, H_gate: null} + +metrics: + kl_floor: 1.0e-9 + support_eps: 1.0e-9 + +sweep: + - param: g + values: [0.0, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2, 0.4] + +output: + dir: results/N0 diff --git a/configs/neural/N1.yaml b/configs/neural/N1.yaml new file mode 100644 index 0000000..1c73e1a --- /dev/null +++ b/configs/neural/N1.yaml @@ -0,0 +1,50 @@ +experiment: N1_collapse_in_weights +kind: gen_lineage +seed: 20260704 +n_replicates: 5 + +# N1 (Layer 1.5, maps to Layer-1 E1 / blueprint C1): does model collapse appear in REAL +# trained weights under dry recursive self-training, and does a little grounding arrest it? +# An autoregressive RNN is retrained each generation on n samples drawn from the previous +# generation's RNN (drift), optionally mixed with m verifier-grounded samples. Expect: the +# dry arm (g=0) loses diversity (H falls) and forgets the tail (forward-KL to truth rises, +# tail_mass shrinks); grounded arms hold. Falsifier: dry inheritance does not degrade in +# real weights -> the neural collapse claim is unsupported at this scale. +generations: 25 + +synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 1.0e-3 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + +model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 2.0e-3 + batch_size: 256 + n_eval: 12000 + +dynamics: + n: 200 + grounding: {m: 0, policy: proportional} # m overwritten per g by the sweep + remint: {enabled: false, period: null, H_gate: null} + +metrics: + kl_floor: 1.0e-9 + support_eps: 1.0e-9 + +sweep: + - param: g + values: [0.0, 0.02, 0.05, 0.1] + +output: + dir: results/N1 diff --git a/configs/neural/N2.yaml b/configs/neural/N2.yaml new file mode 100644 index 0000000..df31add --- /dev/null +++ b/configs/neural/N2.yaml @@ -0,0 +1,50 @@ +experiment: N2_grounding_phase_boundary_neural +kind: gen_lineage +seed: 20260704 +n_replicates: 5 + +# N2 (Layer 1.5 headline, maps to Layer-1 E2): the grounding phase boundary in REAL weights. +# Sweep the grounding fraction g = m/(n+m) and locate the neural critical g* at which +# stationary diversity is restored. Layer 1 found g* = 0.048 << 1. The neural regime (finite +# model capacity, a smaller K so gen-0 fidelity holds) will not reproduce that value exactly +# -- the claim is directional (blueprint 3.5): a critical g* << 1 exists in trained weights, +# i.e. a little grounding protects most of the diversity. Falsifier: stationary H flat in g, +# or only restored as g -> 1. +generations: 30 + +synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 1.0e-3 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + +model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 2.0e-3 + batch_size: 256 + n_eval: 12000 + +dynamics: + n: 200 + grounding: {m: 0, policy: proportional} # m overwritten per g by the sweep + remint: {enabled: false, period: null, H_gate: null} + +metrics: + kl_floor: 1.0e-9 + support_eps: 1.0e-9 + +sweep: + - param: g + values: [0.0, 0.005, 0.01, 0.02, 0.05, 0.1, 0.2] + +output: + dir: results/N2 diff --git a/configs/neural/N5.yaml b/configs/neural/N5.yaml new file mode 100644 index 0000000..9e02c74 --- /dev/null +++ b/configs/neural/N5.yaml @@ -0,0 +1,54 @@ +experiment: N5_architecture_generality +kind: gen_lineage +seed: 20260704 +n_replicates: 5 + +# N5 (a new Layer-1.5 axis, no Layer-1 counterpart): is collapse ARCHITECTURE-GENERAL? +# Run the same dry-vs-grounded protocol across three genuinely different learners that +# share only the generative-collapse operator: the exact histogram (= Wright-Fisher, the +# analytic anchor), an autoregressive GRU (recurrent), and an autoregressive MLP (feed- +# forward). Expect the same SIGN in all: dry (g=0) loses diversity / forgets the tail; +# grounding arrests it. Falsifier: the signs appear only for the histogram -> real neural +# inductive biases break the Wright-Fisher mapping. (The sequence VAE is implemented but +# excluded here: it does not clear the gen-0 fidelity gate on the Zipf-codeword task, so +# its collapse would be confounded with underfitting; see tasks/todo.md.) +generations: 22 + +synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 1.0e-3 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + +model: + kind: rnn # overwritten per arm by the model.kind sweep + hidden: 192 + embed: 24 + epochs: 25 + lr: 2.0e-3 + batch_size: 256 + n_eval: 12000 + +dynamics: + n: 200 + grounding: {m: 0, policy: proportional} # m overwritten per g by the sweep + remint: {enabled: false, period: null, H_gate: null} + +metrics: + kl_floor: 1.0e-9 + support_eps: 1.0e-9 + +sweep: + - param: model.kind + values: [histogram, rnn, mlp] + - param: g + values: [0.0, 0.05] + +output: + dir: results/N5 diff --git a/paper/layer1-summary.md b/paper/layer1-summary.md new file mode 100644 index 0000000..a27c7f8 --- /dev/null +++ b/paper/layer1-summary.md @@ -0,0 +1,208 @@ +# Layer 1 — Summary of results + +*The Lamarckian Society, analytical core. Two summaries of the same work: one technical, +one accessible to ML engineers and neuroscientists with no population-genetics background.* + +--- + +## A. Technical summary + +### What was built + +Layer 1 is a parametric model of generational knowledge transmission, built on the +observation that the generational step — *sample from the parent distribution, optionally +mix in fresh real samples, refit* — is **literally a Wright–Fisher process with +immigration**, not merely analogous to one. Knowledge is a distribution `p_t` over `K` +discrete items on the simplex; a fixed true distribution `p*` carries a deliberate heavy +(Zipf) tail; "model collapse" is the loss of rare alleles under drift. Each safeguard +from the perspective paper is one operator on the step: + +- **grounding** `g = m/(n+m)` — immigration of `m` real samples per `n` inherited (mutation supply); +- **region-matched grounding** — immigration structured by locus; +- **multi-teacher distillation** — recombination across lineages; +- **selection** — directional (`greedy`) vs. balancing/novelty (`qd`); +- **re-minting** — a founder event that freezes `p_t` as the new reference and discards `p*`. + +Because the process is Wright–Fisher, it inherits **closed-form validation targets**, which +are enforced as `test_scientific_validation.py` assertions (the "spine of trust"): + +1. neutral heterozygosity decay `E[H_t] = H₀(1−1/n)^t`; +2. fixation probability = initial frequency; +3. **exact** mutation–drift equilibrium `H_eq = H*·m(2n+m−1)/(n+2nm+m²)` (not the textbook `θ/(1+θ)` approximation); +4. tail-persistence threshold `m·p*_i ≳ 1`; +5. recombination union coverage `U(K_T,ρ,q) = T[ρq + (1−ρ)(1−(1−q)^{K_T})]`, with teachers built by a shared-switch exchangeable-Bernoulli construction giving *exact* marginal retention `q` and pairwise correlation `ρ`. + +The simulator matches (1), (3), (5) to `<0.5%` and (2), (4) statistically. 71 tests pass. + +### Findings (E1–E6) + +- **E1 — collapse (null).** Neutral drift reproduces the geometric `H` decay to within + Monte-Carlo error; support collapses `K→1`; forward KL to truth diverges. Tail *items* + go extinct ≈10× faster than head items. **Subtlety:** aggregate tail *mass* is a drift + martingale (mean-conserved), so it is a misleading collapse metric; tail-*item* survival + is the honest one. + +- **E2 — grounding phase boundary (headline).** Stationary `H` tracks the exact `H_eq` + across the sweep. An operational critical grounding `g* = 0.048` (95% bootstrap CI + [0.047, 0.050]) marks where `H` reaches 95% of `H*`; **g* ≪ 1** — as little as `m=1` + real sample against `n=200` inherited (`g=0.005`) restores 68% of the truth's diversity; + `g=0.05` reaches 96%. The phase boundary in `H` is *smooth* (H is continuous in `m`); the + sharp threshold lives in discrete tail-item survival. Per-rarity-band analysis makes the + `m·p*_i ≳ 1` law visible: at feasible grounding the **deep tail is unrescuable** — diversity + is cheap to protect, but the rarest items require grounding budgets that scale as `1/p_min`. + +- **E3 — region-matched grounding.** At fixed total budget, `matched` grounding preserves + the exercised region's tail (survival 0.49) where `uniform` spreads thin and lets it + collapse (0.07). Grounding protects only what it overlaps. (Per-region `H` is confounded + by region mass under matched grounding; tail-item survival is the clean metric.) + +- **E4 — multi-teacher recombination.** Union coverage matches `U(K_T,ρ,q)` exactly + (recombination *supplies* the tail). **Principal finding:** under the blueprint's + mean-mixture distillation, surviving tail coverage is **flat in `K_T`** — a conservation + law, since averaging preserves expected pupil tail mass at `q·(tail mass of p*)` + regardless of `K_T`, and in the rare-tail (linear-survival) regime the `1/K_T` dilution + *exactly cancels* the union gain. The recombination benefit is realised only under a + **union-preserving merge** (`max` over teachers, à la M2N2 model-merging), where surviving + coverage rises with `K_T` and with decorrelation `(1−ρ)`. E4 reports both operators. + +- **E5 — QD vs. greedy.** At matched grounding, greedy (directional) selection drives + fixation (`H≈0.01`); quality-diversity selection (`w_i ∝ f_i·p_i^{−α}`) holds `H` at a + positive plateau (0.48–0.88, rising with the novelty exponent α). qd ≫ greedy. + +- **E6 — re-minting gate.** Re-minting a *collapsed* lineage discards the original truth and + makes forward KL to the original **diverge** (irreversible lock-in), and even accelerates + the `H` collapse (grounding now reinforces the surviving few). A diversity gate + (`H ≥ H_gate`) refuses to re-mint while collapsed and keeps KL bounded; re-minting a + healthy lineage is harmless. + +### Implications + +1. **The economic bet holds for diversity, not the deep tail.** The architecture's central + claim — "a little grounding protects a lot of inheritance" — is confirmed *for overall + diversity* (`g* ≪ 1`). But the deepest tail cannot be held by grounding at any feasible + budget (`m* ∼ 1/p_min`). Preserving the deep tail is therefore *not* grounding's job — it + is recombination's, which sets up E4 and the paper's multi-teacher argument. + +2. **Naive multi-teacher distillation does not prevent tail collapse; merging does.** This is + the sharpest new result. The paper's recombination benefit is real at the *supply* (union) + level but is annihilated by mean-mixture averaging at matched budget. The benefit survives + into the pupil only under a union-preserving merge operator. The paper's recombination + claim should therefore rest on **model-merging (M2N2)**, not on averaging distillation — + a concrete, falsifiable design constraint carried into Layer 2 (contrast C4). + +3. **Re-minting is a one-way door and must be gated.** Assimilating soft inheritance into a + new base while the lineage has narrowed locks in the collapse irreversibly. A cheap + diversity gate suffices to prevent it. + +4. **Everything is anchored to closed forms.** Three of the five predictions are exact, so + the simulator is *validated*, not merely plausible — the headline curves sit on analytic + targets. The study is bitwise-reproducible from a seed (uv-locked environment). + +--- + +## B. Accessible summary (for ML engineers and neuroscientists) + +### The question + +Modern AI is trained once and frozen; it cannot keep learning without *catastrophically +forgetting*. The Lamarckian Society proposes an alternative: **generations** of bounded +agents that learn through a working life, then *teach* a fresh pupil, who inherits the +compressed knowledge and starts ahead — a cultural ratchet. The danger is well known to ML +engineers under a different name: train a model on the previous model's outputs, generation +after generation, and it suffers **model collapse** — the rare, improbable cases (the *tail*) +vanish first and the model drifts to its own mode. The teaching step in this architecture *is* +that collapse operation. So the whole scheme lives or dies on one question: **under what +conditions does generational teaching accumulate knowledge instead of degrading it?** Layer 1 +answers that quantitatively, before any GPUs are involved. + +### The one idea that makes it rigorous + +Represent a model's knowledge as a probability distribution over discrete "items" +(capabilities, facts, behaviours). One generation = *draw a finite sample of size `n` from the +teacher, and refit the pupil to it.* That finite-sampling step is **mathematically identical** +to genetic drift in a finite population — the century-old **Wright–Fisher** process. That is +not a metaphor; it is the same equations. The payoff: population genetics already has **exact +formulas** for how diversity decays, what survives, and how "immigration" of fresh individuals +holds a population together. We inherit those formulas as **ground truth to check the simulator +against** — so the results below are *provably correct*, not just plausible-looking curves. + +A small dictionary: + +| in this model | ML reading | neuroscience reading | +|---|---|---| +| knowledge item | a capability / mode of the model | a memory / stored pattern | +| sample size `n` | how much data the student distils from | consolidation bandwidth | +| the tail | rare capabilities / long-tail inputs | rare episodic detail | +| grounding `g` | fraction of fresh **verified** real data in the training mix | new lived experience replenishing memory | +| heterozygosity `H` | diversity of the model's knowledge | richness / non-degeneracy of memory | +| collapse | mode-seeking / catastrophic forgetting | memory degradation, loss of the improbable | + +### What we found, in plain terms + +1. **Without fresh data, teaching collapses — and the rare stuff goes first, fast.** Pure + generation-on-generation distillation loses diversity exponentially, at a rate set by how + much data the student sees. Rare items go extinct roughly 10× faster than common ones. + (This reproduces, exactly, the known math of drift.) + +2. **A little fresh grounded data rescues almost all the diversity — this is the headline.** + Mixing in even ~5% verified real data (in the extreme, *one* real sample against 200 + inherited) restores ~70–96% of the model's diversity and holds it there indefinitely. + Grounding is cheap and it works. **But** there is a hard limit: the *very rarest* + capabilities still cannot be saved by grounding alone — protecting an item of rarity `p` + needs a real-data budget that grows like `1/p`. So grounding rescues *diversity* cheaply, + but not the deepest tail. (That is a feature, not a bug — it tells us what the other + mechanisms are for.) + +3. **Grounding only protects what it overlaps.** Spreading a fixed amount of fresh data thinly + across everything fails to protect any specific area; you must ground the *specific* region + you want to keep. "Don't inherit dry, region by region" is literally true. + +4. **Learning from several diverse teachers can preserve rare knowledge one teacher would + lose — but only if you combine them correctly. This is the surprising, important one.** + Multiple decorrelated teachers *collectively* retain far more of the tail than any one of + them (we verified this against an exact formula). But whether the *pupil* keeps that + depends entirely on **how you merge the teachers**. The standard approach — averaging their + outputs (ordinary multi-teacher distillation) — **mathematically cancels the benefit**: the + averaging dilutes each teacher's rare knowledge by exactly the factor by which more teachers + would have helped. A **"keep-the-strongest-teacher-per-item" merge** (the style of model + *merging*, e.g. Sakana's M2N2) *does* realise the benefit — rare-capability retention rises + with the number and diversity of teachers. **Design lesson: to fight tail collapse with + multiple teachers, merge their weights; don't average their outputs.** + +5. **Optimising for "quality" alone collapses diversity; rewarding novelty too keeps it + alive.** Selecting for fitness drives everything to the single best item (fixation); + rewarding rareness alongside fitness (quality-diversity selection) maintains a rich, + diverse population. (Familiar to anyone who has watched a population-based or RLHF pipeline + mode-collapse.) + +6. **"Baking in" accumulated knowledge into a new base model is a one-way door.** Periodically + consolidating soft inheritance into fresh base weights lets the system grow without bound — + but if you do it *after* the model has already narrowed, you lock in the damage + **permanently** (the original, uncollapsed reference is gone). A cheap check — only + consolidate while diversity is still high — prevents the irreversible mistake. + +### Why it is novel and why it matters + +- **It turns a hand-wavy debate into exact, falsifiable science.** "Does generational + distillation ratchet up or collapse?" was an argument by analogy. Casting it as + Wright–Fisher makes it a set of equations with closed-form answers, and the simulator is + validated against them — so the headline curves *sit on analytic targets*, not on + eyeballing. + +- **It quantifies the feasibility of the whole architecture.** The result that a *tiny* + grounding fraction protects most of the diversity (`g* ≪ 1`) is what makes a + continually-teaching society economically plausible rather than a data-hungry fantasy. + +- **It corrects how the field should build multi-teacher systems.** The finding that ordinary + averaging distillation gives *no* protection against tail collapse — while weight-merging + does — is a concrete, testable design constraint that most current multi-agent/distillation + setups get wrong by default. + +- **It gives an operational safety rule for self-improving systems.** "Consolidate only while + diversity is high" is a simple, measurable gate against a failure mode (irreversible + collapse-in-place) that self-distilling systems are otherwise prone to. + +All of this is at the level of *distributions and dynamics*, deliberately upstream of neural +networks — Layer 2 then checks that the same three signs (grounded inheritance holds where dry +inheritance degrades; complementary teachers preserve what one sheds; general capability climbs +while each specialty is re-earned) appear in real LoRA-adapted language models. diff --git a/pyproject.toml b/pyproject.toml index e61f8ac..e446765 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,15 +16,22 @@ dependencies = [ [project.optional-dependencies] dev = ["pytest>=8.0"] +# Layer 1.5 neural existence proof. Torch is only needed from Stage C (RNN/VAE/MLP); +# Stages A-B (synthetic sandbox + histogram bridge) are pure NumPy and run in the base env. +# The default PyPI torch wheel is CUDA-enabled (cu13, matching the RTX A4000 driver). +# Install with `uv sync --extra neural`. +neural = ["torch>=2.2"] +# The real-MNIST secondary-confirmation tier only. Install with `uv sync --extra mnist`. +mnist = ["torchvision>=0.17"] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" # src-layout: src/knowledge/ is importable as `knowledge` (the normative package -# name the scientific-validation conformance tests import). +# name the scientific-validation conformance tests import). src/neural/ is Layer 1.5. [tool.hatch.build.targets.wheel] -packages = ["src/knowledge"] +packages = ["src/knowledge", "src/neural"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/results/N0/manifest.json b/results/N0/manifest.json new file mode 100644 index 0000000..58ce453 --- /dev/null +++ b/results/N0/manifest.json @@ -0,0 +1,16 @@ +{ + "experiment": "N0_bridge_histogram", + "master_seed": 20260704, + "git_commit": null, + "python": "3.14.5", + "libraries": { + "numpy": "2.5.0", + "scipy": "1.18.0", + "pandas": "3.0.3", + "pyarrow": "24.0.0" + }, + "rows": 96480, + "results_sha256": "445fd5165fc69edbca87f79c0cf669b5879025637f4d36ddc769e940fb02f114", + "layer": "1.5", + "model_kind": "histogram" +} \ No newline at end of file diff --git a/results/N0/resolved_config.yaml b/results/N0/resolved_config.yaml new file mode 100644 index 0000000..9318e66 --- /dev/null +++ b/results/N0/resolved_config.yaml @@ -0,0 +1,288 @@ +experiment: N0_bridge_histogram +seed: 20260704 +n_replicates: 60 +source_config: + experiment: N0_bridge_histogram + kind: gen_lineage + seed: 20260704 + n_replicates: 60 + generations: 200 + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 + sweep: + - param: g + values: + - 0.0 + - 0.005 + - 0.01 + - 0.02 + - 0.05 + - 0.1 + - 0.2 + - 0.4 + output: + dir: results/N0 +grid: +- label: + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.005 + m: 1 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 1 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.01 + m: 2 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 2 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.02 + m: 4 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 4 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.1 + m: 22 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 22 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.2 + m: 50 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 50 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.4 + m: 133 + neural_cfg: + synthetic: + K: 200 + R: 1 + tail: zipf + zipf_s: 1.1 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 2 + style_vocab: 4 + id_base: 2 + model: + kind: histogram + dynamics: + n: 200 + grounding: + m: 133 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 200 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 diff --git a/results/N1/manifest.json b/results/N1/manifest.json new file mode 100644 index 0000000..981cc7c --- /dev/null +++ b/results/N1/manifest.json @@ -0,0 +1,17 @@ +{ + "experiment": "N1_collapse_in_weights", + "master_seed": 20260704, + "git_commit": null, + "python": "3.14.5", + "libraries": { + "numpy": "2.5.0", + "scipy": "1.18.0", + "pandas": "3.0.3", + "pyarrow": "24.0.0", + "torch": "2.12.1" + }, + "rows": 520, + "results_sha256": "27438db70af3240524855e59c4a14d3fb63bf6fca2d2002cdcb1ac354559909b", + "layer": "1.5", + "model_kind": "rnn" +} \ No newline at end of file diff --git a/results/N1/resolved_config.yaml b/results/N1/resolved_config.yaml new file mode 100644 index 0000000..21020a9 --- /dev/null +++ b/results/N1/resolved_config.yaml @@ -0,0 +1,194 @@ +experiment: N1_collapse_in_weights +seed: 20260704 +n_replicates: 5 +source_config: + experiment: N1_collapse_in_weights + kind: gen_lineage + seed: 20260704 + n_replicates: 5 + generations: 25 + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 + sweep: + - param: g + values: + - 0.0 + - 0.02 + - 0.05 + - 0.1 + output: + dir: results/N1 +grid: +- label: + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 25 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.02 + m: 4 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 4 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 25 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 25 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.1 + m: 22 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 22 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 25 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 diff --git a/results/N2/manifest.json b/results/N2/manifest.json new file mode 100644 index 0000000..b4fb6fb --- /dev/null +++ b/results/N2/manifest.json @@ -0,0 +1,17 @@ +{ + "experiment": "N2_grounding_phase_boundary_neural", + "master_seed": 20260704, + "git_commit": null, + "python": "3.14.5", + "libraries": { + "numpy": "2.5.0", + "scipy": "1.18.0", + "pandas": "3.0.3", + "pyarrow": "24.0.0", + "torch": "2.12.1" + }, + "rows": 1085, + "results_sha256": "93590677221955189814ccf044b9dc8aee36b026e350c9dea7e8d23b137e514b", + "layer": "1.5", + "model_kind": "rnn" +} \ No newline at end of file diff --git a/results/N2/resolved_config.yaml b/results/N2/resolved_config.yaml new file mode 100644 index 0000000..4922775 --- /dev/null +++ b/results/N2/resolved_config.yaml @@ -0,0 +1,305 @@ +experiment: N2_grounding_phase_boundary_neural +seed: 20260704 +n_replicates: 5 +source_config: + experiment: N2_grounding_phase_boundary_neural + kind: gen_lineage + seed: 20260704 + n_replicates: 5 + generations: 30 + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 + sweep: + - param: g + values: + - 0.0 + - 0.005 + - 0.01 + - 0.02 + - 0.05 + - 0.1 + - 0.2 + output: + dir: results/N2 +grid: +- label: + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.005 + m: 1 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 1 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.01 + m: 2 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 2 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.02 + m: 4 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 4 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.1 + m: 22 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 22 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + g: 0.2 + m: 50 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 128 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 50 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 30 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 diff --git a/results/N5/manifest.json b/results/N5/manifest.json new file mode 100644 index 0000000..fa038e9 --- /dev/null +++ b/results/N5/manifest.json @@ -0,0 +1,17 @@ +{ + "experiment": "N5_architecture_generality", + "master_seed": 20260704, + "git_commit": null, + "python": "3.14.5", + "libraries": { + "numpy": "2.5.0", + "scipy": "1.18.0", + "pandas": "3.0.3", + "pyarrow": "24.0.0", + "torch": "2.12.1" + }, + "rows": 690, + "results_sha256": "4e8d7931e791493aa35ea7b114d81b90a3a58eabd92d8b1dbd87cf0a9e200ba1", + "layer": "1.5", + "model_kind": "rnn" +} \ No newline at end of file diff --git a/results/N5/resolved_config.yaml b/results/N5/resolved_config.yaml new file mode 100644 index 0000000..48f7ee1 --- /dev/null +++ b/results/N5/resolved_config.yaml @@ -0,0 +1,275 @@ +experiment: N5_architecture_generality +seed: 20260704 +n_replicates: 5 +source_config: + experiment: N5_architecture_generality + kind: gen_lineage + seed: 20260704 + n_replicates: 5 + generations: 22 + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 + sweep: + - param: model.kind + values: + - histogram + - rnn + - mlp + - param: g + values: + - 0.0 + - 0.05 + output: + dir: results/N5 +grid: +- label: + kind: histogram + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: histogram + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + kind: histogram + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: histogram + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + kind: rnn + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + kind: rnn + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: rnn + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + kind: mlp + g: 0.0 + m: 0 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: mlp + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 0 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 +- label: + kind: mlp + g: 0.05 + m: 11 + neural_cfg: + synthetic: + K: 256 + R: 1 + tail: zipf + zipf_s: 1.3 + tail_frac: 0.5 + tail_threshold: 0.001 + init: truth + style_len: 3 + style_vocab: 5 + id_base: 2 + model: + kind: mlp + hidden: 192 + embed: 24 + epochs: 25 + lr: 0.002 + batch_size: 256 + n_eval: 12000 + dynamics: + n: 200 + grounding: + m: 11 + policy: proportional + remint: + enabled: false + period: null + H_gate: null + generations: 22 + metrics: + kl_floor: 1.0e-09 + support_eps: 1.0e-09 diff --git a/src/knowledge/experiment.py b/src/knowledge/experiment.py index 208075a..983ba25 100644 --- a/src/knowledge/experiment.py +++ b/src/knowledge/experiment.py @@ -211,11 +211,28 @@ def _content_hash(path: Path) -> str: return h.hexdigest() -def save_artifacts(cfg: dict, df: pd.DataFrame, out_dir: Path) -> None: +def save_artifacts(cfg: dict, df: pd.DataFrame, out_dir: Path, + extra_libs: tuple[str, ...] = (), + extra_manifest: dict | None = None, + grid: list | None = None) -> None: """Write the reproducibility output contract (blueprint 2.7 / 4). Writes ``results.parquet``, ``resolved_config.yaml`` (the fully-expanded config), and ``manifest.json`` (library versions, master seed, git commit, content hash). + + Args: + cfg (dict): The parsed experiment config. + df (pd.DataFrame): The long-form results. + out_dir (Path): Output directory. + extra_libs (tuple[str, ...]): Extra library names to record versions for (e.g. + ``torch``, ``torchvision`` for Layer 1.5). Missing libraries are skipped, so a + caller can pass optional deps unconditionally. + extra_manifest (dict | None): Extra key/value pairs to merge into the manifest + (e.g. model architecture, oracle checkpoint hash, determinism flags). + grid (list | None): Pre-expanded ``[{label, lineage_cfg}, ...]`` to record in the + resolved config. If None, it is computed via ``expand_sweeps`` for the Layer-1 + ``lineage`` kind (a caller with a different schema, e.g. Layer 1.5, passes its + own expanded grid here). """ out_dir.mkdir(parents=True, exist_ok=True) results_path = out_dir / "results.parquet" @@ -227,24 +244,32 @@ def save_artifacts(cfg: dict, df: pd.DataFrame, out_dir: Path) -> None: "n_replicates": cfg["n_replicates"], "source_config": cfg, } - if cfg.get("kind", "lineage") == "lineage": + if grid is not None: + resolved["grid"] = grid + elif cfg.get("kind", "lineage") == "lineage": resolved["grid"] = [ {"label": label, "lineage_cfg": lineage_cfg} for label, lineage_cfg in expand_sweeps(cfg) ] (out_dir / "resolved_config.yaml").write_text(yaml.safe_dump(resolved, sort_keys=False)) + libraries: dict[str, str] = {} + for lib in ("numpy", "scipy", "pandas", "pyarrow") + tuple(extra_libs): + try: + libraries[lib] = version(lib) + except Exception: # optional dep not installed -> omit rather than crash + pass manifest = { "experiment": cfg["experiment"], "master_seed": cfg["seed"], "git_commit": _git_commit(), "python": sys.version.split()[0], - "libraries": { - lib: version(lib) for lib in ("numpy", "scipy", "pandas", "pyarrow") - }, + "libraries": libraries, "rows": int(len(df)), "results_sha256": _content_hash(results_path), } + if extra_manifest: + manifest.update(extra_manifest) (out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2)) diff --git a/src/neural/__init__.py b/src/neural/__init__.py new file mode 100644 index 0000000..30efddb --- /dev/null +++ b/src/neural/__init__.py @@ -0,0 +1,13 @@ +"""Layer 1.5 — the architecture-general neural existence proof. + +Realises the Layer-1 (``knowledge``) Wright-Fisher abstractions in *real trained +generative models* on a fully-synthetic sandbox whose ground-truth ``p*`` is known +exactly. A model's knowledge is measured as its output distribution over ``K`` discrete +*modes* (via an oracle), so the same metrics (``knowledge.metrics``), the same closed +forms, and the same experiments carry over — a neural collapse curve can be overlaid on +a Layer-1 analytic curve. + +The package is staged by cost: the histogram model (pure NumPy) reduces this layer +*exactly* to Layer 1 and is the validation bridge; the RNN/VAE/MLP models (torch, added +from Stage C) show that collapse is architecture-general. +""" diff --git a/src/neural/config.py b/src/neural/config.py new file mode 100644 index 0000000..0c5d48b --- /dev/null +++ b/src/neural/config.py @@ -0,0 +1,124 @@ +"""Resolved run configuration for a neural (Layer 1.5) lineage. + +Mirrors the ``knowledge.config`` idiom exactly: frozen dataclasses with a ``from_dict`` +that fills defaults and rejects unknown keys via ``knowledge.config._sub``. The grounding, +re-mint, and metrics blocks are *reused verbatim* from ``knowledge.config`` so the neural +runner speaks the same schema as Layer 1 (grounding ``m``, the ``g -> m`` conversion, the +re-mint gate, and the KL/support floors are all identical). Only the data source +(``synthetic``) and the model (``model``) are neural-specific. +""" + +from __future__ import annotations + +import math +from dataclasses import dataclass, field, replace +from typing import Any, Mapping + +from knowledge.config import GroundingCfg, MetricsCfg, RemintCfg, _sub + + +@dataclass(frozen=True) +class SyntheticCfg: + """The fully-synthetic mode-truth and observation grammar. + + The first seven fields are the Layer-1 ``TruthCfg`` knobs (they build ``p*`` over the + ``K`` modes via ``knowledge.truth.make_true_distribution``). The remaining fields + define how a mode is rendered to a categorical token sequence: an *identity* segment + that encodes the mode losslessly (read by the exact oracle) followed by a *style* + segment of within-mode stochastic tokens (so a real generative model has a + distribution to learn, not just a lookup table). + """ + + K: int + R: int = 1 + tail: str = "zipf" + zipf_s: float = 1.1 + tail_frac: float = 0.5 + tail_threshold: float = 1e-3 + init: str = "uniform" # initial p_0 over modes: {uniform, truth} + style_len: int = 4 # style-segment length (within-mode entropy) + style_vocab: int = 6 # style token alphabet size + id_base: int = 2 # identity segment encodes the mode in this base + + @property + def id_len(self) -> int: + """Identity-segment length: fewest base-``id_base`` digits to index ``K`` modes.""" + if self.K <= 1: + return 1 + return max(1, math.ceil(math.log(self.K, self.id_base))) + + @property + def vocab(self) -> int: + """Token alphabet size (shared by identity and style segments).""" + return max(self.id_base, self.style_vocab) + + @property + def seq_len(self) -> int: + """Total observation length in tokens.""" + return self.id_len + self.style_len + + +@dataclass(frozen=True) +class ModelCfg: + """The generative learner. ``kind`` selects the architecture behind a thin adapter. + + Neural hyperparameters are ignored by the ``histogram`` bridge model. + """ + + kind: str = "histogram" # {histogram, rnn, vae, mlp} + hidden: int = 64 + embed: int = 16 + epochs: int = 30 + lr: float = 1.0e-3 + batch_size: int = 256 + device: str = "auto" # {auto, cpu, cuda} + latent: int = 16 # VAE latent dimension (VAE only) + beta: float = 1.0 # VAE KL weight (VAE only) + # Samples used to estimate a neural model's mode distribution by generate-and-classify + # (ignored by the exact histogram bridge). Larger -> less measurement noise on p_hat. + n_eval: int = 8000 + + +@dataclass(frozen=True) +class NeuralDynamicsCfg: + """Generational dynamics: drift strength ``n`` + reused grounding/re-mint blocks.""" + + n: int = 4000 # pupil training-sample size (drift strength ~ 1/n) + grounding: GroundingCfg = field(default_factory=GroundingCfg) + remint: RemintCfg = field(default_factory=RemintCfg) + + +@dataclass(frozen=True) +class NeuralLineageCfg: + """A fully-resolved neural lineage configuration.""" + + synthetic: SyntheticCfg + model: ModelCfg = field(default_factory=ModelCfg) + dynamics: NeuralDynamicsCfg = field(default_factory=NeuralDynamicsCfg) + generations: int = 30 + metrics: MetricsCfg = field(default_factory=MetricsCfg) + + @staticmethod + def from_dict(cfg: Mapping[str, Any]) -> "NeuralLineageCfg": + """Build a validated NeuralLineageCfg from a nested mapping, filling defaults.""" + if isinstance(cfg, NeuralLineageCfg): + return cfg + synthetic = _sub(cfg.get("synthetic", {}), SyntheticCfg) + model = _sub(cfg.get("model", {}), ModelCfg) + dyn_raw = dict(cfg.get("dynamics", {})) + dynamics = NeuralDynamicsCfg( + n=dyn_raw.get("n", NeuralDynamicsCfg.n), + grounding=_sub(dyn_raw.get("grounding", {}), GroundingCfg), + remint=_sub(dyn_raw.get("remint", {}), RemintCfg), + ) + metrics = _sub(cfg.get("metrics", {}), MetricsCfg) + return NeuralLineageCfg( + synthetic=synthetic, + model=model, + dynamics=dynamics, + generations=int(cfg.get("generations", NeuralLineageCfg.generations)), + metrics=metrics, + ) + + def replace(self, **kw) -> "NeuralLineageCfg": + return replace(self, **kw) diff --git a/src/neural/evaluate.py b/src/neural/evaluate.py new file mode 100644 index 0000000..d8af531 --- /dev/null +++ b/src/neural/evaluate.py @@ -0,0 +1,77 @@ +"""Metrics for a neural lineage — the *same* row schema as ``knowledge.lineage``. + +``measure_metrics`` takes a model's oracle-measured mode distribution ``p_hat`` and emits a +row with exactly the columns Layer 1 logs per generation (``knowledge.lineage.record``), +computed with the *same* ``knowledge.metrics`` functions. Identical columns are what let a +neural collapse curve be plotted on top of an analytic one, and let the same figure and +analysis code (``knowledge.analysis``) run unchanged. +""" + +from __future__ import annotations + +import numpy as np + +from knowledge.config import MetricsCfg +from knowledge.lineage import N_BANDS +from knowledge.metrics import ( + forward_kl, + heterozygosity, + per_region, + support_size, + tail_band_metrics, + tail_mass, +) + + +def measure_metrics(p: np.ndarray, p_star_orig: np.ndarray, tail_mask: np.ndarray, + regions: np.ndarray, R: int, metrics_cfg: MetricsCfg) -> dict: + """Compute every per-generation metric for a measured mode distribution. + + Mirrors ``knowledge.lineage.record`` field-for-field. ``forward_kl`` and the tail set + are always measured against the *original* truth, so a re-minted lineage that has lost + tails is penalised exactly as in Layer 1's E6. + + Args: + p (np.ndarray): The model's measured mode distribution ``p_hat`` (length ``K``). + p_star_orig (np.ndarray): The original true distribution over modes. + tail_mask (np.ndarray): Boolean tail mask on the original truth. + regions (np.ndarray): Length-``K`` region index per mode. + R (int): Number of regions. + metrics_cfg (MetricsCfg): KL floor and support epsilon. + + Returns: + dict: One row of metrics (no ``generation``/label columns; the runner adds those). + """ + eps = metrics_cfg.support_eps + kl_floor = metrics_cfg.kl_floor + head_mask = ~tail_mask + n_tail = int(tail_mask.sum()) + n_head = int(head_mask.sum()) + + row = { + "heterozygosity": heterozygosity(p), + "forward_kl": forward_kl(p_star_orig, p, kl_floor), + "tail_mass": tail_mass(p, tail_mask), + "support_size": support_size(p, eps), + "tail_support": int(np.sum(p[tail_mask] > eps)), + "head_support": int(np.sum(p[head_mask] > eps)), + "tail_frac_alive": (float(np.mean(p[tail_mask] > eps)) if n_tail else 0.0), + "head_frac_alive": (float(np.mean(p[head_mask] > eps)) if n_head else 0.0), + "tail_truth_mass_alive": ( + float(p_star_orig[tail_mask][p[tail_mask] > eps].sum() + / p_star_orig[tail_mask].sum()) if n_tail else 0.0), + } + if n_tail >= N_BANDS: + fa, _ = tail_band_metrics(p, p_star_orig, tail_mask, n_bands=N_BANDS, alive_eps=eps) + for b in range(N_BANDS): + row[f"band{b}_alive"] = fa[b] + if R > 1: + for r, v in per_region(heterozygosity, p, regions).items(): + row[f"H_region_{r}"] = v + for r, v in per_region(tail_mass, p, regions, tail_mask).items(): + row[f"tail_region_{r}"] = v + for r in range(R): + region_tail = (regions == r) & tail_mask + row[f"tailalive_region_{r}"] = ( + float(np.mean(p[region_tail] > eps)) if region_tail.any() else 0.0) + return row diff --git a/src/neural/experiment.py b/src/neural/experiment.py new file mode 100644 index 0000000..576f583 --- /dev/null +++ b/src/neural/experiment.py @@ -0,0 +1,124 @@ +"""Neural (Layer 1.5) experiment runner: sweep a grid x replicates, write artifacts. + +Mirrors ``knowledge.experiment`` and reuses its sweep-expansion primitives +(``_apply_param`` — including the ``g -> m`` conversion — and ``_set_by_path``), its +provenance helpers, and its output contract (``save_artifacts``). Only the per-run call and +the config key set differ: a neural run trains generative models rather than resampling a +frequency vector, and its config groups are ``synthetic``/``model``/``dynamics``/... . + +CLI: python -m neural.experiment configs/neural/N0.yaml +""" + +from __future__ import annotations + +import argparse +import copy +import itertools +from pathlib import Path +from typing import Any + +import pandas as pd +import yaml + +from knowledge.experiment import _apply_param, save_artifacts +from knowledge.seeding import spawn_seeds + +from .generation_loop import run_generative_lineage + +# Config groups that make up a single neural lineage (everything else is experiment-level). +_NEURAL_KEYS = ("synthetic", "model", "dynamics", "generations", "metrics", "n_eval") + +# Libraries recorded in the manifest on top of the Layer-1 core set (skipped if absent). +_EXTRA_LIBS = ("torch", "torchvision") + + +def expand_sweeps(cfg: dict) -> list[tuple[dict, dict]]: + """Expand the sweep grid into (label, resolved_neural_cfg) pairs. + + Identical semantics to ``knowledge.experiment.expand_sweeps`` (Cartesian product of the + declared ``{param, values}`` entries, reusing ``_apply_param`` for the ``g -> m`` and + ``arm`` special cases) but assembling the base from the neural config groups. + + Returns: + list[tuple[dict, dict]]: One (label-columns, neural-config) pair per grid point. + """ + base = {k: copy.deepcopy(cfg[k]) for k in _NEURAL_KEYS if k in cfg} + sweeps = cfg.get("sweep", []) + if isinstance(sweeps, dict): + sweeps = [sweeps] + if not sweeps: + return [({}, base)] + params = [s["param"] for s in sweeps] + value_lists = [list(s["values"]) for s in sweeps] + combos: list[tuple[dict, dict]] = [] + for values in itertools.product(*value_lists): + lin = copy.deepcopy(base) + label: dict = {} + for param, val in zip(params, values): + label.update(_apply_param(lin, param, val)) + combos.append((label, lin)) + return combos + + +def run_experiment(cfg: dict) -> pd.DataFrame: + """Run every grid point x every replicate; return long-form results. + + Replicate seeds are derived once from the master seed and reused across grid points, so + comparisons across sweep values are paired (shared drift noise) — as in Layer 1. + + Args: + cfg (dict): Parsed experiment YAML. + + Returns: + pd.DataFrame: One row per (combo, replicate, generation). + """ + name = cfg["experiment"] + master = int(cfg["seed"]) + n_rep = int(cfg["n_replicates"]) + combos = expand_sweeps(cfg) + seeds = spawn_seeds(master, n_rep) + + frames: list[pd.DataFrame] = [] + for label, neural_cfg in combos: + for rep, ss in enumerate(seeds): + df = run_generative_lineage(neural_cfg, int(ss.generate_state(1)[0])) + for col, val in label.items(): + df[col] = val + df["replicate"] = rep + frames.append(df) + out = pd.concat(frames, ignore_index=True) + out.insert(0, "experiment", name) + return out + + +def run_and_save(config_path: str | Path) -> Path: + """Load a neural experiment YAML, run it, and write artifacts. Returns the output dir.""" + config_path = Path(config_path) + cfg = yaml.safe_load(config_path.read_text()) + out_dir = Path(cfg.get("output", {}).get("dir", f"results/{cfg['experiment']}")) + kind = cfg.get("kind", "gen_lineage") + if kind == "recombination": + from .recombine import run_recombination # Stage C (N4); imported lazily + df = run_recombination(cfg) + grid = None + elif kind == "gen_lineage": + df = run_experiment(cfg) + grid = [{"label": label, "neural_cfg": c} for label, c in expand_sweeps(cfg)] + else: + raise ValueError(f"unknown neural experiment kind {kind!r}") + model_kind = cfg.get("model", {}).get("kind", "histogram") + save_artifacts(cfg, df, out_dir, extra_libs=_EXTRA_LIBS, + extra_manifest={"layer": "1.5", "model_kind": model_kind}, grid=grid) + return out_dir + + +def main(argv: list[str] | None = None) -> None: + parser = argparse.ArgumentParser(description="Run a Layer-1.5 neural experiment from a YAML config.") + parser.add_argument("config", help="Path to configs/neural/NX.yaml") + args = parser.parse_args(argv) + out_dir = run_and_save(args.config) + print(f"wrote artifacts to {out_dir}/") + + +if __name__ == "__main__": + main() diff --git a/src/neural/generation_loop.py b/src/neural/generation_loop.py new file mode 100644 index 0000000..975f166 --- /dev/null +++ b/src/neural/generation_loop.py @@ -0,0 +1,113 @@ +"""The neural analogue of ``knowledge.lineage.run_lineage``. + +Runs ``T`` generations of *train-a-model-on-the-previous-model's-samples*, the neural +image of the Wright-Fisher generational step. Each generation the pupil is trained on a +pool of (i) ``n`` observations drawn from the parent model (drift) and (ii) ``m`` fresh +observations drawn from the grounding reference (immigration, ``g = m/(n+m)``), then its +oracle-measured mode distribution is logged with the *same* metric schema Layer 1 uses. +Grounding structure (proportional / uniform / matched over regions) and the re-mint gate +reuse ``knowledge.step`` and mirror ``run_lineage`` exactly, so a histogram-model lineage +reproduces the analytic core and a neural-model lineage tests whether the same signs hold +in real weights. +""" + +from __future__ import annotations + +from typing import Any, Mapping + +import numpy as np +import pandas as pd + +from knowledge.metrics import heterozygosity +from knowledge.step import allocate_m, structured_multinomial +from knowledge.truth import uniform_init + +from .config import NeuralLineageCfg +from .evaluate import measure_metrics +from .models import make_model +from .oracle import ExactOracle +from .synthetic import id_codewords, make_mode_truth, render_modes + + +def _counts_to_observations(counts: np.ndarray, cfg, rng, codewords) -> np.ndarray: + """Expand a per-mode count vector into rendered token sequences.""" + modes = np.repeat(np.arange(counts.size), counts) + return render_modes(modes, cfg, rng, codewords) + + +def run_generative_lineage(cfg: Mapping[str, Any] | NeuralLineageCfg, + seed: int) -> pd.DataFrame: + """Run one neural lineage and return per-generation metrics. + + Args: + cfg (Mapping | NeuralLineageCfg): Resolved neural-lineage configuration. + seed (int): Seed for this replicate; the run is a pure function of (cfg, seed) for + the histogram model (statistically reproducible for torch models). + + Returns: + pd.DataFrame: One row per generation 0..T with the same metric columns as + ``knowledge.lineage.run_lineage``. + """ + cfg = NeuralLineageCfg.from_dict(cfg) + syn = cfg.synthetic + td = make_mode_truth(syn) + p_star_orig = td.p_star # forward_kl is always vs the original truth + regions = td.regions + tail_mask = td.tail_mask + R = syn.R + + rng = np.random.default_rng(seed) + oracle = ExactOracle(syn) + codewords = id_codewords(syn) + + # Initial distribution over modes (exact, like Layer 1). + if syn.init == "uniform": + p0 = uniform_init(syn.K) + elif syn.init == "truth": + p0 = p_star_orig.copy() + else: + raise ValueError(f"unknown init {syn.init!r} (expected uniform|truth)") + + # Grounding wiring (reused verbatim from Layer 1). + grounding = cfg.dynamics.grounding + exercised = np.asarray(grounding.exercised) if grounding.exercised is not None else None + m_vector = allocate_m(grounding.m, R, grounding.policy, exercised) + p_star_eff = p_star_orig.copy() # grounding reference; may be re-minted (N6) + remint = cfg.dynamics.remint + n = cfg.dynamics.n + + model = make_model(cfg.model, syn, oracle) + model.initialise(p0, rng) + + rows: list[dict] = [] + + def record(t: int, p: np.ndarray) -> None: + row = {"generation": t} + row.update(measure_metrics(p, p_star_orig, tail_mask, regions, R, cfg.metrics)) + rows.append(row) + + record(0, model.mode_distribution(rng)) + + for t in range(1, cfg.generations + 1): + X_syn = model.sample(n, rng) # drift: n from the parent + if m_vector is not None: # immigration: m grounded samples + counts_real = structured_multinomial( + m_vector, p_star_eff, regions, grounding.policy, rng) + X_real = _counts_to_observations(counts_real, syn, rng, codewords) + pool = np.concatenate([X_syn, X_real], axis=0) + else: + pool = X_syn + + pupil = make_model(cfg.model, syn, oracle) + pupil.fit(pool, rng) + model = pupil + p = model.mode_distribution(rng) + + if remint.enabled and remint.period and t % remint.period == 0: + # Founder event: current distribution becomes the new grounding reference and + # the original truth is discarded for grounding. Gated on diversity (N6). + if remint.H_gate is None or heterozygosity(p) >= remint.H_gate: + p_star_eff = p.copy() + record(t, p) + + return pd.DataFrame(rows) diff --git a/src/neural/models.py b/src/neural/models.py new file mode 100644 index 0000000..6a69fe6 --- /dev/null +++ b/src/neural/models.py @@ -0,0 +1,119 @@ +"""Generative models behind a thin adapter, so architecture is a config switch. + +Every model implements the same three-method protocol: ``fit`` on a batch of token +sequences, ``sample`` fresh token sequences, and report its ``mode_distribution`` (the +model's ``p_t``). Keeping the interface identical is what makes "collapse is +architecture-general" (experiment N5) a single sweep over ``model.kind``. + +``HistogramModel`` is the bridge: its ``fit`` is a maximum-likelihood mode histogram and +its ``sample`` is a multinomial draw, so a lineage of histogram models is *exactly* +neutral Wright-Fisher drift with immigration — the analytic core in disguise. The +torch-backed RNN/VAE/MLP models are added in Stage C and reuse this same protocol. +""" + +from __future__ import annotations + +from typing import Protocol, runtime_checkable + +import numpy as np + +from .config import ModelCfg, SyntheticCfg +from .oracle import Oracle, measure_distribution +from .synthetic import id_codewords, render_modes + + +@runtime_checkable +class GenerativeModel(Protocol): + """A learner of ``p(x)`` over the synthetic observation space.""" + + def initialise(self, p0: np.ndarray, rng: np.random.Generator) -> None: + """Initialise generation 0 to represent the mode distribution ``p0``. + + The histogram bridge sets ``p0`` exactly (matching Layer 1's exact ``p_0`` start); + a neural model trains on a sample drawn from ``p0`` (its gen-0 fidelity is checked + by the Stage-C fidelity gate). + """ + ... + + def fit(self, X: np.ndarray, rng: np.random.Generator) -> None: + """Train (from scratch) on a batch of token sequences ``X``.""" + ... + + def sample(self, n: int, rng: np.random.Generator) -> np.ndarray: + """Draw ``n`` fresh token sequences of shape ``(n, seq_len)``.""" + ... + + def mode_distribution(self, rng: np.random.Generator) -> np.ndarray: + """Return the model's length-``K`` distribution over modes (its ``p_t``).""" + ... + + +class HistogramModel: + """MLE mode-histogram generator — reduces Layer 1.5 exactly to Layer 1. + + ``fit`` counts oracle-labelled modes in the training pool and stores the empirical + distribution; ``sample`` draws modes multinomially and renders them; the stored + distribution *is* the model's ``p_t`` (read exactly, no eval-sampling noise). Composed + over generations this is neutral Wright-Fisher drift with immigration. + + Args: + cfg (SyntheticCfg): The synthetic grammar (for ``K`` and rendering). + oracle (Oracle): The mode adjudicator used to label the training pool. + model_cfg (ModelCfg): Present for interface symmetry; unused by the histogram. + """ + + def __init__(self, cfg: SyntheticCfg, oracle: Oracle, + model_cfg: ModelCfg | None = None) -> None: + self.cfg = cfg + self.oracle = oracle + self._codewords = id_codewords(cfg) + self._p: np.ndarray | None = None + + def initialise(self, p0: np.ndarray, rng: np.random.Generator) -> None: + """Set the stored distribution to ``p0`` exactly (no gen-0 sampling noise).""" + p0 = np.asarray(p0, dtype=float) + self._p = p0 / p0.sum() + + def fit(self, X: np.ndarray, rng: np.random.Generator) -> None: + """Store the empirical mode distribution of the (oracle-labelled) pool ``X``.""" + self._p = measure_distribution(X, self.oracle, self.cfg.K) + + def sample(self, n: int, rng: np.random.Generator) -> np.ndarray: + """Draw ``n`` observations whose modes follow the stored distribution.""" + if self._p is None: + raise RuntimeError("HistogramModel.sample called before fit") + counts = rng.multinomial(n, self._p) + modes = np.repeat(np.arange(self.cfg.K), counts) + return render_modes(modes, self.cfg, rng, self._codewords) + + def mode_distribution(self, rng: np.random.Generator) -> np.ndarray: + """Return the stored mode distribution (exact; no eval sampling).""" + if self._p is None: + raise RuntimeError("HistogramModel.mode_distribution called before fit") + return self._p.copy() + + +def make_model(model_cfg: ModelCfg, cfg: SyntheticCfg, oracle: Oracle) -> GenerativeModel: + """Construct a generative model of the requested ``kind``. + + Args: + model_cfg (ModelCfg): Selects the architecture and its hyperparameters. + cfg (SyntheticCfg): The synthetic grammar. + oracle (Oracle): The mode adjudicator (needed by the histogram bridge; the neural + models estimate their mode distribution by generate-and-classify). + + Returns: + GenerativeModel: A fresh, untrained model. + + Raises: + ValueError: If ``kind`` is unknown. + """ + kind = model_cfg.kind + if kind == "histogram": + return HistogramModel(cfg, oracle, model_cfg) + if kind in ("rnn", "vae", "mlp"): + # Torch-backed models arrive in Stage C; imported lazily so Stages A-B need no GPU. + from .torch_models import make_torch_model # noqa: PLC0415 + + return make_torch_model(model_cfg, cfg, oracle) + raise ValueError(f"unknown model kind {kind!r} (expected histogram|rnn|vae|mlp)") diff --git a/src/neural/oracle.py b/src/neural/oracle.py new file mode 100644 index 0000000..f07d953 --- /dev/null +++ b/src/neural/oracle.py @@ -0,0 +1,82 @@ +"""The oracle — the neural analogue of Layer 1's "reality's no". + +An oracle maps an observation to the mode it belongs to. For the fully-synthetic sandbox +the oracle is **exact** (it decodes the lossless identity segment), so the measured mode +distribution ``p_hat`` carries zero measurement noise — this is what lets a trained model's +collapse be read directly against the known ``p*``. (The MNIST tier will add a +``ClassifierOracle`` wrapping a frozen network plus its confusion matrix; that arrives with +Stage C and is confirmation-only.) +""" + +from __future__ import annotations + +from typing import Protocol, runtime_checkable + +import numpy as np + +from .config import SyntheticCfg + + +@runtime_checkable +class Oracle(Protocol): + """Adjudicates which mode an observation belongs to.""" + + def classify(self, X: np.ndarray) -> np.ndarray: + """Return the length-``n`` mode index for each row of ``X``.""" + ... + + +class ExactOracle: + """Zero-error oracle for the synthetic sandbox: decodes the identity segment. + + Args: + cfg (SyntheticCfg): The synthetic configuration whose grammar produced ``X``. + """ + + def __init__(self, cfg: SyntheticCfg) -> None: + self.cfg = cfg + self.id_len = cfg.id_len + self.base = cfg.id_base + self.K = cfg.K + # Positional weights for base-`base` decoding, most-significant digit first. + self._weights = self.base ** np.arange(self.id_len - 1, -1, -1, dtype=np.int64) + + def classify(self, X: np.ndarray) -> np.ndarray: + """Decode mode indices from the identity segment of each observation. + + Args: + X (np.ndarray): Token sequences of shape ``(n, seq_len)``. + + Returns: + np.ndarray: Length-``n`` decoded indices. Grammar-valid data always decodes into + ``[0, K)``; a *neural* model may emit an invalid codeword that decodes to + ``>= K`` — such samples are dropped by :func:`measure_distribution` rather than + being clipped onto a real mode (which would bias ``p_hat``). + """ + X = np.asarray(X, dtype=np.int64) + ident = X[:, : self.id_len] + return ident @ self._weights + + +def measure_distribution(X: np.ndarray, oracle: Oracle, K: int) -> np.ndarray: + """Measure the empirical mode distribution ``p_hat`` of a sample. + + This is the neural readout of ``p_t``: classify every observation and normalise the + mode histogram. Modes absent from ``X`` receive zero mass (support shrinks exactly as + in Layer 1). Invalid codewords (decoded index ``>= K``, only producible by a neural + model) are dropped, so ``p_hat`` is renormalised over grammar-valid samples. + + Args: + X (np.ndarray): Token sequences of shape ``(n, seq_len)``. + oracle (Oracle): The mode adjudicator. + K (int): Number of modes. + + Returns: + np.ndarray: Length-``K`` probability vector summing to 1. + """ + modes = oracle.classify(X) + counts = np.bincount(modes, minlength=K)[:K].astype(float) + total = counts.sum() + if total <= 0: + raise ValueError("measure_distribution received an empty sample") + return counts / total diff --git a/src/neural/synthetic.py b/src/neural/synthetic.py new file mode 100644 index 0000000..4833fc7 --- /dev/null +++ b/src/neural/synthetic.py @@ -0,0 +1,109 @@ +"""The fully-synthetic sandbox: a known ``p*`` over modes + a lossless observation grammar. + +The mode-truth (``p*``, regions, tail mask) comes straight from Layer 1's +``knowledge.truth.make_true_distribution`` — so "mode", "region", and "tail" are *the same +objects* as in the analytic core. Each mode is rendered to a categorical token sequence: + +* an **identity** segment of ``id_len`` base-``id_base`` digits that encodes the mode + index exactly (the exact oracle reads these back with zero error), and +* a **style** segment of ``style_len`` tokens drawn uniformly at random, giving genuine + within-mode entropy so a real generative model must learn a *distribution* ``p(x|mode)`` + rather than memorise ``K`` fixed strings. + +Because the identity segment is lossless, the measured mode distribution ``p_hat`` is a +noise-free readout of the model's output — the property that lets the histogram model +reduce this layer exactly to Wright-Fisher drift. +""" + +from __future__ import annotations + +import numpy as np + +from knowledge.truth import TrueDist, make_true_distribution + +from .config import SyntheticCfg + + +def make_mode_truth(cfg: SyntheticCfg) -> TrueDist: + """Build the true distribution over modes (thin wrapper over Layer 1's truth). + + Args: + cfg (SyntheticCfg): The synthetic configuration (its first seven fields are the + Layer-1 ``TruthCfg`` knobs). + + Returns: + TrueDist: ``p_star`` (length ``K``), ``regions``, and ``tail_mask`` over modes. + """ + return make_true_distribution( + cfg.K, cfg.R, cfg.tail, cfg.tail_frac, cfg.zipf_s, 0, + tail_threshold=cfg.tail_threshold, + ) + + +def id_codewords(cfg: SyntheticCfg) -> np.ndarray: + """Return the ``(K, id_len)`` matrix of base-``id_base`` identity codewords. + + Codeword of mode ``k`` is ``k`` written in base ``id_base``, most-significant digit + first, zero-padded to ``id_len``. Deterministic and invertible. + + Args: + cfg (SyntheticCfg): The synthetic configuration. + + Returns: + np.ndarray: Integer array of shape ``(K, id_len)`` with tokens in + ``[0, id_base)``. + """ + k = np.arange(cfg.K, dtype=np.int64) + id_len, base = cfg.id_len, cfg.id_base + digits = np.empty((cfg.K, id_len), dtype=np.int64) + for pos in range(id_len - 1, -1, -1): # least-significant digit last + digits[:, pos] = k % base + k //= base + return digits + + +def render_modes(modes: np.ndarray, cfg: SyntheticCfg, rng: np.random.Generator, + codewords: np.ndarray | None = None) -> np.ndarray: + """Render an array of mode indices to token sequences. + + Args: + modes (np.ndarray): Length-``n`` integer array of mode indices in ``[0, K)``. + cfg (SyntheticCfg): The synthetic configuration. + rng (np.random.Generator): Random source for the style segment. + codewords (np.ndarray | None): Optional precomputed identity codewords. + + Returns: + np.ndarray: Integer array of shape ``(n, seq_len)`` — identity segment followed by + a freshly-sampled style segment. + """ + modes = np.asarray(modes, dtype=np.int64) + if codewords is None: + codewords = id_codewords(cfg) + ident = codewords[modes] # (n, id_len) + style = rng.integers(0, cfg.style_vocab, size=(modes.shape[0], cfg.style_len)) + return np.concatenate([ident, style], axis=1) + + +def sample_synthetic(p_over_modes: np.ndarray, n: int, cfg: SyntheticCfg, + rng: np.random.Generator, + codewords: np.ndarray | None = None) -> tuple[np.ndarray, np.ndarray]: + """Draw ``n`` observations whose modes follow ``p_over_modes``. + + This is the *grounding* generator (draw from a fixed distribution and render) and the + reference sampler used to seed generation 0. + + Args: + p_over_modes (np.ndarray): Distribution over the ``K`` modes to sample from. + n (int): Number of observations. + cfg (SyntheticCfg): The synthetic configuration. + rng (np.random.Generator): Random source. + codewords (np.ndarray | None): Optional precomputed identity codewords. + + Returns: + tuple[np.ndarray, np.ndarray]: ``(X, modes)`` — token sequences of shape + ``(n, seq_len)`` and the length-``n`` true mode indices. + """ + p = np.asarray(p_over_modes, dtype=float) + modes = rng.choice(cfg.K, size=n, p=p / p.sum()) + X = render_modes(modes, cfg, rng, codewords) + return X, modes diff --git a/src/neural/torch_mlp.py b/src/neural/torch_mlp.py new file mode 100644 index 0000000..3326224 --- /dev/null +++ b/src/neural/torch_mlp.py @@ -0,0 +1,109 @@ +"""Autoregressive MLP generative model (Stage C, for the N5 architecture-generality axis). + +A causal feed-forward next-token model: token ``i`` is predicted from the concatenated +(causally-masked) embeddings of all earlier tokens. Deliberately a *different* inductive +bias from the GRU — if collapse appears here too, it is a property of the transmission +operator, not of any one architecture. +""" + +from __future__ import annotations + +import numpy as np + +from .config import ModelCfg, SyntheticCfg +from .oracle import Oracle +from .torch_models import _BaseTorchGenerator +from .train import device_generator, seed_everything + + +def _make_mlp_net(V: int, L: int, embed: int, hidden: int): + import torch + import torch.nn as nn + + class MLPNet(nn.Module): + """Predict every position from a causally-masked flatten of prior embeddings.""" + + def __init__(self) -> None: + super().__init__() + self.V, self.L, self.E = V, L, embed + self.bos = V + self.embed = nn.Embedding(V + 1, embed) + self.net = nn.Sequential( + nn.Linear(L * embed, hidden), nn.ReLU(), + nn.Linear(hidden, hidden), nn.ReLU(), + nn.Linear(hidden, V), + ) + # lower-triangular INCLUSIVE mask over input positions: position i sees inputs + # 0..i (the input is already shifted by one, so this is strictly causal on x). + mask = torch.tril(torch.ones(L, L)) + self.register_buffer("mask", mask) + + def _context(self, inp): # inp: (B, L) input tokens + B = inp.shape[0] + emb = self.embed(inp) # (B, L, E) + m = self.mask.to(emb.dtype) # (L, L) + # ctx[b, i] = concat_j ( emb[b, j] * mask[i, j] ) -> (B, L, L*E) + ctx = emb.unsqueeze(1) * m.unsqueeze(0).unsqueeze(-1) # (B, L, L, E) + return ctx.reshape(B, self.L, self.L * self.E) + + def forward(self, x): # x: (B, L) targets + B = x.shape[0] + bos = torch.full((B, 1), self.bos, dtype=torch.long, device=x.device) + inp = torch.cat([bos, x[:, :-1]], dim=1) + ctx = self._context(inp) + return self.net(ctx) # (B, L, V) + + def step_logits(self, prefix): # prefix: (B, pos) tokens so far + """Logits for the next token given the tokens generated so far.""" + B, pos = prefix.shape + bos = torch.full((B, 1), self.bos, dtype=torch.long, device=prefix.device) + inp = torch.cat([bos, prefix], dim=1)[:, : self.L] # (B, <=L) + if inp.shape[1] < self.L: + pad = torch.zeros((B, self.L - inp.shape[1]), dtype=torch.long, + device=prefix.device) + inp = torch.cat([inp, pad], dim=1) + ctx = self._context(inp) # (B, L, L*E) + return self.net(ctx[:, pos, :]) # logits at position `pos` + + return MLPNet() + + +class MLPGenerator(_BaseTorchGenerator): + """Autoregressive feed-forward generative model over token sequences.""" + + def fit(self, X: np.ndarray, rng: np.random.Generator) -> None: + import torch + + g = seed_everything(int(rng.integers(2 ** 31))) + net = _make_mlp_net(self.V, self.L, self.mcfg.embed, self.mcfg.hidden).to(self.device) + net.train() + opt = torch.optim.Adam(net.parameters(), lr=self.mcfg.lr) + loss_fn = torch.nn.CrossEntropyLoss() + data = torch.as_tensor(np.asarray(X), dtype=torch.long, device=self.device) + n, bs = data.shape[0], self.mcfg.batch_size + for _ in range(self.mcfg.epochs): + perm = torch.randperm(n, generator=g).to(self.device) + for i in range(0, n, bs): + batch = data[perm[i:i + bs]] + logits = net(batch) + loss = loss_fn(logits.reshape(-1, self.V), batch.reshape(-1)) + opt.zero_grad() + loss.backward() + opt.step() + net.eval() + self.net = net + + def sample(self, n: int, rng: np.random.Generator) -> np.ndarray: + import torch + + if self.net is None: + raise RuntimeError("MLPGenerator.sample called before fit") + g = device_generator(int(rng.integers(2 ** 31)), self.device) + prefix = torch.empty((n, 0), dtype=torch.long, device=self.device) + with torch.no_grad(): + for pos in range(self.L): + logits = self.net.step_logits(prefix) + probs = torch.softmax(logits, dim=-1) + tok = torch.multinomial(probs, 1, generator=g) + prefix = torch.cat([prefix, tok], dim=1) + return prefix.cpu().numpy() diff --git a/src/neural/torch_models.py b/src/neural/torch_models.py new file mode 100644 index 0000000..7d44143 --- /dev/null +++ b/src/neural/torch_models.py @@ -0,0 +1,146 @@ +"""Torch-backed generative models over the synthetic token grammar (Stage C). + +Each model implements the same ``GenerativeModel`` protocol as the histogram bridge +(``initialise`` / ``fit`` / ``sample`` / ``mode_distribution``), so a lineage is +architecture-agnostic and N5 is a single sweep over ``model.kind``. Unlike the histogram +model, a neural model's ``mode_distribution`` is *estimated* by generate-and-classify +(``n_eval`` samples), which is the honest, slightly-noisy neural readout of ``p_t``. + +Implemented so far: ``RNNGenerator`` (autoregressive GRU). ``VAEGenerator`` and +``MLPGenerator`` follow and reuse the shared measure/initialise helpers. +""" + +from __future__ import annotations + +import numpy as np + +from .config import ModelCfg, SyntheticCfg +from .oracle import Oracle, measure_distribution +from .synthetic import sample_synthetic +from .train import device_generator, resolve_device, seed_everything, set_determinism + + +def _measure(model, rng: np.random.Generator, oracle: Oracle, K: int, n_eval: int) -> np.ndarray: + """Estimate a model's mode distribution by generate-and-classify.""" + X = model.sample(n_eval, rng) + return measure_distribution(X, oracle, K) + + +class _BaseTorchGenerator: + """Shared plumbing: device, gen-0 initialisation, and mode measurement.""" + + def __init__(self, cfg: SyntheticCfg, model_cfg: ModelCfg, oracle: Oracle) -> None: + self.cfg = cfg + self.mcfg = model_cfg + self.oracle = oracle + self.device = resolve_device(model_cfg.device) + self.V = cfg.vocab + self.L = cfg.seq_len + self.net = None + set_determinism() + + def initialise(self, p0: np.ndarray, rng: np.random.Generator) -> None: + """Train generation 0 on a sample drawn from ``p0`` (fidelity-gated in Stage C).""" + n_init = max(self.mcfg.n_eval, 4000) + X, _ = sample_synthetic(p0, n_init, self.cfg, rng) + self.fit(X, rng) + + def mode_distribution(self, rng: np.random.Generator) -> np.ndarray: + return _measure(self, rng, self.oracle, self.cfg.K, self.mcfg.n_eval) + + +# --- autoregressive GRU ----------------------------------------------------------------- + +def _make_ar_net(V: int, embed: int, hidden: int): + """Build an autoregressive GRU next-token network (built lazily to avoid a torch import + at module load).""" + import torch.nn as nn + + class ARNet(nn.Module): + """Predict token ``i`` from tokens ``0..i-1`` via a GRU (BOS-prefixed).""" + + def __init__(self) -> None: + super().__init__() + self.bos = V # extra input id for the start token + self.embed = nn.Embedding(V + 1, embed) + self.gru = nn.GRU(embed, hidden, batch_first=True) + self.out = nn.Linear(hidden, V) + + def forward(self, x): # x: (B, L) target tokens + import torch + + B = x.shape[0] + bos = torch.full((B, 1), self.bos, dtype=torch.long, device=x.device) + inp = torch.cat([bos, x[:, :-1]], dim=1) # teacher forcing + h, _ = self.gru(self.embed(inp)) + return self.out(h) # (B, L, V) + + return ARNet() + + +class RNNGenerator(_BaseTorchGenerator): + """Autoregressive GRU generative model over token sequences.""" + + def _train_net(self, X, rng: np.random.Generator): + import torch + + g = seed_everything(int(rng.integers(2 ** 31))) + net = _make_ar_net(self.V, self.mcfg.embed, self.mcfg.hidden).to(self.device) + net.train() + opt = torch.optim.Adam(net.parameters(), lr=self.mcfg.lr) + loss_fn = torch.nn.CrossEntropyLoss() + data = torch.as_tensor(np.asarray(X), dtype=torch.long, device=self.device) + n = data.shape[0] + bs = self.mcfg.batch_size + for _ in range(self.mcfg.epochs): + perm = torch.randperm(n, generator=g).to(self.device) + for i in range(0, n, bs): + idx = perm[i:i + bs] + batch = data[idx] + logits = net(batch) # (b, L, V) + loss = loss_fn(logits.reshape(-1, self.V), batch.reshape(-1)) + opt.zero_grad() + loss.backward() + opt.step() + net.eval() + return net + + def fit(self, X: np.ndarray, rng: np.random.Generator) -> None: + self.net = self._train_net(X, rng) + + def sample(self, n: int, rng: np.random.Generator) -> np.ndarray: + import torch + + if self.net is None: + raise RuntimeError("RNNGenerator.sample called before fit") + g = device_generator(int(rng.integers(2 ** 31)), self.device) + out = torch.empty((n, self.L), dtype=torch.long, device=self.device) + tok = torch.full((n, 1), self.V, dtype=torch.long, device=self.device) # BOS + h = None + with torch.no_grad(): + for pos in range(self.L): + emb = self.net.embed(tok) + hid, h = self.net.gru(emb, h) + logits = self.net.out(hid[:, -1, :]) # (n, V) + probs = torch.softmax(logits, dim=-1) + tok = torch.multinomial(probs, 1, generator=g) + out[:, pos] = tok[:, 0] + return out.cpu().numpy() + + +# --- dispatch --------------------------------------------------------------------------- + +def make_torch_model(model_cfg: ModelCfg, cfg: SyntheticCfg, oracle: Oracle): + """Construct a torch generative model of the requested ``kind``.""" + kind = model_cfg.kind + if kind == "rnn": + return RNNGenerator(cfg, model_cfg, oracle) + if kind == "vae": + from .torch_vae import VAEGenerator # noqa: PLC0415 + + return VAEGenerator(cfg, model_cfg, oracle) + if kind == "mlp": + from .torch_mlp import MLPGenerator # noqa: PLC0415 + + return MLPGenerator(cfg, model_cfg, oracle) + raise ValueError(f"unknown torch model kind {kind!r}") diff --git a/src/neural/torch_vae.py b/src/neural/torch_vae.py new file mode 100644 index 0000000..0030335 --- /dev/null +++ b/src/neural/torch_vae.py @@ -0,0 +1,104 @@ +"""Sequence VAE generative model (Stage C, for the N5 architecture-generality axis). + +A GRU encoder maps a token sequence to a Gaussian latent ``z``; a GRU decoder (its initial +hidden state projected from ``z``) reconstructs the sequence. Trained by the ELBO +(reconstruction CE + ``beta`` * KL). A latent-variable generator is a third, distinct +inductive bias — and the canonical model in which generative collapse was first studied — so +its collapse under dry self-training is strong evidence the effect is operator-driven. +""" + +from __future__ import annotations + +import numpy as np + +from .config import ModelCfg, SyntheticCfg +from .oracle import Oracle +from .torch_models import _BaseTorchGenerator +from .train import device_generator, seed_everything + + +def _make_vae(V: int, L: int, embed: int, hidden: int, latent: int): + import torch + import torch.nn as nn + + class SeqVAE(nn.Module): + def __init__(self) -> None: + super().__init__() + self.V, self.L, self.bos = V, L, V + self.embed = nn.Embedding(V + 1, embed) + self.enc = nn.GRU(embed, hidden, batch_first=True) + self.to_mu = nn.Linear(hidden, latent) + self.to_lv = nn.Linear(hidden, latent) + self.z_to_h = nn.Linear(latent, hidden) + self.dec = nn.GRU(embed, hidden, batch_first=True) + self.out = nn.Linear(hidden, V) + + def encode(self, x): + _, h = self.enc(self.embed(x)) # h: (1, B, H) + h = h[-1] + return self.to_mu(h), self.to_lv(h) + + def decode_logits(self, z, x): # teacher forcing + B = x.shape[0] + bos = torch.full((B, 1), self.bos, dtype=torch.long, device=x.device) + inp = torch.cat([bos, x[:, :-1]], dim=1) + h0 = torch.tanh(self.z_to_h(z)).unsqueeze(0) # (1, B, H) + out, _ = self.dec(self.embed(inp), h0) + return self.out(out) + + def forward(self, x): + mu, lv = self.encode(x) + std = torch.exp(0.5 * lv) + z = mu + std * torch.randn_like(std) + logits = self.decode_logits(z, x) + kl = -0.5 * torch.sum(1 + lv - mu.pow(2) - lv.exp(), dim=1).mean() + return logits, kl + + return SeqVAE() + + +class VAEGenerator(_BaseTorchGenerator): + """Sequence VAE generative model over token sequences.""" + + def fit(self, X: np.ndarray, rng: np.random.Generator) -> None: + import torch + + g = seed_everything(int(rng.integers(2 ** 31))) + net = _make_vae(self.V, self.L, self.mcfg.embed, self.mcfg.hidden, + self.mcfg.latent).to(self.device) + net.train() + opt = torch.optim.Adam(net.parameters(), lr=self.mcfg.lr) + ce = torch.nn.CrossEntropyLoss() + data = torch.as_tensor(np.asarray(X), dtype=torch.long, device=self.device) + n, bs, beta = data.shape[0], self.mcfg.batch_size, self.mcfg.beta + for _ in range(self.mcfg.epochs): + perm = torch.randperm(n, generator=g).to(self.device) + for i in range(0, n, bs): + batch = data[perm[i:i + bs]] + logits, kl = net(batch) + recon = ce(logits.reshape(-1, self.V), batch.reshape(-1)) + loss = recon + beta * kl / batch.shape[0] + opt.zero_grad() + loss.backward() + opt.step() + net.eval() + self.net = net + + def sample(self, n: int, rng: np.random.Generator) -> np.ndarray: + import torch + + if self.net is None: + raise RuntimeError("VAEGenerator.sample called before fit") + g = device_generator(int(rng.integers(2 ** 31)), self.device) + z = torch.randn(n, self.mcfg.latent, generator=g, device=self.device) + h = torch.tanh(self.net.z_to_h(z)).unsqueeze(0) # (1, n, H) + tok = torch.full((n, 1), self.net.bos, dtype=torch.long, device=self.device) + out = torch.empty((n, self.L), dtype=torch.long, device=self.device) + with torch.no_grad(): + for pos in range(self.L): + dec_out, h = self.net.dec(self.net.embed(tok), h) + logits = self.net.out(dec_out[:, -1, :]) + probs = torch.softmax(logits, dim=-1) + tok = torch.multinomial(probs, 1, generator=g) + out[:, pos] = tok[:, 0] + return out.cpu().numpy() diff --git a/src/neural/train.py b/src/neural/train.py new file mode 100644 index 0000000..9cd08d2 --- /dev/null +++ b/src/neural/train.py @@ -0,0 +1,79 @@ +"""Torch determinism, seeding, and device helpers for the neural (Stage C) models. + +Layer 1.5's neural tiers are *statistically* reproducible, not bitwise (blueprint 4 rider): +we derive every torch seed from the same ``SeedSequence`` stream the rest of the study uses, +set all available determinism flags, and report per-seed points. ``CUBLAS_WORKSPACE_CONFIG`` +must be set before the first CUDA op, so it is set at import time. +""" + +from __future__ import annotations + +import os + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") + +import numpy as np + +_DETERMINISM_SET = False + + +def torch_seed_from(seed) -> int: + """Reduce an int or ``np.random.SeedSequence`` to a 32-bit torch seed.""" + if isinstance(seed, np.random.SeedSequence): + return int(seed.generate_state(1)[0]) + return int(seed) & 0xFFFFFFFF + + +def resolve_device(device: str): + """Resolve ``{auto, cpu, cuda}`` to a concrete ``torch.device``.""" + import torch + + if device == "auto": + device = "cuda" if torch.cuda.is_available() else "cpu" + return torch.device(device) + + +def set_determinism() -> None: + """Set torch/cuDNN determinism flags once per process (best-effort).""" + global _DETERMINISM_SET + if _DETERMINISM_SET: + return + import torch + + torch.use_deterministic_algorithms(True, warn_only=True) + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + _DETERMINISM_SET = True + + +def seed_everything(seed) -> "object": + """Seed torch (CPU+CUDA) from ``seed`` and return a seeded CPU ``torch.Generator``. + + Args: + seed: An int or ``np.random.SeedSequence`` from the study's seed stream. + + Returns: + torch.Generator: A CPU generator seeded for sampling ops (e.g. ``randperm``). + """ + import torch + + s = torch_seed_from(seed) + torch.manual_seed(s) + if torch.cuda.is_available(): + torch.cuda.manual_seed_all(s) + g = torch.Generator() + g.manual_seed(s) + return g + + +def device_generator(seed, device) -> "object": + """Return a ``torch.Generator`` on ``device`` seeded from ``seed``. + + ``torch.multinomial`` requires the generator to live on the same device as the + probabilities, so sampling ops use this rather than the CPU generator. + """ + import torch + + g = torch.Generator(device=device) + g.manual_seed(torch_seed_from(seed)) + return g diff --git a/tasks/todo.md b/tasks/todo.md index f6ee8a1..01bba08 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -123,6 +123,79 @@ Design decisions #1 (dataclasses now / pydantic at YAML layer), #2 (fitness `f_i - **E2 analysis add-ons** (companion work order `tasks/workorder-E2-analysis-addons.md`, verified): new `analysis.py` (`reduce_to_stationary`, `critical_grounding` bootstrap CI) — real E2 **g*=0.048, CI [0.047,0.050]**; `metrics.tail_band_metrics` + per-band lineage logging; `tests/test_analysis.py` reproduces the work order's verified numbers exactly. E2 figure rebuilt 2×2. **Deviation:** used truth-mass-weighted tail coverage instead of raw `tail_mass` (a drift martingale). - All six figures regenerate via `make figures`; **71 tests green**. +--- + +# Layer 1.5 — Architecture-general neural existence proof (RNN/VAE/MLP + synthetic/MNIST) + +*Created 2026-07-04. Plan: `~/.claude/plans/we-are-going-to-cheerful-fog.md`. Re-scopes Layer 2: +build a cheap, architecture-general neural collapse proof in real trained weights on a +fully-synthetic sandbox (exact known `p*`) before the LLM rung. Locked decisions: exact-oracle +categorical token sequences; Histogram+RNN+VAE+MLP; real MNIST as secondary confirmation; LLM + +C3 vertical claim deferred.* + +## Progress log + +**2026-07-04 — Stages A, B, plumbing complete.** + +- **Env:** installed `uv` 0.11.26 (`~/.local/bin`); `/home` was 100% full — GG approved clearing + pip/yay/browser caches (~10 GB freed). Base venv synced; 71 Layer-1 tests green. +- **Stage A (scaffold, pure NumPy):** `src/neural/` — `config.py` (frozen dataclasses reusing + `knowledge.config` GroundingCfg/RemintCfg/MetricsCfg/_sub), `synthetic.py` (mode-truth via + `make_true_distribution`; lossless identity + stochastic style token grammar), `oracle.py` + (`ExactOracle` zero-error + `measure_distribution`), `models.py` (`GenerativeModel` protocol + + `HistogramModel` bridge), `evaluate.py` (reuses `knowledge.metrics`, Layer-1 row schema), + `generation_loop.py` (`run_generative_lineage`, reuses `allocate_m`/`structured_multinomial`). + 15 correctness tests green. +- **Stage B — HARD GATE PASSED:** `tests/test_neural_validation.py` — histogram lineage reproduces + Pred. 1 (neutral decay, <3% rel err), Pred. 3 (exact `H_eq`, <5%), and tracks Layer-1 + `run_lineage` directly (<3%). The neural plumbing reproduces the analytic core. +- **Plumbing:** `neural/experiment.py` (`run_and_save` dispatch on `kind`, reuses `_apply_param` + g→m, paired seeds); extended `knowledge.experiment.save_artifacts` (optional `extra_libs`, + `extra_manifest`, injectable `grid`; skips missing libs — backward compatible). `configs/neural/N0.yaml`, + Makefile `neural`/`env-neural`/`layer2` targets, `.gitignore`. +- **N0 result (bridge, 17s):** neural **g\* = 0.0474, CI [0.045, 0.052]** — reproduces Layer-1 E2's + g\*=0.048 essentially exactly (g=0.005→67% of H*, g=0.05→96%). **89 tests green.** + +**2026-07-04 — Stage C: torch models + N1/N2/N5.** + +- **Env:** torch **2.12.1+cu130** (default PyPI wheel ships CUDA 13, matches RTX A4000 driver; + no custom index needed, cp314 wheels exist). `--extra neural` = torch only; `--extra mnist` = + torchvision (later). `UV_CACHE_DIR=/tmp` during install (RAM-backed) to spare `/home`. +- **Models:** `torch_models.py` (RNNGenerator, autoregressive GRU), `torch_mlp.py` (autoregressive + MLP, causal-masked), `torch_vae.py` (sequence VAE), `train.py` (determinism flags + device/seed + helpers derived from the SeedSequence stream). `tests/test_neural_torch.py` (torch-gated): gen-0 + fidelity (rnn+mlp) + dry-collapse/grounded-holds. **92 tests green.** +- **Validated regime:** K=256, n=200, zipf_s=1.3, RNN hidden=128/epochs=25. RNN gen-0 fidelity + KL(p*‖p̂)=0.008, 64/64 (or 256/256) modes recovered. MLP fidelity KL=0.011. **VAE does NOT clear + the gen-0 gate** on the Zipf-codeword task (KL≈0.8; prior-hole mismatch — sampling z~N(0,I) misses + the aggregate posterior) → excluded from N5 to avoid confounding collapse with underfitting. +- **N1 (collapse in weights):** dry RNN lineage collapses — forward-KL rises to ~2.2 vs grounded + ~1.4; grounding lifts tail survival (tailalive 0.31 dry → 0.50 at g=0.02). Sign confirmed. +- **N2 (neural phase boundary):** stationary H hovers 80–91% of H* and is **noisy / non-monotonic** + at 5 reps — no crisp g*. **KEY FINDING:** the neural models' smoothing inductive bias *partially + resists* H-collapse (dry H stays ~83% of H*), so **forward-KL and tail survival are the sharp + neural collapse metrics, not H** (mirrors Layer-1's "H is smooth; the threshold lives in tail + survival"). N2 needs (a) forward-KL as the phase metric, (b) more reps (≥10), and/or (c) a + stronger-collapse regime for a clean neural g*. +- **N5 (architecture-generality) — clean result:** collapse + grounding-rescue appear in ALL three + model classes (dry→grounded forward-KL: histogram 6.2→4.6, MLP 4.8→1.3, RNN 3.8→1.1; tailalive + RNN 0.41→0.64, MLP 0.07→0.20). The WF operator is architecture-general. Bonus: neural smoothing + lets RNN/MLP retain *more* tail than the exact histogram under grounding (they generalise to + unseen codewords) — an inductive-bias finding worth the write-up. + +## Remaining + +- [ ] **N2 refinement:** re-run with forward-KL as the phase metric + ≥10 reps (and/or smaller n) + for a clean neural g*. Pin the falsifier ("g* ≪ 1 exists") before re-running. +- [ ] **N4 (load-bearing):** `recombine.py` — mean-mixture vs union-preserving merge. The neural + merge MUST be **oracle-guided mixture sampling** (sample from the teacher strongest on each mode), + NOT weight-averaging of recurrent nets (flag #7). Reproduce the E4 "mean flat, max rises" finding. +- [ ] **N3** region-matched grounding (R>1), **N6** re-mint gate (optional). +- [ ] **VAE fidelity:** fix the prior-hole mismatch (KL-annealing / free-bits / larger latent) so it + clears the gen-0 gate, then add to N5. Or document as a known limitation. +- [ ] Real-MNIST secondary tier (`ClassifierOracle` + confusion matrix; `--extra mnist`). +- [ ] `figures/plot_N*.py` (reuse `figures/_figlib.py`); wire into `make figures`. + ## Discovered during work - **E2 grounding policy vs. the analytic H_eq:** Pred. 3's closed form is derived for *plain* immigration `Multinomial(m, p*)`. Implemented as `policy="proportional"`, and every policy reduces to it at `R=1`. E2 should therefore run at `R=1` (or `proportional`) so the phase-boundary sweep tracks the exact `H_eq`; region structure is E3's concern. Decide E2's `init` (uniform vs truth) when building its config. diff --git a/tests/test_neural_correctness.py b/tests/test_neural_correctness.py new file mode 100644 index 0000000..4948e9b --- /dev/null +++ b/tests/test_neural_correctness.py @@ -0,0 +1,174 @@ +"""Correctness tests for the Layer 1.5 neural scaffold (Stage A). + +Pure-NumPy checks (no torch): the synthetic grammar is lossless, the exact oracle has zero +error, the histogram model reduces to a mode-frequency estimator, and the generation loop +produces the Layer-1 row schema deterministically. +""" + +from __future__ import annotations + +import numpy as np +import pandas as pd +import pytest + +from neural.config import NeuralLineageCfg, SyntheticCfg +from neural.generation_loop import run_generative_lineage +from neural.models import HistogramModel, make_model +from neural.oracle import ExactOracle, measure_distribution +from neural.synthetic import id_codewords, make_mode_truth, render_modes, sample_synthetic + + +def _syn(**over) -> SyntheticCfg: + base = dict(K=64, R=1, zipf_s=1.1, tail_threshold=1e-3, style_len=3, style_vocab=5, + id_base=2) + base.update(over) + return SyntheticCfg(**base) + + +# --- synthetic grammar ------------------------------------------------------------------ + +def test_id_len_covers_all_modes(): + syn = _syn(K=100, id_base=2) + assert syn.id_base ** syn.id_len >= syn.K + assert syn.id_base ** (syn.id_len - 1) < syn.K + + +def test_seq_len_and_vocab(): + syn = _syn(K=64, id_base=2, style_len=3, style_vocab=5) + assert syn.id_len == 6 # 2**6 = 64 + assert syn.seq_len == syn.id_len + syn.style_len + assert syn.vocab == max(syn.id_base, syn.style_vocab) + + +def test_codewords_are_unique_and_invertible(): + syn = _syn(K=64) + cw = id_codewords(syn) + assert cw.shape == (syn.K, syn.id_len) + assert cw.max() < syn.id_base + # each mode's codeword is distinct + assert len({tuple(r) for r in cw}) == syn.K + + +def test_render_shapes_and_token_ranges(): + syn = _syn(K=32, style_len=4, style_vocab=7) + rng = np.random.default_rng(0) + modes = np.arange(syn.K) + X = render_modes(modes, syn, rng) + assert X.shape == (syn.K, syn.seq_len) + assert X[:, : syn.id_len].max() < syn.id_base + assert X[:, syn.id_len :].max() < syn.style_vocab + + +# --- exact oracle ----------------------------------------------------------------------- + +def test_exact_oracle_zero_error_on_all_modes(): + syn = _syn(K=100) + rng = np.random.default_rng(1) + modes = np.repeat(np.arange(syn.K), 5) # every mode, many style draws + X = render_modes(modes, syn, rng) + recovered = ExactOracle(syn).classify(X) + assert np.array_equal(recovered, modes) # zero measurement error + + +def test_measure_distribution_recovers_frequencies(): + syn = _syn(K=16) + rng = np.random.default_rng(2) + p = np.array([0.5] + [0.5 / 15] * 15) + X, _ = sample_synthetic(p, 200_000, syn, rng) + p_hat = measure_distribution(X, ExactOracle(syn), syn.K) + assert p_hat.shape == (syn.K,) + assert np.isclose(p_hat.sum(), 1.0) + assert abs(p_hat[0] - 0.5) < 0.01 + + +# --- histogram model -------------------------------------------------------------------- + +def test_histogram_initialise_is_exact(): + syn = _syn(K=32) + m = HistogramModel(syn, ExactOracle(syn)) + p0 = np.full(syn.K, 1.0 / syn.K) + m.initialise(p0, np.random.default_rng(0)) + assert np.allclose(m.mode_distribution(np.random.default_rng(0)), p0) + + +def test_histogram_fit_then_sample_roundtrip(): + syn = _syn(K=16) + rng = np.random.default_rng(3) + m = HistogramModel(syn, ExactOracle(syn)) + p = np.array([0.4, 0.3, 0.2] + [0.1 / 13] * 13) + X, _ = sample_synthetic(p, 100_000, syn, rng) + m.fit(X, rng) + drawn = m.sample(100_000, rng) + p_hat = measure_distribution(drawn, ExactOracle(syn), syn.K) + assert np.allclose(p_hat, m.mode_distribution(rng), atol=0.01) + + +def test_make_model_histogram(): + syn = _syn() + from neural.config import ModelCfg + model = make_model(ModelCfg(kind="histogram"), syn, ExactOracle(syn)) + assert isinstance(model, HistogramModel) + + +def test_make_model_rejects_unknown_kind(): + syn = _syn() + from neural.config import ModelCfg + with pytest.raises(ValueError): + make_model(ModelCfg(kind="nope"), syn, ExactOracle(syn)) + + +# --- mode truth reuses Layer 1 ---------------------------------------------------------- + +def test_mode_truth_is_layer1_truth(): + syn = _syn(K=100, R=10) + td = make_mode_truth(syn) + assert td.p_star.shape == (syn.K,) + assert np.isclose(td.p_star.sum(), 1.0) + assert td.tail_mask.dtype == bool + assert len(np.unique(td.regions)) == syn.R + + +# --- generation loop: schema + determinism --------------------------------------------- + +def _cfg(**over) -> dict: + base = { + "synthetic": {"K": 64, "R": 1, "zipf_s": 1.1, "init": "truth", + "style_len": 2, "style_vocab": 4, "id_base": 2}, + "model": {"kind": "histogram"}, + "dynamics": {"n": 200, "grounding": {"m": 0}}, + "generations": 5, + } + base.update(over) + return base + + +def test_lineage_returns_layer1_schema(): + df = run_generative_lineage(_cfg(), seed=0) + assert isinstance(df, pd.DataFrame) + assert list(df["generation"]) == [0, 1, 2, 3, 4, 5] + for col in ("heterozygosity", "forward_kl", "tail_mass", "support_size", + "tail_frac_alive", "head_frac_alive", "tail_truth_mass_alive"): + assert col in df.columns + + +def test_lineage_deterministic_given_seed(): + a = run_generative_lineage(_cfg(), seed=7) + b = run_generative_lineage(_cfg(), seed=7) + pd.testing.assert_frame_equal(a, b) + + +def test_lineage_h0_is_truth_heterozygosity(): + # init='truth' -> gen-0 H equals H* of the truth exactly (histogram is exact at gen 0) + syn = SyntheticCfg(K=64, R=1, zipf_s=1.1, init="truth", style_len=2, style_vocab=4) + td = make_mode_truth(syn) + h_star = 1.0 - np.sum(td.p_star ** 2) + df = run_generative_lineage(_cfg(), seed=1) + assert abs(df.loc[df["generation"] == 0, "heterozygosity"].iloc[0] - h_star) < 1e-12 + + +def test_dry_lineage_collapses(): + # m=0, small n -> heterozygosity must fall over generations (collapse) + df = run_generative_lineage(_cfg(generations=40, dynamics={"n": 50, + "grounding": {"m": 0}}), seed=2) + h = df["heterozygosity"].to_numpy() + assert h[-1] < h[0] - 0.1 diff --git a/tests/test_neural_torch.py b/tests/test_neural_torch.py new file mode 100644 index 0000000..9b5a044 --- /dev/null +++ b/tests/test_neural_torch.py @@ -0,0 +1,60 @@ +"""Stage C torch-model tests (skipped when torch is absent). + +Small, fast sign checks — the neural tiers are statistically reproducible and directional, +not exact, so these assert the *sign* of each effect (blueprint 3.5): gen-0 fidelity, dry +collapse, and grounding arresting it. They gate the RNN before the N-series experiments. +""" + +from __future__ import annotations + +import numpy as np +import pytest + +pytest.importorskip("torch") + +from knowledge.metrics import forward_kl, heterozygosity # noqa: E402 +from neural.config import ModelCfg, SyntheticCfg # noqa: E402 +from neural.generation_loop import run_generative_lineage # noqa: E402 +from neural.models import make_model # noqa: E402 +from neural.oracle import ExactOracle # noqa: E402 +from neural.synthetic import make_mode_truth # noqa: E402 + +_SYN = dict(K=256, R=1, zipf_s=1.3, init="truth", style_len=3, style_vocab=5, id_base=2, + tail_threshold=1e-3) +# hidden/epochs high enough that the RNN sharpens (an underfit RNN smooths and resists +# collapse); with n=200 K=256 the dry lineage collapses robustly across seeds. +_MODEL = dict(kind="rnn", hidden=128, embed=24, epochs=25, lr=2e-3, batch_size=256, n_eval=10000) + + +def _lineage_cfg(g: float, n: int, gens: int) -> dict: + m = 0 if g == 0 else round(n * g / (1 - g)) + return { + "synthetic": dict(_SYN), + "model": dict(_MODEL), + "dynamics": {"n": n, "grounding": {"m": m, "policy": "proportional"}}, + "generations": gens, + } + + +@pytest.mark.parametrize("kind", ["rnn", "mlp"]) +def test_gen0_fidelity(kind): + # A trained gen-0 model must recover p* (else "collapse" would be underfitting). Checked + # for the RNN and MLP; the VAE does not clear this gate on the codeword task (see todo). + syn = SyntheticCfg(**_SYN) + td = make_mode_truth(syn) + model = make_model(ModelCfg(**{**_MODEL, "kind": kind}), syn, ExactOracle(syn)) + model.initialise(td.p_star, np.random.default_rng(0)) + p_hat = model.mode_distribution(np.random.default_rng(1)) + assert forward_kl(td.p_star, p_hat, 1e-9) < 0.25 # close to truth + assert (p_hat > 1e-9).sum() >= 0.9 * syn.K # most modes represented + + +def test_rnn_dry_collapses_grounded_holds(): + # gens=20 gives clean dry-vs-grounded separation (KL ~2+ vs ~0.3); big margins survive + # GPU non-determinism. Directional per blueprint 3.5. + dry = run_generative_lineage(_lineage_cfg(0.0, 200, 25), seed=0) + grd = run_generative_lineage(_lineage_cfg(0.05, 200, 25), seed=0) + assert dry["heterozygosity"].iloc[-1] < dry["heterozygosity"].iloc[0] - 0.10 + assert dry["forward_kl"].iloc[-1] > 1.5 # tail forgotten + assert grd["forward_kl"].iloc[-1] < dry["forward_kl"].iloc[-1] # grounding closer to truth + assert grd["heterozygosity"].iloc[-1] > dry["heterozygosity"].iloc[-1] diff --git a/tests/test_neural_validation.py b/tests/test_neural_validation.py new file mode 100644 index 0000000..6912033 --- /dev/null +++ b/tests/test_neural_validation.py @@ -0,0 +1,100 @@ +"""Stage B — the HARD GATE: the neural runner reproduces the Layer-1 analytic core. + +With ``model.kind == "histogram"`` the neural generational step (train-on-parent's-samples ++ grounding) is *exactly* neutral Wright-Fisher drift with immigration. This module asserts +that the neural runner reproduces the two closed forms Layer 1 is validated against +(blueprint 2.4-1 neutral heterozygosity decay, 2.4-3 exact mutation-drift equilibrium) and +that its mean H-trajectory tracks ``knowledge.lineage.run_lineage`` directly. If any of +these fail the neural plumbing is wrong — no real network should be trained until they pass. +""" + +from __future__ import annotations + +import numpy as np +import pytest + +from knowledge.lineage import run_lineage +from knowledge.seeding import spawn_seeds +from neural.generation_loop import run_generative_lineage +from neural.synthetic import make_mode_truth +from neural.config import SyntheticCfg + + +def theory_decay(H0: float, n: int, t: np.ndarray) -> np.ndarray: + return H0 * (1.0 - 1.0 / n) ** np.asarray(t, dtype=float) + + +def theory_H_eq(n: int, m: int, H_star: float) -> float: + return H_star * m * (2 * n + m - 1) / (n + 2 * n * m + m * m) + + +def _mean_H(cfg: dict, n_rep: int, master: int = 20260704) -> np.ndarray: + """Mean heterozygosity trajectory over ``n_rep`` histogram-model replicates.""" + seeds = spawn_seeds(master, n_rep) + Hs = [run_generative_lineage(cfg, int(s.generate_state(1)[0]))["heterozygosity"].to_numpy() + for s in seeds] + return np.mean(np.stack(Hs), axis=0) + + +# --- Pred. 1: neutral heterozygosity decay ---------------------------------------------- + +def test_bridge_neutral_decay_matches_theory(): + K, n, gens, reps = 50, 100, 20, 800 + cfg = { + "synthetic": {"K": K, "R": 1, "zipf_s": 1.1, "init": "uniform", + "style_len": 2, "style_vocab": 4, "id_base": 2}, + "model": {"kind": "histogram"}, + "dynamics": {"n": n, "grounding": {"m": 0}}, + "generations": gens, + } + H_sim = _mean_H(cfg, reps) + t = np.arange(gens + 1) + H_theory = theory_decay(1.0 - 1.0 / K, n, t) + rel_err = np.abs(H_sim - H_theory) / H_theory + assert rel_err.max() < 0.03, f"max rel err {rel_err.max():.4f} exceeds 0.03" + + +# --- Pred. 3: exact mutation-drift equilibrium under grounding --------------------------- + +def test_bridge_grounded_equilibrium_matches_theory(): + K, n, m, gens, reps = 80, 100, 8, 220, 300 + cfg = { + "synthetic": {"K": K, "R": 1, "zipf_s": 1.1, "init": "uniform", + "style_len": 2, "style_vocab": 4, "id_base": 2}, + "model": {"kind": "histogram"}, + "dynamics": {"n": n, "grounding": {"m": m}}, # R=1 -> proportional immigration + "generations": gens, + } + H_sim_traj = _mean_H(cfg, reps) + H_sim = float(H_sim_traj[-60:].mean()) # stationary average + td = make_mode_truth(SyntheticCfg(K=K, R=1, zipf_s=1.1)) + H_star = 1.0 - float(np.sum(td.p_star ** 2)) + H_eq = theory_H_eq(n, m, H_star) + assert H_sim == pytest.approx(H_eq, rel=0.05), f"sim {H_sim:.4f} vs theory {H_eq:.4f}" + + +# --- Direct bridge: histogram lineage tracks Layer-1 run_lineage ------------------------- + +def test_bridge_tracks_layer1_trajectory(): + K, n, m, gens, reps = 60, 120, 6, 40, 400 + neural_cfg = { + "synthetic": {"K": K, "R": 1, "zipf_s": 1.1, "init": "uniform", + "style_len": 2, "style_vocab": 4, "id_base": 2}, + "model": {"kind": "histogram"}, + "dynamics": {"n": n, "grounding": {"m": m}}, + "generations": gens, + } + layer1_cfg = { + "truth": {"K": K, "R": 1, "zipf_s": 1.1, "init": "uniform"}, + "dynamics": {"n": n, "grounding": {"m": m, "policy": "proportional"}}, + "generations": gens, + } + seeds = spawn_seeds(20260704, reps) + H_neural = np.mean(np.stack([ + run_generative_lineage(neural_cfg, int(s.generate_state(1)[0]))["heterozygosity"].to_numpy() + for s in seeds]), axis=0) + H_layer1 = np.mean(np.stack([ + run_lineage(layer1_cfg, int(s.generate_state(1)[0]))["heterozygosity"].to_numpy() + for s in seeds]), axis=0) + rel_err = np.abs(H_neural - H_layer1) / H_layer1 + assert rel_err.max() < 0.03, f"neural vs Layer-1 max rel err {rel_err.max():.4f}" diff --git a/uv.lock b/uv.lock index a3af488..96280af 100644 --- a/uv.lock +++ b/uv.lock @@ -114,6 +114,74 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cudart = [ + { name = "nvidia-cuda-runtime" }, +] +cufft = [ + { name = "nvidia-cufft" }, +] +cufile = [ + { name = "nvidia-cufile" }, +] +cupti = [ + { name = "nvidia-cuda-cupti" }, +] +curand = [ + { name = "nvidia-curand" }, +] +cusolver = [ + { name = "nvidia-cusolver" }, +] +cusparse = [ + { name = "nvidia-cusparse" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc" }, +] +nvtx = [ + { name = "nvidia-nvtx" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -123,6 +191,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] +[[package]] +name = "filelock" +version = "3.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, +] + [[package]] name = "fonttools" version = "4.63.0" @@ -172,6 +249,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, ] +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -181,6 +267,18 @@ 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 = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "kiwisolver" version = "1.5.0" @@ -307,6 +405,12 @@ dependencies = [ dev = [ { name = "pytest" }, ] +mnist = [ + { name = "torchvision" }, +] +neural = [ + { name = "torch" }, +] [package.metadata] requires-dist = [ @@ -318,8 +422,84 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "pyyaml", specifier = ">=6.0" }, { name = "scipy", specifier = ">=1.11" }, + { name = "torch", marker = "extra == 'neural'", specifier = ">=2.2" }, + { name = "torchvision", marker = "extra == 'mnist'", specifier = ">=0.17" }, +] +provides-extras = ["dev", "neural", "mnist"] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] -provides-extras = ["dev"] [[package]] name = "matplotlib" @@ -386,6 +566,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, ] +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + [[package]] name = "numpy" version = "2.4.6" @@ -529,6 +727,158 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, ] +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + [[package]] name = "packaging" version = "26.2" @@ -1096,6 +1446,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, ] +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1105,6 +1464,112 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "torch" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/38/7028d3be540f1dcdf41660a2b01d0c51d2cb73915fe370d84e4d277a6d47/torch-2.12.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ef81f503912effea2ce3d9b12a2e3a6ed488943e91271c90c7a829f60baf6aa2", size = 87975425, upload-time = "2026-06-17T21:08:34.094Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e3/750b3e3548635ceac03ba255daa26dbc7ed66ca3484dc4b4d955ab7f4501/torch-2.12.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:107df6888624bdea41508f9aeb6149d9333c737a5530ceecb56c904e811369ae", size = 426379894, upload-time = "2026-06-17T21:06:55.077Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ca/ed24783da629ff3e640ba3f70a7639e9045d3d88b93ee6bc47b8a28a1f2c/torch-2.12.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6e29e7e74d05bda7d955c75e99459f878ebd970ef851b4057edbd3b34a5eb4a3", size = 532169264, upload-time = "2026-06-17T21:08:17.65Z" }, + { url = "https://files.pythonhosted.org/packages/46/61/c63f0158446f3a98ea672b004d761b848911eba567ea4a624c7db5aadc04/torch-2.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:a513506cfda3c1c78dabeb6574c1597538c0254b3d39af174dde35d8177f4ce3", size = 122953086, upload-time = "2026-06-17T21:08:27.69Z" }, + { url = "https://files.pythonhosted.org/packages/f0/54/efb7ebca77970012b0cc21687a55d70eb2ba514b2c2b8e18d9fb1222f3be/torch-2.12.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d2dd0f2c5f7ccbddaf34cade0deaf476808368f902b9cdb7f36a2ab42301bc0e", size = 87991951, upload-time = "2026-06-17T21:07:49.309Z" }, + { url = "https://files.pythonhosted.org/packages/1e/00/4210d76ca7424981f04033ebe7e48816ab83287a62538747a58825db770c/torch-2.12.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2de4e19b88a481482c6c75291f2d6a52eda3ce51f311b29aa9b68499c830c07c", size = 426382721, upload-time = "2026-06-17T21:06:41.842Z" }, + { url = "https://files.pythonhosted.org/packages/76/1f/bc9f5a5aa569307076365f25afcebacb22e9c754b1bcfbaaa146627c7fda/torch-2.12.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:649e4ced014ba646f76f8cb9c9726735a6323eb321b7919f942790a923f90921", size = 532261322, upload-time = "2026-06-17T21:06:06.673Z" }, + { url = "https://files.pythonhosted.org/packages/9e/49/c549461daa008159d006a76a991fbc2f26fa8bac27a4030c858463dcb20f/torch-2.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e86550597877fb272ddc52db2f85b82cb601ea7bd932576a0340152cae2200b3", size = 122988095, upload-time = "2026-06-17T21:07:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4a/0300261818e1560d72cc160ac826005507e8b7ca0a35788b591436d05b4a/torch-2.12.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c75e93173c700bccd6bfcc4a9d19ce242ab6dacd1f1781483027a16239b9e650", size = 87992358, upload-time = "2026-06-17T21:07:40.299Z" }, + { url = "https://files.pythonhosted.org/packages/30/a7/874a5ca05e8f159211dca7921060f7057acc1adb26431e119fd150623efc/torch-2.12.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fcb61ccd20784b62bdd78ec84238a5cfb383b4994902e03bac95505ab360884c", size = 426386134, upload-time = "2026-06-17T21:07:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/e1/75/20bb8fe9c1ad6538cce8cd0391b51927ae5af0b17ed1eab44b8824465dc1/torch-2.12.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f4afc8083dff08719edbea346644476e3cec0cf40ebe256be0ee5d5b7c7e8c0d", size = 532268019, upload-time = "2026-06-17T21:05:37.925Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fa/824ddb662af55b2eabc0dbb7b57c7c0b1bcd93693754a2b8509ec4d16490/torch-2.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:f92609e3b3ce72f25e2eb780d043ced2480c1a86c47c852604fc7a9108648386", size = 122987777, upload-time = "2026-06-17T21:07:09.49Z" }, + { url = "https://files.pythonhosted.org/packages/63/b7/1b49fe7086ea36839cc80abc43174c43d0ab6f676c0891c871c162f44fe3/torch-2.12.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e9b6f7d2dd66ea87a3ae620069d31335d594c06effb1a383bdd21cfe61e44ece", size = 88010025, upload-time = "2026-06-17T21:07:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/d7/06/5b44063a6545036dcc680d2d303b137d9176cfb2cc1e1863e3ef94abeb52/torch-2.12.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:7973ccd3d2cd35c74449213f7bded199bec6c6247e705cbeda7407af79703d91", size = 426392891, upload-time = "2026-06-17T21:05:52.261Z" }, + { url = "https://files.pythonhosted.org/packages/f8/dd/c9ce9a4b0eb3c5bb92d9ea56766e2c22559f0b45171149188494edcce80f/torch-2.12.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c64ac4aac16be5e296dcd912305605804b203333c690bf98c55bc09494ee92ad", size = 532272494, upload-time = "2026-06-17T21:06:22.72Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/f3a601fc1b1f663ff269bfe553654e638651939aa6563e8daa7167c33098/torch-2.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:f6dc4caf7eb4adb38a2d9f536b51db56310fdd1254e69a2d96767e1367c892b3", size = 122987254, upload-time = "2026-06-17T21:06:33.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/b8087556cf81ddd808dbeb34afb8396d7ae7a1694ab489f08b1a0004e7d0/torch-2.12.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2afbb2bdaa8a95040e733f05492ddf133c3967c9b7ce0abd218d704b6cab437d", size = 88303173, upload-time = "2026-06-17T21:05:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/fe09d1699fbed2afa10ebc692ff2b99d113f2605b6748cea633989e2789a/torch-2.12.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:97eba061fcb042fed191400b15568990073d67eaacaa6ee9b7ca01dd8b790fe9", size = 426404009, upload-time = "2026-06-17T21:04:57.557Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/0ce4f6c1962c60ded7270e0a9eb560fb615c92b89d332cf9e3dff36d5ecc/torch-2.12.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3867b861391701012adb2df93360efb88494dca245a185e3bb7624495cfe3f33", size = 532184292, upload-time = "2026-06-17T21:05:17.526Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/e384c12aba30320ca92aaaf557456cbcb26f04b4df307728bb8f019f5000/torch-2.12.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dd15595f8fc764cffde8c6361a3beb6ef69a028c851b1b3e70e077f615980d4e", size = 123231142, upload-time = "2026-06-17T21:05:27.061Z" }, +] + +[[package]] +name = "torchvision" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/46/bc0ebd93282aeedc1759f054a252c6fadf14b42a0535db3233c85cce4ae5/torchvision-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ad8743a9c12c8c124ad0a1491e54c3ca0c749e91e374e3d92136060b22c9e0f4", size = 1852118, upload-time = "2026-06-17T21:09:32.448Z" }, + { url = "https://files.pythonhosted.org/packages/b2/00/752adc57b6aa8bb833f5b0672acb9538aa5535d64998b9d8dd48ee51fa80/torchvision-0.27.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a726707e4cbe438fcc507d787af7acf6bca52de30bf4b03579f1dfc0675da829", size = 7831256, upload-time = "2026-06-17T21:09:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8a/c474fb27faba02e84dc40e0ac9ea1aa828d6d3557a378f7d0a22468bb2a3/torchvision-0.27.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a1d6a123009af59ad288459f579f67a65cbe8f59372dc7b97e41bc01a6a9b767", size = 7659995, upload-time = "2026-06-17T21:09:25.325Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7c/e254f8e242a921adc2cc62c11674fa8a16d33e0a1b6c6f5436cb91628ee7/torchvision-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:f3b57a984283896f15c9698562418282f828332886c77315bf269936e6ba0280", size = 3807497, upload-time = "2026-06-17T21:09:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/2e8fdc19e4f0bbe31d403a55d78318bcea4afcd3083e1e4700ef61ebb893/torchvision-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:448abfc3baba984da4577f737209e445da6be93e3b5f4799d90162bf61e3f485", size = 1852105, upload-time = "2026-06-17T21:09:33.695Z" }, + { url = "https://files.pythonhosted.org/packages/43/42/103fa8f9366cfd1329fe449d6b1a25a640c0c17862ed48f21c4af94af322/torchvision-0.27.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9edfb5a549fc2f30ccadb24eca907901e92e426c91a59316be6703a9360e5098", size = 7830902, upload-time = "2026-06-17T21:09:29.739Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/fa6052a42110a3657fc94073648da6171220469f4bf9f27e6a0b9378075c/torchvision-0.27.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae3d49e57c4abc8eafc1a1971f80fc4948a6268fa69340737ca4466936def080", size = 7664211, upload-time = "2026-06-17T21:09:17.206Z" }, + { url = "https://files.pythonhosted.org/packages/d0/95/27aca854da7e536a339f46bab1ef67823ac2ac97c59ab2b3203b373d46cf/torchvision-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b6e3aa98b7433506bbce1d0d05cb13ec787fc6eb8c5fbd998b26ce05f047543", size = 4079076, upload-time = "2026-06-17T21:09:15.907Z" }, + { url = "https://files.pythonhosted.org/packages/32/bb/b21e0f598ca191bb2a9e9fda2fee37c06ad113313b43c6769dbefa0e921d/torchvision-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d60311a6d08df905f9656a3a312f0a8f55f0d46321bc737bad30a8dec9644309", size = 1852110, upload-time = "2026-06-17T21:09:22.577Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/d61171daa5d6cd5f9315f84f9ef947b047a9fdf283d53241327045a8dd6d/torchvision-0.27.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:08aa33bc8e062cca32aefa90ac714916c5a855cbe1ab4c6148fc0453eb40ca5a", size = 7789476, upload-time = "2026-06-17T21:09:13.105Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dc/b21d7801562c23a770e7037989814582f22ca4db479204293561de4b62e8/torchvision-0.27.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:916448be4b19676677b0dbf47d08f68b7955ea0abec7fc79340c31e217a824ba", size = 7664256, upload-time = "2026-06-17T21:09:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/4386976ff77eda55f0aed504a288564f3ff8d170b6db49ee22e172eddfac/torchvision-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:18bc906235bfa901c135acd239f05b8c8ab90d502830cf1ef2cba3301e1f8a23", size = 4150710, upload-time = "2026-06-17T21:09:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/74/1d237c61f665bf46d02e15f67c9d40be42b1b634f87164b9cefd257450e7/torchvision-0.27.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9f5ef59ad60e695796eca6b64e97cb9b21b9d5463cac5ac0ef86cfb72b6e5db9", size = 1852112, upload-time = "2026-06-17T21:09:21.445Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/f0d772e7ed85891f084755bd5d7f6f7fd279992a02652c653c1c8429dd84/torchvision-0.27.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ab2f8047c2da5bf6742fec6da86840e5feaeb0cea76930d0536f3520df31e166", size = 7789751, upload-time = "2026-06-17T21:09:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/76/68/3febd41b6eef453a83fb7a0178446334fbb0405eb4b0c40b00efaf99a2dc/torchvision-0.27.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b44ef28ad1963f8cba5bf82f3564c454c74be300df9f79efa43f773312d17d6c", size = 7664350, upload-time = "2026-06-17T21:09:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/52/49/a23e199faf29e42a90f7d6b76437ade5d17e3185da3c64d368973ba8243e/torchvision-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:b3e9bc71854fddbf94ddb69ed8d88983945f3f28f78ee104214b0088669af66a", size = 4177297, upload-time = "2026-06-17T21:09:10.273Z" }, + { url = "https://files.pythonhosted.org/packages/ba/48/b3240eaf0fe3676dcf677ce8930ef477fe77d7f69ebe58ca8d0941384952/torchvision-0.27.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c2fd9902f23b56b6ac667213171672fb6c89287ff011918b04af053852a2c4eb", size = 1852118, upload-time = "2026-06-17T21:09:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/73/01/6c8f3158994a9e5bb0c7b1bacc361d60e015ad79487af88fa4d7ce72c2b6/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8abb6d5cacd56486ca2240e5580750e53ac559412e472ea6a3cee83231a77ca7", size = 7791242, upload-time = "2026-06-17T21:09:02.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e6/f66733fc411a9ce070c0d899c1ae562ff11654a0bc708511e23efe9d6872/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d11da1ce8a5cc7fc527f2d5e0fe25efba93687897fe9339382b593910b1d1c6e", size = 7664934, upload-time = "2026-06-17T21:09:06.221Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/d6179812ec52b70a7a8f5e99fe7937895d28c535106df1ca0d03f5f51425/torchvision-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:12deaee20d0d9dec6302025d3f93354266befeb692f5c50bca0137b395598b9e", size = 4284412, upload-time = "2026-06-17T21:09:08.989Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + [[package]] name = "typing-extensions" version = "4.16.0"