Files
giant/tests/test_dataset.py
T
lars da7cde3ef9
CI / Lint (ruff check) (push) Successful in 27s
CI / Format (ruff format) (push) Successful in 28s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 36s
CI / Type check (ty) (push) Successful in 39s
CI / Format (ruff format) (pull_request) Successful in 30s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 30s
CI / Tests (pull_request) Successful in 2m50s
CI / Tests (push) Successful in 2m58s
v0.3.0 post-implementation audit: resolve all 9 tracked discrepancies
Works through docs/v0.3.0-followups.md item by item, closing the gap
between the design doc and the shipped v0.3.0-stage2-autoregressive code:

1. validate.py: 7-tuple batch unpacking, sample_stage1/sample_stage2
   dispatch, stage-2 particle-type-class marginal.
2. Stage-prefixed --stage1-*/--stage2-* CLI flags for train/new-run.
3. Thread stage2_model.k_max through loader/transforms/dataset/pipeline/
   train instead of the hardcoded K_MAX constant.
4. Mixed conditioning.particle.type / conditioning.material.type support
   end-to-end (data pipeline + dwarf warm-cache).
5. conditioning.share_stages = true: one shared ConditionEncoder instance
   across both stages.
6. stage2_model.generator = "ddpm" formally deferred into design doc §11.2
   (was silently unimplemented).
7. giant predict/rollout: implement conditioning.*.type = "onehot" via the
   checkpoint's saved pdg_topn_map/mat_topn_map.
8. network.py's checkpoint-path model_config migration now fails loudly on
   non-zero legacy expert_hidden_dim/expert_n_blocks, matching config.py's
   TOML-load path (§4.2).
9. validate_config now rejects stage2_model.n_sec.mode = "truth" for a
   rollout-capable checkpoint (§9).

Also cleared all pre-existing `ty check` noise (44 -> 0 diagnostics),
mostly a test-helper dict-unpack pattern that made every unrelated
constructor keyword look like a type error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 16:12:58 +02:00

141 lines
5.0 KiB
Python

import numpy as np
import pandas as pd
from giant.constants import COND_DIM, X_DIM
from giant.data import setup_cache
from giant.data.dataset import StreamingStepsDataset, make_event_split
from giant.data.transforms import Normalizer
def test_make_event_split_sizes():
rng = np.random.default_rng(42)
event_ids = rng.integers(0, 50, size=1000)
train_set, val_set = make_event_split(event_ids, val_fraction=0.2)
unique = np.unique(event_ids)
assert len(train_set) + len(val_set) == len(unique)
def test_make_event_split_no_overlap():
rng = np.random.default_rng(7)
event_ids = rng.integers(0, 50, size=1000)
train_set, val_set = make_event_split(event_ids, val_fraction=0.2)
assert train_set.isdisjoint(val_set)
def test_make_event_split_no_empty_sets():
rng = np.random.default_rng(0)
event_ids = rng.integers(0, 20, size=500)
train_set, val_set = make_event_split(event_ids, val_fraction=0.2)
assert len(train_set) > 0
assert len(val_set) > 0
def test_make_event_split_val_fraction_zero_holds_out_nothing():
"""val_fraction=0.0 is an explicit "train on everything" request and
must not be silently overridden into holding out 1 event."""
rng = np.random.default_rng(3)
event_ids = rng.integers(0, 50, size=1000)
train_set, val_set = make_event_split(event_ids, val_fraction=0.0)
assert val_set == set()
assert train_set == set(np.unique(event_ids).tolist())
def test_make_event_split_reproducible():
event_ids = np.arange(100)
a_tr, a_val = make_event_split(event_ids, val_fraction=0.1, seed=42)
b_tr, b_val = make_event_split(event_ids, val_fraction=0.1, seed=42)
assert a_tr == b_tr
assert a_val == b_val
# ── StreamingStepsDataset: cross-file event_id offsetting ──────────────────
def _steps_df(event_ids, n_per_event=3, pre_E=100.0):
"""A schema-complete but minimal steps DataFrame — no secondaries, so
`require_secondaries=True` never needs the per-secondary list columns."""
rows = []
for eid in event_ids:
for s in range(n_per_event):
rows.append(
{
"event_id": eid,
"pdg": 11,
"pre_x": 0.0,
"pre_y": 0.0,
"pre_z": 0.0,
"pre_E": pre_E,
"pre_dx": 0.0,
"pre_dy": 0.0,
"pre_dz": 1.0,
"material": "G4_AIR",
"layer_id": s,
"child_track_ids": [],
"e_sec": 0.0,
"step_length": 1.0,
"post_E": pre_E * 0.9,
"edep": pre_E * 0.1,
"post_dx": 0.0,
"post_dy": 0.0,
"post_dz": 1.0,
"post_x": 0.0,
"post_y": 0.0,
"post_z": 1.0,
}
)
return pd.DataFrame(rows)
def _dummy_normalizer(width):
norm = Normalizer()
norm.mean = np.zeros(width, dtype=np.float32)
norm.std = np.ones(width, dtype=np.float32)
return norm
def test_streaming_dataset_offsets_colliding_event_ids_across_files(tmp_path):
"""Two files that each restart event_id from 0 (one Geant4 job per file,
see scripts/steps_to_parquet.py) must not have their same-numbered events
collapsed together: every row from every file must show up in exactly one
of train/val, and the number of distinct events must be the sum across
files, not the union of raw ids."""
n_events, n_per_event = 5, 3
path_a = tmp_path / "a.parquet"
path_b = tmp_path / "b.parquet"
_steps_df(range(n_events), n_per_event=n_per_event).to_parquet(path_a)
_steps_df(range(n_events), n_per_event=n_per_event).to_parquet(path_b)
files = [path_a, path_b]
unique_ids, counts = setup_cache.compute_event_index_from_files(files)
assert len(unique_ids) == 2 * n_events
train_events, val_events = make_event_split(unique_ids, val_fraction=0.4, seed=0)
assert train_events.isdisjoint(val_events)
pdg_map, mat_map = {11: 0}, {"G4_AIR": 0}
cond_norm = _dummy_normalizer(COND_DIM)
tgt_norm = _dummy_normalizer(X_DIM)
def _count_rows(split_events):
ds = StreamingStepsDataset(
files=files,
split_events=split_events,
pdg_map=pdg_map,
mat_map=mat_map,
cond_normalizer=cond_norm,
target_normalizer=tgt_norm,
batch_size=4,
shuffle=False,
particle_conditioning="embedding",
material_conditioning="embedding",
)
return sum(len(batch[0]) for batch in ds)
n_train = _count_rows(train_events)
n_val = _count_rows(val_events)
total_rows = 2 * n_events * n_per_event
assert n_train + n_val == total_rows
assert n_train == int(counts[np.isin(unique_ids, list(train_events))].sum())
assert n_val == int(counts[np.isin(unique_ids, list(val_events))].sum())