Skip to content

Commit 6a82dd0

Browse files
pre significant refactor to refocus on main points of interest to avoid long computation time
1 parent 53085d8 commit 6a82dd0

File tree

4 files changed

+1112
-381
lines changed

4 files changed

+1112
-381
lines changed

python/remage/utils.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,99 @@
1818
from collections.abc import Iterable
1919

2020

21+
def write_process_table(file_path: str, proc_name_to_id: dict[str, int]) -> None:
22+
"""Write an LH5 ``processes`` struct from a process-name to id mapping."""
23+
if not proc_name_to_id:
24+
return
25+
26+
import numpy as np
27+
from lgdo import Scalar, Struct, lh5
28+
29+
process_struct = Struct(
30+
{name: Scalar(np.int64(pid)) for name, pid in proc_name_to_id.items()}
31+
)
32+
lh5.write(process_struct, "processes", file_path, wo_mode="overwrite")
33+
34+
35+
def canonicalize_process_ids_for_concat(
36+
lh5_files: list[str],
37+
*,
38+
suffix: str = "-canon",
39+
) -> tuple[list[str], dict[str, int]]:
40+
"""Create remapped LH5 copies with canonical process ids for safe concatenation.
41+
This is useful when concatenating simulation outputs generated by different remage instances with different process-name to id mappings.
42+
43+
Parameters
44+
----------
45+
lh5_files
46+
Input files to canonicalize.
47+
suffix
48+
Suffix appended to each copied filename stem.
49+
50+
Returns
51+
-------
52+
remapped_files, canonical_proc_ids
53+
Paths to remapped copies and canonical process-name to id mapping.
54+
"""
55+
if not lh5_files:
56+
return [], {}
57+
58+
import shutil
59+
from pathlib import Path
60+
61+
from lgdo import Array, lh5
62+
63+
def _read_process_map(file_path: str) -> dict[str, int]:
64+
if lh5.ls(file_path, "processes") == []:
65+
return {}
66+
67+
procs = lh5.read("processes", file_path)
68+
return {name: int(proc.value) for name, proc in procs.items()}
69+
70+
ordered_names = []
71+
shard_proc_maps = {}
72+
for lh5_file in lh5_files:
73+
proc_map = _read_process_map(lh5_file)
74+
shard_proc_maps[lh5_file] = proc_map
75+
for name in proc_map:
76+
if name not in ordered_names:
77+
ordered_names.append(name)
78+
79+
canonical_proc_ids = {name: idx for idx, name in enumerate(ordered_names)}
80+
shard_id_maps = {
81+
lh5_file: {
82+
old_id: canonical_proc_ids[name] for name, old_id in proc_map.items()
83+
}
84+
for lh5_file, proc_map in shard_proc_maps.items()
85+
}
86+
87+
remapped_files = []
88+
for lh5_file in lh5_files:
89+
src_path = Path(lh5_file)
90+
remapped_file = str(
91+
src_path.with_name(f"{src_path.stem}{suffix}{src_path.suffix}")
92+
)
93+
shutil.copy2(lh5_file, remapped_file)
94+
95+
if lh5.ls(remapped_file, "tracks") != []:
96+
tracks = lh5.read("tracks", remapped_file)
97+
old_procid = tracks["procid"].view_as("np")
98+
new_procid = old_procid.copy()
99+
for old_id, new_id in shard_id_maps.get(lh5_file, {}).items():
100+
new_procid[old_procid == old_id] = new_id
101+
102+
tracks["procid"] = Array(
103+
new_procid.astype(old_procid.dtype, copy=False),
104+
attrs=tracks["procid"].attrs,
105+
)
106+
lh5.write(tracks, "tracks", remapped_file, wo_mode="overwrite")
107+
108+
write_process_table(remapped_file, canonical_proc_ids)
109+
remapped_files.append(remapped_file)
110+
111+
return remapped_files, canonical_proc_ids
112+
113+
21114
def _to_list(thing):
22115
if not isinstance(thing, tuple | list):
23116
return [thing]
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
file(
22
GLOB _file_list
33
RELATIVE ${PROJECT_SOURCE_DIR}
4-
macros/*.mac gdml/*.gdml *.py)
4+
macros/*.mac gdml/*.gdml misc/*.yaml *.py)
55

66
# copy them to the build area
77
foreach(_file ${_file_list})
@@ -11,4 +11,4 @@ endforeach()
1111
# run python tests using pytest
1212
add_test(NAME cosmogenics/muon/pytest-all COMMAND ${PYTHONPATH} -m pytest -s -vvv .)
1313
set_tests_properties(cosmogenics/muon/pytest-all PROPERTIES LABELS "extra;val;val-only" TIMEOUT
14-
7200)
14+
36000)

tests/cosmogenics/muon/test_energy_loss.py

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
u = pint.get_application_registry()
1818

19-
g_world_size = 200 * u.cm
19+
g_world_size = 50 * u.cm
2020
g_world_size_cm = g_world_size.to(u.cm).magnitude
2121

2222
macro = """
@@ -102,9 +102,14 @@ def simulate(
102102
em_physics: str = "Livermore",
103103
max_threads: int = 1,
104104
) -> str:
105-
output = (
106-
f"output-energy_loss-{energy:.0f}-{material}-{had_physics}-{em_physics}.lh5"
107-
)
105+
if energy > 1:
106+
output = (
107+
f"output-energy_loss-{energy:.0f}-{material}-{had_physics}-{em_physics}.lh5"
108+
)
109+
else:
110+
output = (
111+
f"output-energy_loss-{energy:.2f}-{material}-{had_physics}-{em_physics}.lh5"
112+
)
108113

109114
events = 100000 * int(os.environ.get("RMG_STATS_FACTOR", "1"))
110115

@@ -136,7 +141,10 @@ def calculate_dEdx(remage_output: str):
136141
mask = tracks["parent_trackid"] == 0
137142

138143
# read in event data
139-
stp = lh5.read("stp/", remage_output)
144+
try:
145+
stp = lh5.read("stp/", remage_output)
146+
except lh5.exceptions.LH5DecodeError:
147+
return np.array([0])
140148

141149
c = sp.constants.physical_constants["speed of light in vacuum"][0]
142150
m = sp.constants.physical_constants["muon mass energy equivalent in MeV"][0]
@@ -363,7 +371,10 @@ def plot(energies, materials, had_physics, em_physics, outfiles):
363371
for had_physic in had_physics:
364372
for em_physic in em_physics:
365373
remage_output = outfiles[(energy, material, had_physic, em_physic)]
366-
dEdx_sims[(had_physic, em_physic)] = calculate_dEdx(remage_output)
374+
out = calculate_dEdx(remage_output)
375+
if isinstance(out, float):
376+
out = np.array([out])
377+
dEdx_sims[(had_physic, em_physic)] = out
367378

368379
fig, ax = plt.subplots()
369380

@@ -438,6 +449,48 @@ def plot(energies, materials, had_physics, em_physics, outfiles):
438449
)
439450

440451

452+
def plot_energy_range(energies, materials, had_physics, em_physics, outfiles):
453+
454+
for material in materials:
455+
dEdx_sims = {}
456+
for energy in energies:
457+
for had_physic in had_physics:
458+
for em_physic in em_physics:
459+
remage_output = outfiles[(energy, material, had_physic, em_physic)]
460+
dEdx_sims[energy] = calculate_dEdx(remage_output)
461+
462+
x = np.array(energies)
463+
y = np.array([np.mean(dEdx_sim) for dEdx_sim in dEdx_sims.values()])
464+
mask = y > 0
465+
x = x[mask]
466+
y = y[mask]
467+
y_unc = np.array(
468+
[
469+
np.std(dEdx_sim) / np.sqrt(len(dEdx_sim))
470+
for dEdx_sim in dEdx_sims.values()
471+
]
472+
)
473+
474+
x_exp = np.array(lookup_tables["energy_loss"]["total"][material])[:, 0] / 1e3
475+
y_exp = (
476+
np.array(lookup_tables["energy_loss"]["total"][material])[:, 1]
477+
* lookup_tables["densities"][material]
478+
)
479+
480+
fig, ax = plt.subplots()
481+
ax.errorbar(x, y, yerr=y_unc, fmt="o", label="simulation")
482+
ax.plot(x_exp, y_exp, label="expected")
483+
ax.set_xscale("log")
484+
ax.set_xlabel("muon energy [GeV]")
485+
ax.set_ylabel("mean energy loss dE/dx [MeV/cm]")
486+
ax.legend()
487+
ax.set_title(
488+
f"Energy loss of muons in {material}",
489+
size=8,
490+
)
491+
fig.savefig(f"energy_loss_{material}_energy_range.output.png")
492+
493+
441494
def _simulate_case(
442495
case: tuple[float, str, str, str], max_threads: int = 1
443496
) -> tuple[tuple[float, str, str, str], str]:
@@ -521,3 +574,37 @@ def test_energy_loss():
521574
outfiles[key] = output
522575

523576
plot(energies, materials, had_physics_list, em_physics_list, outfiles)
577+
578+
energies = np.array(lookup_tables["energy_loss"]["total"]["lar"])[:, 0][10::2] / 1e3
579+
materials = ["lar", "water"]
580+
had_physics_list = ["Shielding"]
581+
em_physics_list = ["Livermore"]
582+
583+
cases = [
584+
(energy, material, had_physics, em_physics)
585+
for material in materials
586+
for had_physics in had_physics_list
587+
for em_physics in em_physics_list
588+
for energy in energies
589+
]
590+
591+
max_workers = min(len(cases), os.cpu_count() // 2)
592+
max_threads = os.cpu_count() // 2 // max_workers
593+
max_threads = max(1, max_threads)
594+
595+
outfiles = {}
596+
if max_workers == 1:
597+
for case in cases:
598+
key, output = _simulate_case(case, max_threads=max_threads)
599+
outfiles[key] = output
600+
else:
601+
with ProcessPoolExecutor(max_workers=max_workers) as ex:
602+
futures = [
603+
ex.submit(_simulate_case, case, max_threads=max_threads)
604+
for case in cases
605+
]
606+
for fut in as_completed(futures):
607+
key, output = fut.result()
608+
outfiles[key] = output
609+
610+
plot_energy_range(energies, materials, had_physics_list, em_physics_list, outfiles)

0 commit comments

Comments
 (0)