Commit Graph

37 Commits

Author SHA1 Message Date
lars 05d5dee606 Apply ruff format after merging phase2-secondary-prediction
The merged proc_idx/proc_map plumbing wasn't run through ruff format
before merging; reflow only, no logic changes.
2026-07-15 10:00:36 +02:00
lars c81076c324 Support --energy-gev in dwarf make-root for the new minicalosim energy arg
run_pbwo4/run_sampling now accept a trailing energy_GeV positional arg;
thread it through plan/run/seed so datasets like pbwo4_10gev can be
generated at non-default beam energies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-13 13:59:09 +02:00
lars 740ebdf6b6 Drop orphaned child tracks instead of nulling secondary targets
A listed child_track_id can fail to match any first-step row (e.g. a
secondary absorbed below the tracking threshold at birth). The
parent->child left join in _add_secondary_attributes left these as
nulls, which silently became NaN once the parquet round-tripped
through the loader's float32 padding — poisoning every later secondary
slot in that step via the cumulative "remaining budget" in
encode_secondaries, while e_sec quietly undercounted and n_sec (from
len(child_track_ids)) overcounted relative to the actual lists.

Drop orphans from both the per-secondary lists and child_track_ids
itself so downstream counts stay consistent, and thread the per-file
orphaned count back through convert_steps_to_parquet so both the
sequential and --jobs>1 batch paths in `dwarf convert` can report an
aggregate total instead of relying on grepping printed output.

Also floors encode_secondaries' slot-0 budget to _EPS (matching the
i>0 branch), fixing a harmless but noisy 0/0 divide warning on
zero-secondary steps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 14:37:28 +02:00
lars a8fc019e32 Derive a unique per-job seed for minicalosim shard generation
Concurrent job launches in create_root_files.py can start within the
same wall-clock second, and minicalosim's default seed falls back to
time(NULL) in that case — so two "independent" shards could silently
get identical RNG state and produce byte-identical physics. Requires
the companion MINICALOSIM_SEED env-var support in the minicalosim repo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 10:09:23 +02:00
lars 3faa272562 Add fast slab lookup for the GeometryOracle, replacing knn as the default
miniCaloSim's detector is a stack of planar layer slabs along one axis, so
material/layer_id are a pure function of depth. The new "slab" method
exploits this with an exact O(log #segments) binary search over
depth-axis segment boundaries, instead of a nearest-neighbour search over
hundreds of thousands of reference points — much cheaper per call, which
matters since the oracle is queried on every autoregressive rollout step.
"knn"/"svm" remain as fallbacks for non-slab geometries.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-08 11:42:12 +02:00
lars 26a176aeaa Add autoregressive shower rollout driver
Closes the loop from single-step prediction into full showers:

- giant/geometry.py + `dwarf build-geometry-oracle`: learn position ->
  (material, layer_id) from data (KNN/SVM) to supply the conditioning the
  surrogate does not predict; flag detector escape by NN distance.
- giant/rollout.py: breadth-first batched frontier that steps all active
  tracks, spawns secondaries as new tracks, and terminates on energy cutoff,
  per-track max steps, escape, or natural end. Energy is deposited locally on
  every stop except escape (leakage), so showers conserve energy exactly.
- `giant rollout` CLI: seed from real events (argmax pre_E), load checkpoint,
  write a world-frame steps parquet + YAML sidecar.
- giant/analysis.py: compute_rollout_observables + plot_rollout_* for
  single-sided longitudinal/transverse/total-energy shower profiles;
  analysis/export_rollout_observables.py driver.
- scikit-learn added as an optional `geometry` extra (lazy-imported).
- Tests: tests/test_geometry.py, tests/test_rollout.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 10:10:36 +02:00
lars 4ba419ebe4 Merge energy-conservation-poc into phase2-secondary-prediction
Brings the energy-conservation PoC work (dwarf CLI unification, dwarf
status improvements, predict --comment, ODE-step comparison scripts,
predict-parquet-only analysis refactor) onto the Phase 2 branch.

Conflict resolution:
- giant/analysis.py: took the energy-conservation-poc version wholesale.
  That branch deliberately removed the live checkpoint+sampler diagnostics
  path (ModelBundle/load_model_bundle/make_val_loader/collect_samples) in
  favor of reading `giant predict --coord local` parquet output. Phase 2's
  only edits to this file adapted the removed path to the new dataset API,
  so nothing Phase-2-specific is lost; no external code called those funcs.

Fixes for pre-existing breakage surfaced by the merge (both predate it):
- giant/cli.py: predict's `_process` unpacked build_features into 5 values,
  but Phase 2 made it return 8 (added n_sec/sec_cont/sec_pdg_idx). Expanded
  the unpack; `giant predict --coord local` would have crashed otherwise.
- tests/test_steps_to_parquet.py: Phase 2 renamed _add_secondary_energy ->
  _add_secondary_attributes without updating this test. Renamed the calls
  and extended the fixture with the pdg/pre_d{x,y,z} columns the expanded
  function reads; e_sec assertions unchanged.
- analysis/compare_ode_steps_energy_conservation.py: E731 lambda assignment
  (added in the un-linted final PoC commit) rewritten as a def.

ruff, ty, and pytest (179 passed) all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:18:30 +02:00
lars 25718f175e Fix ruff, ty, and pytest failures; apply ruff format
Removes unused imports and an ambiguous variable name, narrows
Optional types before use so ty's flow analysis is satisfied, swaps
sum() over polars expressions for pl.sum_horizontal to avoid the
Literal[0] fallback type, and converts numpy bin edges to plain lists
before passing to matplotlib's hist (whose stub only accepts
Sequence[float]). Also applies ruff format across the repo, which had
drifted out of sync with the formatter.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 16:59:22 +02:00
lars e5bf7c51cb Fix silent failure modes surfaced by extensive code review
- energy_simplex_encode: warn when clipping post_E to pre_E discards
  recorded edep/e_sec instead of silently zeroing them
