Layer 1.5: architecture-general neural existence proof

Re-scopes Layer 2 into a cheaper, architecture-general neural collapse proof
before the LLM rung. Realises the same Wright–Fisher abstractions in real trained
generative models on a fully-synthetic sandbox with an exact oracle, reusing
knowledge.metrics/truth/seeding and the output contract so neural curves overlay
the Layer-1 analytic curves.

  - src/neural/: synthetic token-grammar sandbox (lossless identity + stochastic
    style), ExactOracle, HistogramModel bridge, generation loop, experiment runner
  - HARD GATE passed: histogram lineage reproduces Layer 1 exactly (neutral decay,
    exact H_eq, tracks run_lineage) — tests/test_neural_validation.py
  - torch models: autoregressive RNN + MLP (VAE implemented, not yet fidelity-
    passing); determinism seeding derived from the SeedSequence stream
  - N0 bridge (neural g*=0.047 ≈ Layer-1 0.048), N1 collapse-in-weights, N2 phase
    boundary, N5 architecture-generality (collapse + grounding-rescue in histogram
    + RNN + MLP). Manifests/configs committed; parquet gitignored, hashes tracked
  - additive backward-compatible save_artifacts extension; Makefile neural targets

Finding: neural smoothing partially resists H-collapse, so forward-KL and tail
survival are the sharp neural collapse metrics (H is smooth, per Layer 1).

92 tests green. Remaining (tasks/todo.md): N4 merge, N2 refine, N3/N6, VAE
fidelity, MNIST tier, figures. LLM/LoRA rung and C3 deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Giorgio Gilestro 2026-07-04 21:02:49 +01:00
parent 1721d047fa
commit 840b6b00b3
35 changed files with 3679 additions and 23 deletions

13
.gitignore vendored
View file

@ -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/ .venv/
__pycache__/ __pycache__/
*.pyc *.pyc
.pytest_cache/ .pytest_cache/
results/**/results.parquet
# Results: the large data artifact (results.parquet) is regenerable and gitignored; models/
# 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

View file

@ -2,12 +2,28 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 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 E1E6, 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 WrightFisher 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." The two design documents are the source of truth for intent:
- `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.
- `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. 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.

View file

@ -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`. # 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 env: ## build .venv from the committed lockfile
uv sync --extra dev 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) test: ## correctness tests + scientific-validation tests (the spine of trust)
uv run pytest uv run pytest
layer1: ## run experiments E1-E6 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 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 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 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) clean: ## remove caches and generated results (keeps committed manifests)
rm -rf .pytest_cache **/__pycache__ rm -rf .pytest_cache **/__pycache__

42
configs/neural/N0.yaml Normal file
View file

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

50
configs/neural/N1.yaml Normal file
View file

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

50
configs/neural/N2.yaml Normal file
View file

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

54
configs/neural/N5.yaml Normal file
View file

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

208
paper/layer1-summary.md Normal file
View file

@ -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 WrightFisher 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 WrightFisher, 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₀(11/n)^t`;
2. fixation probability = initial frequency;
3. **exact** mutationdrift equilibrium `H_eq = H*·m(2n+m1)/(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(1q)^{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 (E1E6)
- **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.480.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 **WrightFisher** 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 ~7096% 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
WrightFisher 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.

View file

@ -16,15 +16,22 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = ["pytest>=8.0"] 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] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
# src-layout: src/knowledge/ is importable as `knowledge` (the normative package # 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] [tool.hatch.build.targets.wheel]
packages = ["src/knowledge"] packages = ["src/knowledge", "src/neural"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]

16
results/N0/manifest.json Normal file
View file

@ -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"
}

View file

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

17
results/N1/manifest.json Normal file
View file

@ -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"
}

View file

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

17
results/N2/manifest.json Normal file
View file

@ -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"
}

View file

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

17
results/N5/manifest.json Normal file
View file

@ -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"
}

View file

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

View file

@ -211,11 +211,28 @@ def _content_hash(path: Path) -> str:
return h.hexdigest() 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). """Write the reproducibility output contract (blueprint 2.7 / 4).
Writes ``results.parquet``, ``resolved_config.yaml`` (the fully-expanded config), and Writes ``results.parquet``, ``resolved_config.yaml`` (the fully-expanded config), and
``manifest.json`` (library versions, master seed, git commit, content hash). ``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) out_dir.mkdir(parents=True, exist_ok=True)
results_path = out_dir / "results.parquet" 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"], "n_replicates": cfg["n_replicates"],
"source_config": cfg, "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"] = [ resolved["grid"] = [
{"label": label, "lineage_cfg": lineage_cfg} {"label": label, "lineage_cfg": lineage_cfg}
for label, lineage_cfg in expand_sweeps(cfg) for label, lineage_cfg in expand_sweeps(cfg)
] ]
(out_dir / "resolved_config.yaml").write_text(yaml.safe_dump(resolved, sort_keys=False)) (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 = { manifest = {
"experiment": cfg["experiment"], "experiment": cfg["experiment"],
"master_seed": cfg["seed"], "master_seed": cfg["seed"],
"git_commit": _git_commit(), "git_commit": _git_commit(),
"python": sys.version.split()[0], "python": sys.version.split()[0],
"libraries": { "libraries": libraries,
lib: version(lib) for lib in ("numpy", "scipy", "pandas", "pyarrow")
},
"rows": int(len(df)), "rows": int(len(df)),
"results_sha256": _content_hash(results_path), "results_sha256": _content_hash(results_path),
} }
if extra_manifest:
manifest.update(extra_manifest)
(out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2)) (out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2))

13
src/neural/__init__.py Normal file
View file

@ -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.
"""

124
src/neural/config.py Normal file
View file

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

77
src/neural/evaluate.py Normal file
View file

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

124
src/neural/experiment.py Normal file
View file

@ -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()

View file

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

119
src/neural/models.py Normal file
View file

@ -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)")

82
src/neural/oracle.py Normal file
View file

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

109
src/neural/synthetic.py Normal file
View file

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

109
src/neural/torch_mlp.py Normal file
View file

@ -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()

146
src/neural/torch_models.py Normal file
View file

@ -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}")

104
src/neural/torch_vae.py Normal file
View file

@ -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()

79
src/neural/train.py Normal file
View file

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

View file

@ -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). - **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**. - 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 8091% 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 ## 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. - **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.

View file

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

View file

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

View file

@ -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}"

467
uv.lock generated
View file

@ -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" }, { 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]] [[package]]
name = "cycler" name = "cycler"
version = "0.12.1" 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" }, { 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]] [[package]]
name = "fonttools" name = "fonttools"
version = "4.63.0" 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" }, { 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]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.3.0" 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" }, { 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]] [[package]]
name = "kiwisolver" name = "kiwisolver"
version = "1.5.0" version = "1.5.0"
@ -307,6 +405,12 @@ dependencies = [
dev = [ dev = [
{ name = "pytest" }, { name = "pytest" },
] ]
mnist = [
{ name = "torchvision" },
]
neural = [
{ name = "torch" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
@ -318,8 +422,84 @@ requires-dist = [
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" },
{ name = "pyyaml", specifier = ">=6.0" }, { name = "pyyaml", specifier = ">=6.0" },
{ name = "scipy", specifier = ">=1.11" }, { 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]] [[package]]
name = "matplotlib" 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" }, { 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]] [[package]]
name = "numpy" name = "numpy"
version = "2.4.6" 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" }, { 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]] [[package]]
name = "packaging" name = "packaging"
version = "26.2" 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" }, { 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]] [[package]]
name = "six" name = "six"
version = "1.17.0" 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" }, { 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.16.0" version = "4.16.0"