Add pdg energy/length contribution pie plots
pdg_contribution_table_pl sums real/generated total deposited energy and total step_length per pdg species over the whole file (pure lazy polars group_by, no post_pos reconstruction needed for these scalars). Adds plot_pdg_energy_share/plot_pdg_length_share, each rendering two pies (real vs generated) so the per-species breakdown can be compared directly, plus a matching notebook section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+71
-3
@@ -19,6 +19,7 @@ from giant.analysis import (
|
||||
load_predicted_local,
|
||||
marginal_table,
|
||||
marginal_table_pl,
|
||||
pdg_contribution_table_pl,
|
||||
plot_constraint_violations,
|
||||
plot_correlation_matrices,
|
||||
plot_direction_alignment,
|
||||
@@ -27,6 +28,8 @@ from giant.analysis import (
|
||||
plot_longitudinal_profile,
|
||||
plot_marginals,
|
||||
plot_pairwise,
|
||||
plot_pdg_energy_share,
|
||||
plot_pdg_length_share,
|
||||
plot_shower_max_depth,
|
||||
plot_total_energy,
|
||||
plot_total_length,
|
||||
@@ -400,10 +403,11 @@ def _write_event_level_parquet(path, rng=None):
|
||||
_make_event_level_arrays(rng)
|
||||
)
|
||||
n = len(event_id)
|
||||
pdg = rng.choice([11, -11, 22], n)
|
||||
table = pa.table(
|
||||
{
|
||||
"event_id": event_id,
|
||||
"pdg": rng.choice([11, -11, 22], n),
|
||||
"pdg": pdg,
|
||||
"pre_x": pre_pos[:, 0],
|
||||
"pre_y": pre_pos[:, 1],
|
||||
"pre_z": pre_pos[:, 2],
|
||||
@@ -431,7 +435,7 @@ def _write_event_level_parquet(path, rng=None):
|
||||
}
|
||||
)
|
||||
pq.write_table(table, path)
|
||||
return event_id, pre_pos, pre_dir, pre_E, true_log_local, pred_log_local
|
||||
return event_id, pre_pos, pre_dir, pre_E, true_log_local, pred_log_local, pdg
|
||||
|
||||
|
||||
def _expected_event_table(
|
||||
@@ -480,7 +484,7 @@ def _expected_event_table(
|
||||
|
||||
def test_compute_event_observables_pl_matches_manual_reconstruction(tmp_path):
|
||||
path = tmp_path / "event_level.parquet"
|
||||
event_id, pre_pos, pre_dir, pre_E, true_log_local, pred_log_local = (
|
||||
event_id, pre_pos, pre_dir, pre_E, true_log_local, pred_log_local, _pdg = (
|
||||
_write_event_level_parquet(path)
|
||||
)
|
||||
expected = _expected_event_table(
|
||||
@@ -566,3 +570,67 @@ def test_event_level_plots_run_without_error(tmp_path):
|
||||
assert plot_longitudinal_profile(obs) is not None
|
||||
assert plot_transverse_profile(obs) is not None
|
||||
assert plot_shower_max_depth(obs) is not None
|
||||
|
||||
|
||||
def test_pdg_contribution_table_pl_matches_manual_sums(tmp_path):
|
||||
path = tmp_path / "event_level.parquet"
|
||||
_, _, _, _, true_log_local, pred_log_local, pdg = _write_event_level_parquet(path)
|
||||
|
||||
real_edep = inv_log_transform(true_log_local[:, 2])
|
||||
gen_edep = inv_log_transform(pred_log_local[:, 2])
|
||||
real_length = inv_log_transform(true_log_local[:, 0])
|
||||
gen_length = inv_log_transform(pred_log_local[:, 0])
|
||||
|
||||
expected = {}
|
||||
for p in np.unique(pdg):
|
||||
mask = pdg == p
|
||||
expected[int(p)] = (
|
||||
float(real_edep[mask].sum()),
|
||||
float(gen_edep[mask].sum()),
|
||||
float(real_length[mask].sum()),
|
||||
float(gen_length[mask].sum()),
|
||||
)
|
||||
|
||||
table = pdg_contribution_table_pl(path).sort("pdg")
|
||||
for i, p in enumerate(table["pdg"].to_list()):
|
||||
real_e, gen_e, real_l, gen_l = expected[int(p)]
|
||||
np.testing.assert_allclose(table["real_total_edep"][i], real_e, rtol=1e-4)
|
||||
np.testing.assert_allclose(table["gen_total_edep"][i], gen_e, rtol=1e-4)
|
||||
np.testing.assert_allclose(table["real_total_length"][i], real_l, rtol=1e-4)
|
||||
np.testing.assert_allclose(table["gen_total_length"][i], gen_l, rtol=1e-4)
|
||||
|
||||
|
||||
def test_pdg_contribution_table_pl_accepts_lazyframe(tmp_path):
|
||||
path = tmp_path / "event_level.parquet"
|
||||
_write_event_level_parquet(path)
|
||||
|
||||
from_path = pdg_contribution_table_pl(path).sort("pdg")
|
||||
from_lf = pdg_contribution_table_pl(pl.scan_parquet(path)).sort("pdg")
|
||||
|
||||
np.testing.assert_allclose(
|
||||
from_lf["real_total_edep"].to_numpy(), from_path["real_total_edep"].to_numpy()
|
||||
)
|
||||
|
||||
|
||||
def test_pdg_pie_plots_run_without_error(tmp_path):
|
||||
path = tmp_path / "event_level.parquet"
|
||||
_write_event_level_parquet(path)
|
||||
table = pdg_contribution_table_pl(path)
|
||||
|
||||
assert plot_pdg_energy_share(table) is not None
|
||||
assert plot_pdg_length_share(table) is not None
|
||||
|
||||
|
||||
def test_plot_pdg_energy_share_caps_slices():
|
||||
table = pl.DataFrame(
|
||||
{
|
||||
"pdg": list(range(10)),
|
||||
"real_total_edep": [float(10 - i) for i in range(10)],
|
||||
"gen_total_edep": [float(10 - i) for i in range(10)],
|
||||
"real_total_length": [float(10 - i) for i in range(10)],
|
||||
"gen_total_length": [float(10 - i) for i in range(10)],
|
||||
}
|
||||
)
|
||||
fig = plot_pdg_energy_share(table, max_slices=4)
|
||||
for ax in fig.axes:
|
||||
assert len(ax.patches) == 4
|
||||
|
||||
Reference in New Issue
Block a user