- local/inv_local_frame_rotation: validate and normalize pre_dir instead
  of silently assuming unit norm; raise on near-zero-norm rows
- train(): make --lr authoritative on resume instead of being silently
  overwritten by the checkpoint's optimizer/scheduler state; print and
  exit cleanly instead of silently training zero epochs when the
  checkpoint already meets --epochs; truncate metrics.csv on a fresh
  run instead of always appending
- dwarf update-manifest: check file existence for every manifest line,
  not just ones whose gen/schema actually changed
- pyproject.toml: dev extra now pulls in convert+analysis so the
  documented `uv sync --extra cpu --extra dev` + `pytest` actually
  passes collection

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 16:54:02 +02:00
lars 857d8b315f Show VERSIONS.md reason extracts in dwarf status
Parses the gen/schema log lines apply_bump() writes to VERSIONS.md and
prints a truncated reason under each gen/schemaN row, so `dwarf status`
answers "why does this version exist" without opening the changelog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 11:13:35 +02:00
lars 57e044e347 Add file counts and reference tracking to dwarf status
Shows per-directory file counts throughout the tree, plus a referenced
count for raw/ (matched against any same-named parquet under processed/)
and each schemaN dir (matched against pools/*.manifest entries).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 11:09:47 +02:00
lars 7761cd21b6 Color-code dwarf status output by tree level
Each row (kind header, gen, raw/processed, schema, root totals) gets a
distinct ANSI color so the hierarchy is easier to scan. Disabled when
stdout isn't a TTY or NO_COLOR is set.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 10:34:06 +02:00
lars c0746c40e0 Add disk usage summary to dwarf status
Shows per-schema, per-gen (raw/processed split), per-kind, and grand
total sizes so the dataset tree's footprint is visible at a glance.
2026-07-02 10:10:51 +02:00
lars 917835e182 Fold --to/--gen dataset-versioning flags into the dwarf CLI
origin/energy-conservation-poc grew bump-gen/bump-schema --to and
update-manifest --gen flags (091b23a) plus a train output-dir date
prefix (305e436) after the dwarf unification was written locally.
Reconcile: bring plan_bump_gen/plan_bump_schema/plan_update_manifest's
target/target_gen support into the plain-function (argparse-free) form,
thread --to/--gen through scripts/dwarf.py's bump-gen/bump-schema/
update-manifest commands, and take giant/cli.py's date-prefix change
and the associated tests as-is.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 09:57:17 +02:00
lars d5853d5a75 Unify dataset/tooling scripts into a single dwarf Typer CLI
Replace the five separately-hyphenated uv entry points (steps-to-parquet,
steps-to-parquet-parallel, migrate-geant-steps, bump-dataset-version,
create-root-files) plus the unregistered hparam_scan.py with one `dwarf`
command exposing convert/migrate/bump-gen/bump-schema/status/
update-manifest/create-manifest/make-root/hparam-scan as subcommands.

Each scripts/*.py module now only holds argparse-free business logic;
scripts/dwarf.py wires it up with Typer, matching giant/cli.py's style.
`dwarf convert` merges the old serial/parallel conversion scripts behind
a --jobs flag (default 1: sequential with plain -o; >1: dataset-layout
fan-out via subprocess).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 09:52:37 +02:00
lars e6e0eb22bf Implement Phase 2: secondary particle prediction
Two-stage factorisation: Stage 1 predicts 9D primary kinematics + n_sec
classification head (COND_DIM reduced to 8, dropping n_sec/e_sec inputs);
Stage 2 (SecondaryDecoder) generates K_MAX=15 secondary slots via masked
flow matching over (stick_logit, local_dir, type_emb) conditioned on Stage 1
output. Joint training with combined loss L_s1 + λ_nsec*L_nsec + λ_s2*L_s2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-29 11:34:31 +02:00
lars caaf3c6102 Restrict holdout overlap check to holdout vs dev/full only
dev and full are allowed to share files — only holdout must be strictly
isolated. When creating dev or full, only compare against holdout.manifest;
when creating holdout, compare against all other manifests in the dir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 11:24:05 +02:00
lars f56a45690d Add update-manifest and create-manifest subcommands to bump_dataset_version
update-manifest rewrites the schemaN component in existing manifest files to a
specified or auto-detected highest schema, verifying all target files exist before
writing. create-manifest builds a new manifest from explicit parquet file paths,
supporting --pool/--type (full|holdout|dev) to derive the output path from root,
and enforcing holdout isolation by checking for cross-manifest overlap whenever a
holdout manifest is involved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 11:03:32 +02:00
lars 6b85e2c90b Add --copy mode to migrate_geant_steps.py
Lets the migration run while another process still has the original files
open for reading: --copy uses shutil.copy2 instead of move, and skips the
now-empty-directory cleanup since the legacy train/ etc. dirs stay populated
by design.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 17:20:13 +02:00
lars 5be91e0d17 Expose dataset/conversion scripts as uv entry points
scripts/ is now a proper package (scripts/__init__.py, added to the wheel's
packages), with each script registered under [project.scripts] using its
bare dashed name (e.g. `uv run migrate-geant-steps`). Tests now import these
modules normally instead of loading them by file path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:51:47 +02:00
lars 320365606a Add tooling for a versioned geant_steps dataset layout
Introduces raw/<kind>/<gen>/<detector>/shard-NNN.root and
processed/<kind>/<gen>/<schema>/<detector>/shard-NNN.parquet as the dataset
convention, plus scripts to operate on it: migrate_geant_steps.py for the
one-time move into this layout, bump_dataset_version.py to cut new
gen/schema versions with a logged reason, steps_to_parquet_parallel.py to
convert ROOT shards to parquet in parallel and place them correctly, and
create_root_files.py to generate new ROOT shards via a minicalosim
executable. The loader gains .manifest file support so pools/ (train/dev/
holdout shard lists) can be passed straight to `giant train`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-25 16:46:48 +02:00
lars 8475199609 Encode edep/secondary/post energy as a conservation-constrained simplex
Replaces the independent log_delta_e/log_edep targets with 2 additive-log-ratio
coordinates over the deposit/secondary/post-energy simplex (fractions of pre_E
summing to 1), so edep + e_sec + post_E == pre_E holds by construction after
decoding (softmax) rather than being learned approximately. Requires e_sec
(secondary energy) as a new conditioning input and a steps_to_parquet.py pass
to derive it from child track first-step energies.
2026-06-25 16:01:13 +02:00
lars bdac36b203 Allow steps_to_parquet.py to accept multiple ROOT input files 2026-06-24 19:28:50 +02:00
lars 46068f356f Add hyperparameter scan 2026-06-22 07:27:02 +02:00
lars 74d0883868 Remove scripts/train.py in favor of the giant train CLI
The Typer-based giant/cli.py train command now has full feature
parity (dropout, warmup-epochs, validate-steps, shorthand flags),
making the standalone argparse script redundant.
2026-06-19 13:17:15 +02:00
lars cf653f7664 Add linear warmup before cosine LR decay
Replaces CosineAnnealingLR with a LambdaLR that linearly ramps the LR
from lr/warmup_epochs to lr over the first warmup_epochs steps, then
applies cosine decay for the remainder. Default warmup_epochs=5;
overridable via --warmup-epochs CLI flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 10:43:31 +02:00
lars 8cebc4809d Apply ruff format and document lint/type tooling in CLAUDE.md
First repo-wide ruff format pass, plus a note in CLAUDE.md to run
ruff and ty periodically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:40:27 +02:00
lars 53fd2e4405 Add ruff and ty as dev dependencies, fix lint/type findings
ruff removed unused imports across analysis.py and several test files.
ty caught a wrong dict[int, int] annotation on StreamingStepsDataset's
mat_map (materials are strings) and a real bug in steps_to_parquet.py
where --compression none passed None to polars' write_parquet, which
only accepts the literal "uncompressed". Also narrows a few
Optional-typed attributes (ddpm_schedule, Normalizer.mean/std) with
asserts and aligns __getitem__'s parameter name with torch's Dataset
base class.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:38:40 +02:00
lars da84801625 Add configurable dropout to ResBlocks
Wire a dropout hyperparameter (default 0.1) through the config, model,
training pipeline, and CLI. Persisted in saved model_config so checkpoints
reconstruct the architecture correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 15:59:20 +02:00
lars 893d91e749 Add KL divergence to marginal validation and hook it into the training loop
validate_marginals now estimates a per-dimension KL(real || generated) via
a shared histogram, alongside the existing mean/std comparison, so
distribution-shape drift shows up even when the first two moments match.

Wire it into giant/train.py: every validate_every epochs (default 10, 0
disables), the training loop runs validate_marginals against val_loader and
prints the table. validate_every flows through DEFAULT_CONFIG/config.toml
and is exposed as --validate-every on both giant train and scripts/train.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:45:12 +02:00
lars 43634ef77a Dedup training pipeline, add seeding/resume and per-epoch metrics logging
cli.py and scripts/train.py duplicated ~140 lines of training setup and had
drifted (scripts/train.py forgot to save model_config, breaking predict on
those checkpoints). Extract shared logic into giant/constants.py (X_DIM,
target names), giant/config.py (device/git/TOML/seeding helpers, run
metadata), and giant/pipeline.py (the actual training-job orchestration),
so both entry points become thin CLI wrappers around the same code path.

Also adds --seed/--resume support (checkpoints now carry optimizer/scheduler
state, epoch, and best_val_loss), a richer [meta] section in the saved
config.toml (git hash, seed, versions, timestamp, invocation, dataset
stats), and a metrics.csv (train/val loss, lr, epoch time) written every
epoch and append-safe across resumes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 13:32:37 +02:00
lars 72bd65ff9f Add post_pos as a model target via travel_dir decomposition
step_length already encodes |post_pos - pre_pos| by definition, so a raw
post_pos target would duplicate that magnitude and could drift inconsistent
with step_length during sampling. Instead add travel_dir, a unit vector
(local frame) giving only the direction of pre_pos->post_pos; post_pos is
reconstructed at inference as pre_pos + step_length * travel_dir, keeping
the two self-consistent. Target grows from 6D to 9D.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:36:55 +02:00
lars c3b7b2744c Batch StreamingStepsDataset internally instead of per-row collate
The dataset yielded one row at a time, forcing DataLoader's default
collate to Python-loop over every row to assemble each batch. That
loop scales with batch size and was pinning a CPU core at 100% while
the GPU sat idle. Now the dataset yields whole batches via vectorized
numpy slicing, used with DataLoader(batch_size=None).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 10:08:36 +02:00
lars 2e4b4d91d1 Add ROOT-to-parquet conversion script with convert dependency group
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 11:27:49 +02:00
lars 93c4d6b74d Add streaming data pipeline and giant CLI entry point
- Streaming pipeline: row-group-level parquet reading (PyArrow) so
  large files never fully land in RAM; Welford online algorithm for
  normalizer fitting; StreamingStepsDataset with shuffle buffer and
  multi-worker file striping; event-ID scan and vocab scan via cheap
  single-column reads
- giant/cli.py: typer-based CLI with `giant train` subcommand, mirroring
  scripts/train.py; --shuffle-buffer flag for RAM control
- pyproject.toml: add typer>=0.12 dependency and giant entry point
- train.py: replace len(loader.dataset) with local counters (compatible
  with IterableDataset which has no __len__)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 11:03:48 +02:00
lars 9277d79dff Implement Phase 1: full data pipeline, model, training, and config support
- Data pipeline: loader (parquet→numpy), transforms (log, local-frame
  Rodrigues rotation, Normalizer), StepsDataset with event-ID-based split
- Model: SinusoidalEmbedding, ConditionEncoder, ResBlock, DenoisingMLP
- Schedule: cosine DDPM and conditional flow matching loss (Lipman 2022)
- Samplers: flow (Euler ODE), DDPM ancestral, DDIM deterministic
- Training loop: AdamW + cosine LR, grad clipping, best-val checkpoint
- Validation: per-dimension marginal summary (normalised space)
- CLI: TOML config support with CLI-overrides; hyperparam-encoded output
  directory; config.toml with git hash saved into each run's checkpoint dir
- 21 unit tests covering transforms, network, flow/DDPM losses, dataset splits

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 10:48:03 +02:00
lars 943a044608 Initial commit: giant surrogate model with two-phase roadmap in README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 09:29:19 +02:00