Condition on material/particle physical properties instead of learned embeddings
Adds model.conditioning = "physical" | "embedding": physical mode routes particle mass/charge and material Z_eff/A_eff/density/X0/lambda_int through small MLPs to replace the learned PDG/material embedding tables, so the surrogate generalizes to PDG codes/materials outside the training vocab instead of memorizing it. "embedding" stays available as the comparison baseline (old checkpoints without the key default to it). Stage 2 now regresses a secondary's mass/charge directly against a fixed physics-derived target instead of a learned/snapped embedding, and uses no snapping at inference — the model's raw predicted (mass, charge) is the secondary's physical identity, including for its own further rollout steps. A separate reporting-only nearest-known-PDG lookup (never fed back into the model) populates output pdg columns / the embedding-mode rollout fallback. giant/materials.py's table is populated with Geant4's own built-in NIST constants (Z_eff, A_eff, density, X0, lambda_int), extracted directly from the Geant4 11.4.1 build vendored in minicalosim via G4NistManager rather than hand-typed literature values. G4_LYSO is left unfilled: confirmed (both by runtime lookup and by searching minicalosim's history) that it's never actually a constructed Geant4 material there, only documentation/UI color-map text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from giant.constants import K_MAX
|
||||
from giant.constants import COND_DIM, COND_DIM_BASE, K_MAX
|
||||
from giant.data.transforms import (
|
||||
build_cond_features,
|
||||
build_features,
|
||||
energy_simplex_decode,
|
||||
energy_simplex_encode,
|
||||
@@ -235,7 +236,7 @@ def test_build_features_clamps_n_sec_label_to_k_max():
|
||||
pdg_map = {11: 0}
|
||||
mat_map = {"PbWO4": 0}
|
||||
|
||||
_, _, _, n_sec, _, _, _, _, _ = build_features(data, pdg_map, mat_map)
|
||||
_, _, _, n_sec, _, _, _, _ = build_features(data, pdg_map, mat_map)
|
||||
|
||||
assert n_sec.max() <= K_MAX
|
||||
np.testing.assert_array_equal(n_sec, [0, 5, K_MAX])
|
||||
@@ -312,9 +313,87 @@ def test_build_features_require_secondaries_ok_when_no_secondaries():
|
||||
data = _step_data_no_sec_lists(np.zeros(3, dtype=np.int32))
|
||||
pdg_map, mat_map = {11: 0}, {"PbWO4": 0}
|
||||
|
||||
_, _, _, _, sec_cont, sec_pdg_idx, *_ = build_features(
|
||||
_, _, _, _, sec_cont, *_ = build_features(
|
||||
data, pdg_map, mat_map, require_secondaries=True
|
||||
)
|
||||
|
||||
assert not sec_cont.any()
|
||||
assert not sec_pdg_idx.any()
|
||||
|
||||
|
||||
# ── physical-property conditioning ────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fake_material_props(monkeypatch):
|
||||
"""Inject a fully-populated fake materials table for "physical" mode
|
||||
tests, independent of when the real giant/materials.py table is filled
|
||||
in by the user (see giant.materials.MaterialPropertiesNotFilledError)."""
|
||||
import giant.materials as gm
|
||||
|
||||
fake = {
|
||||
"PbWO4": gm.MaterialProperties(
|
||||
z_eff=75.6, a_eff=205.3, density=8.28, x0=0.89, lambda_int=20.7
|
||||
)
|
||||
}
|
||||
monkeypatch.setattr(gm, "MATERIAL_PROPERTIES", fake)
|
||||
return fake
|
||||
|
||||
|
||||
def test_build_features_embedding_mode_zero_fills_physical_columns():
|
||||
data = _minimal_step_data(3)
|
||||
pdg_map, mat_map = {11: 0}, {"PbWO4": 0}
|
||||
|
||||
cond_cont, *_ = build_features(data, pdg_map, mat_map, conditioning="embedding")
|
||||
|
||||
assert cond_cont.shape[1] == COND_DIM
|
||||
np.testing.assert_allclose(cond_cont[:, COND_DIM_BASE:], 0.0)
|
||||
|
||||
|
||||
def test_build_features_physical_mode_shape_and_values(fake_material_props):
|
||||
from giant.particles import particle_mass_charge
|
||||
|
||||
data = _minimal_step_data(3)
|
||||
pdg_map, mat_map = {11: 0}, {"PbWO4": 0}
|
||||
|
||||
cond_cont, *_ = build_features(data, pdg_map, mat_map, conditioning="physical")
|
||||
|
||||
assert cond_cont.shape[1] == COND_DIM
|
||||
mass, charge = particle_mass_charge(11)
|
||||
expected_log_mass = log_transform(np.array([mass]))[0]
|
||||
np.testing.assert_allclose(
|
||||
cond_cont[:, COND_DIM_BASE], expected_log_mass, atol=1e-5
|
||||
)
|
||||
np.testing.assert_allclose(cond_cont[:, COND_DIM_BASE + 1], charge)
|
||||
np.testing.assert_allclose(cond_cont[:, COND_DIM_BASE + 2], 75.6) # z_eff
|
||||
np.testing.assert_allclose(cond_cont[:, COND_DIM_BASE + 3], 205.3) # a_eff
|
||||
|
||||
|
||||
def test_build_features_physical_mode_unfilled_material_raises():
|
||||
"""G4_LYSO is the one material giant/materials.py still ships unfilled
|
||||
(not a stock Geant4 NIST material) — must fail loudly, not silently."""
|
||||
from giant.materials import MaterialPropertiesNotFilledError
|
||||
|
||||
data = _minimal_step_data(2)
|
||||
data["material"] = np.full(2, "G4_LYSO", dtype=object)
|
||||
pdg_map, mat_map = {11: 0}, {"G4_LYSO": 0}
|
||||
|
||||
with pytest.raises(MaterialPropertiesNotFilledError):
|
||||
build_features(data, pdg_map, mat_map, conditioning="physical")
|
||||
|
||||
|
||||
def test_build_cond_features_mass_charge_override(fake_material_props):
|
||||
"""rollout.py's secondaries carry their own predicted mass/charge — when
|
||||
present in `data`, these bypass the pdg-based lookup entirely (the "no
|
||||
snapping" design: a track's own future conditioning must use its actual
|
||||
predicted physical identity, not a value re-derived from a PDG code)."""
|
||||
data = _minimal_step_data(2)
|
||||
data["mass"] = np.array([123.0, 456.0], dtype=np.float32)
|
||||
data["charge"] = np.array([2.0, -2.0], dtype=np.float32)
|
||||
pdg_map, mat_map = {11: 0}, {"PbWO4": 0}
|
||||
|
||||
cond_cont, _ = build_cond_features(data, pdg_map, mat_map, conditioning="physical")
|
||||
|
||||
np.testing.assert_allclose(
|
||||
cond_cont[:, COND_DIM_BASE], log_transform(np.array([123.0, 456.0]))
|
||||
)
|
||||
np.testing.assert_allclose(cond_cont[:, COND_DIM_BASE + 1], [2.0, -2.0])
|
||||
|
||||
Reference in New Issue
Block a user