68fb99bed8
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>
146 lines
5.3 KiB
Python
146 lines
5.3 KiB
Python
"""Material physical-property table for the "physical" conditioning mode.
|
|
|
|
Values are Geant4's own built-in NIST material constants, not hand-typed
|
|
literature numbers -- extracted directly from a Geant4 11.4.1 build (the one
|
|
vendored in /home/lars/Programming/minicalosim/lib/geant4, built at
|
|
minicalosim/build/geant4-install) via a small standalone C++ program linked
|
|
against that build (G4NistManager::FindOrBuildMaterial + G4Material::
|
|
GetDensity/GetRadlen/GetNuclearInterLength + G4IonisParamMat::GetZeffective).
|
|
`a_eff` isn't directly exposed by Geant4, so it's computed with the same
|
|
atomic-number-density-weighted-average formula Geant4 itself uses for Zeff
|
|
(see G4IonisParamMat::BuildFluctModel in
|
|
lib/geant4/source/materials/src/G4IonisParamMat.cc), just applied to A
|
|
instead of Z -- for a single-element material this is exact; for a compound
|
|
it matches Geant4's own effective-Z convention rather than a different
|
|
weighting scheme.
|
|
|
|
Never silently substitute a default for a material missing from this table
|
|
(see UnknownMaterialError/MaterialPropertiesNotFilledError below) -- a wrong
|
|
material property would corrupt a whole conditioning axis without any
|
|
visible symptom until deep into training.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import NamedTuple
|
|
|
|
import numpy as np
|
|
|
|
|
|
class MaterialProperties(NamedTuple):
|
|
z_eff: float | None # effective atomic number
|
|
a_eff: float | None # effective atomic mass [g/mol]
|
|
density: float | None # [g/cm^3]
|
|
x0: float | None # radiation length [cm]
|
|
lambda_int: float | None # nuclear interaction length [cm]
|
|
|
|
|
|
class UnknownMaterialError(KeyError):
|
|
pass
|
|
|
|
|
|
class MaterialPropertiesNotFilledError(NotImplementedError):
|
|
pass
|
|
|
|
|
|
# Keys: every NIST material name seen in
|
|
# physics/detector-design/minicalosim-geometry.md, plus G4_AIR/G4_lAr which
|
|
# appear in dataset parquet files but not that doc. Values from Geant4's
|
|
# built-in NIST database (see module docstring) -- all present except
|
|
# G4_LYSO, which is not actually a stock Geant4 NIST material (confirmed:
|
|
# G4NistManager::FindOrBuildMaterial("G4_LYSO") fails to build in the
|
|
# vendored Geant4 11.4.1; it only appears as a plotting-color key in
|
|
# minicalosim/bind/G4Calo.py, never constructed in DetectorConstruction.cc)
|
|
# -- left unfilled until it's either built as a custom material (e.g.
|
|
# Lu1.8Y0.2SiO5:Ce) or dropped from the geometry menu.
|
|
MATERIAL_PROPERTIES: dict[str, MaterialProperties] = {
|
|
"G4_PbWO4": MaterialProperties(
|
|
z_eff=31.333333,
|
|
a_eff=75.843426,
|
|
density=8.28,
|
|
x0=0.892453,
|
|
lambda_int=20.739740,
|
|
),
|
|
"G4_CESIUM_IODIDE": MaterialProperties(
|
|
z_eff=54.0, a_eff=129.904539, density=4.51, x0=1.860288, lambda_int=39.305990
|
|
),
|
|
"G4_Pb": MaterialProperties(
|
|
z_eff=82.0, a_eff=207.216962, density=11.35, x0=0.561253, lambda_int=18.247950
|
|
),
|
|
"G4_W": MaterialProperties(
|
|
z_eff=74.0, a_eff=183.841648, density=19.30, x0=0.350418, lambda_int=10.311580
|
|
),
|
|
"G4_Cu": MaterialProperties(
|
|
z_eff=29.0, a_eff=63.545648, density=8.96, x0=1.435578, lambda_int=15.587940
|
|
),
|
|
"G4_Fe": MaterialProperties(
|
|
z_eff=26.0, a_eff=55.845113, density=7.874, x0=1.757493, lambda_int=16.990300
|
|
),
|
|
"G4_BRASS": MaterialProperties(
|
|
z_eff=30.939130,
|
|
a_eff=68.500857,
|
|
density=8.52,
|
|
x0=1.367465,
|
|
lambda_int=16.947420,
|
|
),
|
|
"G4_POLYSTYRENE": MaterialProperties(
|
|
z_eff=3.5, a_eff=6.509339, density=1.06, x0=41.312510, lambda_int=68.749880
|
|
),
|
|
"G4_PLASTIC_SC_VINYLTOLUENE": MaterialProperties(
|
|
z_eff=3.368421,
|
|
a_eff=6.219791,
|
|
density=1.032,
|
|
x0=42.544200,
|
|
lambda_int=69.969390,
|
|
),
|
|
"G4_BGO": MaterialProperties(
|
|
z_eff=27.578947,
|
|
a_eff=65.565839,
|
|
density=7.13,
|
|
x0=1.118030,
|
|
lambda_int=22.710130,
|
|
),
|
|
"G4_LYSO": MaterialProperties(None, None, None, None, None),
|
|
"G4_AIR": MaterialProperties(
|
|
z_eff=7.261982,
|
|
a_eff=14.547593,
|
|
density=1.204790e-3,
|
|
x0=30392.070000,
|
|
lambda_int=71009.500000,
|
|
),
|
|
"G4_lAr": MaterialProperties(
|
|
z_eff=18.0, a_eff=39.947692, density=1.396, x0=14.003440, lambda_int=85.706400
|
|
),
|
|
}
|
|
|
|
|
|
def get_material_properties(
|
|
name: str, table: dict[str, MaterialProperties] | None = None
|
|
) -> MaterialProperties:
|
|
t = MATERIAL_PROPERTIES if table is None else table
|
|
if name not in t:
|
|
raise UnknownMaterialError(
|
|
f"material {name!r} is not in giant.materials.MATERIAL_PROPERTIES "
|
|
f"-- add it (known: {sorted(t)})"
|
|
)
|
|
props = t[name]
|
|
if any(v is None for v in props):
|
|
raise MaterialPropertiesNotFilledError(
|
|
f"material {name!r} has un-filled physical properties in "
|
|
"giant/materials.py -- a physicist must populate real "
|
|
"z_eff/a_eff/density/x0/lambda_int values before "
|
|
"conditioning='physical' can be used with this material"
|
|
)
|
|
return props
|
|
|
|
|
|
def material_properties_array(
|
|
names: np.ndarray, table: dict[str, MaterialProperties] | None = None
|
|
) -> np.ndarray:
|
|
"""(N,) str material names -> (N, 5) float32 [z_eff, a_eff, density, x0, lambda_int]."""
|
|
out = np.array(
|
|
[get_material_properties(str(m), table) for m in np.asarray(names)],
|
|
dtype=np.float32,
|
|
)
|
|
return out.reshape(-1, 5)
|