import numpy as np import pytest from giant.materials import ( MaterialProperties, MaterialPropertiesNotFilledError, UnknownMaterialError, get_material_properties, material_properties_array, ) def test_get_material_properties_unknown_name_raises(): with pytest.raises(UnknownMaterialError): get_material_properties("G4_Unobtainium") def test_get_material_properties_unfilled_entry_raises(): """G4_LYSO is not a stock Geant4 NIST material (confirmed against the vendored Geant4 11.4.1 build) and is the one entry still shipped unfilled.""" with pytest.raises(MaterialPropertiesNotFilledError): get_material_properties("G4_LYSO") def test_get_material_properties_returns_filled_entry_from_injected_table(): table = { "G4_Pb": MaterialProperties( z_eff=82.0, a_eff=207.2, density=11.35, x0=0.5612, lambda_int=17.59 ) } props = get_material_properties("G4_Pb", table) assert props.z_eff == 82.0 assert props.a_eff == 207.2 assert props.density == 11.35 assert props.x0 == 0.5612 assert props.lambda_int == 17.59 def test_material_properties_array_shape_and_values(): table = { "G4_Pb": MaterialProperties(82.0, 207.2, 11.35, 0.5612, 17.59), "G4_W": MaterialProperties(74.0, 183.84, 19.3, 0.3504, 9.95), } names = np.array(["G4_Pb", "G4_W", "G4_Pb"], dtype=object) arr = material_properties_array(names, table) assert arr.shape == (3, 5) assert arr.dtype == np.float32 np.testing.assert_allclose(arr[0], [82.0, 207.2, 11.35, 0.5612, 17.59], rtol=1e-5) np.testing.assert_allclose(arr[1], [74.0, 183.84, 19.3, 0.3504, 9.95], rtol=1e-5) def test_all_known_materials_present_in_stub_table(): """Every material referenced elsewhere in the repo must at least have a stub entry (even if unfilled) -- an unknown name should never be the failure mode a physicist hits when populating the table.""" from giant.materials import MATERIAL_PROPERTIES expected = { "G4_PbWO4", "G4_CESIUM_IODIDE", "G4_Pb", "G4_W", "G4_Cu", "G4_Fe", "G4_BRASS", "G4_POLYSTYRENE", "G4_PLASTIC_SC_VINYLTOLUENE", "G4_BGO", "G4_LYSO", "G4_AIR", "G4_lAr", } assert expected <= set(MATERIAL_PROPERTIES.keys()) def test_all_materials_filled_except_lyso(): """G4_LYSO is the sole intentionally-unfilled entry (not a stock Geant4 NIST material); every other known material has real Geant4-derived values -- see the module docstring for provenance.""" from giant.materials import MATERIAL_PROPERTIES for name, props in MATERIAL_PROPERTIES.items(): if name == "G4_LYSO": assert all(v is None for v in props) else: assert all(v is not None for v in props), f"{name} unexpectedly unfilled" def test_elemental_material_z_eff_matches_atomic_number(): """Single-element materials' z_eff must equal the element's real Z.""" pb = get_material_properties("G4_Pb") assert pb.z_eff == pytest.approx(82.0) w = get_material_properties("G4_W") assert w.z_eff == pytest.approx(74.0) fe = get_material_properties("G4_Fe") assert fe.z_eff == pytest.approx(26.0) def test_pbwo4_values_match_known_cms_ecal_reference(): """PbWO4 (CMS ECAL crystal) has well-known reference values: X0~0.89cm, density 8.28 g/cm^3 -- sanity check the Geant4-derived numbers land there.""" pbwo4 = get_material_properties("G4_PbWO4") assert pbwo4.density == pytest.approx(8.28) assert pbwo4.x0 == pytest.approx(0.89, abs=0.01) assert pbwo4.z_eff == pytest.approx(31.33, abs=0.01)