Files
giant/giant/materials.py
T
lars 55332db67a
CI / Lint (ruff check) (push) Successful in 31s
CI / Format (ruff format) (push) Successful in 32s
CI / Sync project version with tag (push) Has been skipped
CI / Lint (ruff check) (pull_request) Successful in 26s
CI / Type check (ty) (push) Successful in 29s
CI / Format (ruff format) (pull_request) Successful in 33s
CI / Sync project version with tag (pull_request) Has been skipped
CI / Type check (ty) (pull_request) Successful in 35s
CI / Tests (pull_request) Successful in 3m47s
CI / Tests (push) Successful in 3m55s
Bump ruff line-length to 120 and reformat
Rejoins lines that only wrapped because they exceeded the old 88-char
limit; ruff check and the full test suite (725 passed) are unaffected.
2026-08-12 13:33:09 +02:00

129 lines
5.2 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 -- 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)