Fix conditioning="physical" so it can actually generalize past training vocab
The whole point of conditioning="physical" is generalizing to a species/material outside the training menu, but two independent code paths still hard-required training-vocab membership: - giant/data/transforms.py: build_cond_features unconditionally raised KeyError on an out-of-vocab pdg/material. _vectorized_map_lookup gains a strict=False mode (dummy index instead of raising), used only under conditioning="physical" where ConditionEncoder never reads cond_cat anyway; "embedding" mode is untouched and still raises, since cond_cat IS the conditioning signal there. - giant/rollout.py: the known_pdg termination gate still killed a track on step 1 for any pdg outside pdg_map, regardless of conditioning mode. Now skipped entirely under conditioning="physical". - giant/model/network.py: PdgRouter/ProcessRouter always build their own training-vocab nn.Embedding independent of conditioning, silently reintroducing the same limitation at the routing layer. build_models now raises loudly if conditioning="physical" is paired with either router type, rather than silently building a model that can't generalize the way it claims to. This unblocks the held-out-species/material generalization experiment against the multi-material dataset (see CLAUDE.md roadmap). Each fix has a regression test, including an end-to-end rollout test seeded with a resolvable-but-out-of-vocab PDG code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,21 @@ def test_rollout_physical_conditioning_end_to_end(fake_material_props):
|
||||
assert set(np.unique(rec["pdg"]).tolist()) <= set(PDG_MAP.keys())
|
||||
|
||||
|
||||
def test_rollout_physical_conditioning_generalizes_to_out_of_vocab_pdg(
|
||||
fake_material_props,
|
||||
):
|
||||
"""A real, giant.particles-resolvable species outside the training PDG
|
||||
vocab (muon, 13) must run through physical-property conditioning rather
|
||||
than terminate via TERM_UNKNOWN_PDG — that generalization is the entire
|
||||
point of "physical" mode (see build_cond_features(strict=...))."""
|
||||
seeds = _seeds(6)
|
||||
seeds["pdg"] = np.full(6, 13, dtype=np.int64)
|
||||
assert 13 not in PDG_MAP
|
||||
rec = _run(seeds=seeds, conditioning="physical")
|
||||
assert len(rec["event_id"]) > 0
|
||||
assert TERM_UNKNOWN_PDG not in set(rec["termination_reason"].tolist())
|
||||
|
||||
|
||||
def test_seed_frontier_track_ids():
|
||||
seeds = _seeds(3)
|
||||
fr, counts = make_seed_frontier(**seeds)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Tests for the mixture-of-experts routing prototype (giant/model/network.py)."""
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from giant.constants import COND_DIM, K_MAX, SEC_DIM, X_DIM
|
||||
@@ -484,6 +485,49 @@ def test_build_models_routed_with_pdg_router():
|
||||
assert stage1.router.pdg_emb.num_embeddings == 4
|
||||
|
||||
|
||||
def test_build_models_rejects_pdg_router_with_physical_conditioning():
|
||||
"""conditioning="physical" is meant to generalize beyond the training PDG
|
||||
vocab; PdgRouter always uses a training-vocab nn.Embedding regardless of
|
||||
conditioning, so the combination must raise rather than silently building
|
||||
a model that can't actually generalize the way it claims to."""
|
||||
model_config = dict(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
conditioning="physical",
|
||||
router={"enabled": True, "type": "pdg", "n_experts": 3},
|
||||
)
|
||||
with pytest.raises(ValueError, match="physical"):
|
||||
build_models(model_config)
|
||||
|
||||
|
||||
def test_build_models_rejects_composed_router_with_pdg_axis_and_physical_conditioning():
|
||||
model_config = dict(
|
||||
pdg_vocab=4,
|
||||
mat_vocab=2,
|
||||
emb_dim=16,
|
||||
dropout=0.1,
|
||||
k_max=K_MAX,
|
||||
expert_hidden_dim=16,
|
||||
expert_n_blocks=2,
|
||||
conditioning="physical",
|
||||
router={
|
||||
"enabled": True,
|
||||
"type": "composed",
|
||||
"axis0_type": "energy",
|
||||
"axis0_n_experts": 2,
|
||||
"axis1_type": "pdg",
|
||||
"axis1_n_experts": 3,
|
||||
},
|
||||
)
|
||||
with pytest.raises(ValueError, match="physical"):
|
||||
build_models(model_config)
|
||||
|
||||
|
||||
# ── ProcessRouter ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -574,6 +574,51 @@ def test_vectorized_map_lookup_raises_keyerror_on_missing_value():
|
||||
_vectorized_map_lookup(values, mapping)
|
||||
|
||||
|
||||
def test_vectorized_map_lookup_strict_false_dummy_indexes_unmapped_values():
|
||||
"""strict=False must leave found values untouched and only dummy-index
|
||||
(0) the unmapped ones — never raise, and never disturb a value that IS
|
||||
in the mapping (e.g. one that happens to map to a nonzero index)."""
|
||||
mapping = {1: 5, 2: 7}
|
||||
values = np.array([1, 99, 2, 100])
|
||||
result = _vectorized_map_lookup(values, mapping, strict=False)
|
||||
np.testing.assert_array_equal(result, [5, 0, 7, 0])
|
||||
|
||||
|
||||
def test_build_cond_features_physical_mode_tolerates_out_of_vocab_pdg_and_material():
|
||||
"""conditioning="physical" must not KeyError on a pdg/material outside
|
||||
the training-dataset vocab (mat_map/pdg_map) — that's the entire point
|
||||
of the mode (see giant.rollout's known_pdg gate for the paired fix).
|
||||
"embedding" mode must still raise, since cond_cat IS the conditioning
|
||||
signal there. Note this is specifically about the dataset-scoped
|
||||
vocab index, not giant.materials' physical-properties table — a
|
||||
material must still be a real, known Geant4 material (e.g. "G4_Pb",
|
||||
just not one *this* mat_map happened to include) for "physical" mode
|
||||
to derive its Z_eff/A_eff/density/X0/λ_int; a genuinely unknown
|
||||
material name correctly still raises via giant.materials, same as the
|
||||
documented G4_LYSO precedent — that's a separate, intentional guard."""
|
||||
pdg_map = {11: 0, 22: 1}
|
||||
mat_map = {"G4_AIR": 0}
|
||||
data = {
|
||||
"pre_pos": np.zeros((1, 3), dtype=np.float32),
|
||||
"pre_E": np.array([10.0], dtype=np.float32),
|
||||
"pre_dir": np.array([[0.0, 0.0, 1.0]], dtype=np.float32),
|
||||
"layer_id": np.array([0], dtype=np.int32),
|
||||
"pdg": np.array([13], dtype=np.int64), # not in pdg_map
|
||||
"material": np.array(["G4_Pb"], dtype=object), # not in mat_map
|
||||
"mass": np.array([105.7], dtype=np.float32),
|
||||
"charge": np.array([-1.0], dtype=np.float32),
|
||||
}
|
||||
|
||||
cond_cont, cond_cat = build_cond_features(
|
||||
data, pdg_map, mat_map, conditioning="physical"
|
||||
)
|
||||
assert cond_cont.shape[-1] == COND_DIM
|
||||
np.testing.assert_array_equal(cond_cat, [[0, 0]]) # dummy indices, no raise
|
||||
|
||||
with pytest.raises(KeyError):
|
||||
build_cond_features(data, pdg_map, mat_map, conditioning="embedding")
|
||||
|
||||
|
||||
# ── _WelfordAccumulator ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user