3 Commits
Author SHA1 Message Date
gitea-actions 8fe1d8f162 chore: release v0.3.25
CI / Tests (push) Successful in 3m9s
CI / Lint (ruff check) (push) Successful in 2m7s
CI / Format (ruff format) (push) Successful in 2m6s
CI / Type check (ty) (push) Successful in 2m18s
CI / Sync project version with tag (hand-pushed tags only) (push) Skipped
CI / Publish package to Gitea package registry (push) Skipped
CI / Release (bump, changelog, badges, tag) on merge to master (push) Successful in 16s
2026-09-09 12:45:38 +00:00
lars 8ac3060814 Merge pull request 'fix(analysis): keep pre_E alive through prediction range subsampling' (#102) from fix/analyze-prediction-subsample-pre-e into master
CI / Lint (ruff check) (push) Successful in 27s
CI / Format (ruff format) (push) Successful in 26s
CI / Type check (ty) (push) Successful in 23s
CI / Sync project version with tag (hand-pushed tags only) (push) Skipped
CI / Publish package to Gitea package registry (push) Skipped
CI / Tests (push) Successful in 2m37s
CI / Release (bump, changelog, badges, tag) on merge to master (push) Successful in 1m16s
Reviewed-on: #102
2026-09-09 14:36:23 +02:00
larsandClaude Sonnet 5 c0cbc99231 fix(analysis): keep pre_E alive through prediction range subsampling
CI / Sync project version with tag (hand-pushed tags only) (pull_request) Skipped
CI / Publish package to Gitea package registry (pull_request) Skipped
CI / Format (ruff format) (pull_request) Successful in 2m23s
CI / Lint (ruff check) (pull_request) Successful in 2m33s
CI / Type check (ty) (pull_request) Successful in 2m51s
CI / Tests (pull_request) Successful in 6m35s
CI / Release (bump, changelog, badges, tag) on merge to master (pull_request) Skipped
`_row_subsample` hash-filters on `pre_E`, but `build_context`'s
prediction branch called it on a frame already projected down to
`pred_<var>`/`true_<var>` columns, so any run whose prediction file
exceeds `sample_rows` (the default is 1M; real predict outputs can be
100M+ rows) failed with `ColumnNotFoundError: pre_E`.

Subsample the full paired frame first, then project — matching every
other _row_subsample call site — and restructure the loop to subsample
once per prediction side instead of once per paired variable, cutting
6 streaming passes over the prediction file down to 1.

Add a regression test with sample_rows below the fixture's row count
so the hash-filter branch is actually exercised (the existing test
never took it).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qb7xBAa6aAR94AzimgpPxq
2026-09-09 14:23:42 +02:00
7 changed files with 60 additions and 19 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
[tool.bumpversion]
current_version = "0.3.24"
current_version = "0.3.25"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## [0.3.25] - 2026-09-09
### Changed
- Fix(analysis): keep pre_E alive through prediction range subsampling
## [0.3.24] - 2026-09-09
### Changed
+2 -2
View File
@@ -10,8 +10,8 @@ calorimeter showers.
[![python](https://img.shields.io/badge/python-3.12%2B-3776AB?logo=python&logoColor=white)](pyproject.toml)
[![torch](https://img.shields.io/badge/torch-2.3.x-EE4C2C?logo=pytorch&logoColor=white)](pyproject.toml)
[![version](https://img.shields.io/badge/version-0.3.24-informational)](CHANGELOG.md)
[![tests](https://img.shields.io/badge/tests-1176%20passing-brightgreen)](tests/)
[![version](https://img.shields.io/badge/version-0.3.25-informational)](CHANGELOG.md)
[![tests](https://img.shields.io/badge/tests-1177%20passing-brightgreen)](tests/)
[![CI](https://git.larsbogner.de/lars/giant/actions/workflows/ci.yml/badge.svg?branch=master)](https://git.larsbogner.de/lars/giant/actions)
[![license](https://img.shields.io/badge/license-unlicensed-lightgrey)](#license)
+27 -14
View File
@@ -85,7 +85,11 @@ _LO_Q, _HI_Q = 0.001, 0.999
def _row_subsample(lf: pl.LazyFrame, sample_rows: int, seed: int) -> pl.LazyFrame:
"""Hash-subsample ~``sample_rows`` rows (for range estimation only)."""
"""Hash-subsample ~``sample_rows`` rows (for range estimation only).
``lf`` must still carry ``pre_E`` (the hash key) subsample before
projecting away columns, not after.
"""
n_total = lf.select(pl.len()).collect(engine="streaming").item()
if n_total <= sample_rows:
return lf
@@ -195,20 +199,29 @@ def build_context(
if predictions:
sides = {ps.name: open_prediction(ps.source) for ps in predictions}
present_vars = sorted(set().union(*(paired_vars_for_coord(s.coord) for s in sides.values())))
for var in present_vars:
true_samples, pred_samples, residual_samples = [], [], []
for s in sides.values():
if var not in paired_vars_for_coord(s.coord):
continue
cols = [f"pred_{var}"] + ([f"true_{var}"] if s.has_truth else [])
sample = _row_subsample(s.paired.select(cols), sample_rows, seed).collect(engine="streaming")
pred_samples.append(sample[f"pred_{var}"].to_numpy())
true_samples: dict[str, list[np.ndarray]] = {v: [] for v in present_vars}
pred_samples: dict[str, list[np.ndarray]] = {v: [] for v in present_vars}
residual_samples: dict[str, list[np.ndarray]] = {v: [] for v in present_vars}
# One subsample+collect per side (not per variable) — `s.paired`
# still carries `pre_E`, which `_row_subsample`'s hash needs, so
# subsample before projecting down to the pred/true columns.
for s in sides.values():
vars_here = paired_vars_for_coord(s.coord)
cols = [f"pred_{v}" for v in vars_here] + ([f"true_{v}" for v in vars_here] if s.has_truth else [])
sample = _row_subsample(s.paired, sample_rows, seed).select(cols).collect(engine="streaming")
for var in vars_here:
p = sample[f"pred_{var}"].to_numpy()
pred_samples[var].append(p)
if s.has_truth:
true_samples.append(sample[f"true_{var}"].to_numpy())
residual_samples.append(sample[f"pred_{var}"].to_numpy() - sample[f"true_{var}"].to_numpy())
pred_var_ranges[var] = _combined_quantiles([*true_samples, *pred_samples], _LO_Q, _HI_Q)
if residual_samples:
pred_residual_ranges[var] = _combined_quantiles(residual_samples, _LO_Q, _HI_Q)
t = sample[f"true_{var}"].to_numpy()
true_samples[var].append(t)
residual_samples[var].append(p - t)
for var in present_vars:
pred_var_ranges[var] = _combined_quantiles([*true_samples[var], *pred_samples[var]], _LO_Q, _HI_Q)
if residual_samples[var]:
pred_residual_ranges[var] = _combined_quantiles(residual_samples[var], _LO_Q, _HI_Q)
sec_pdg_counts: dict[int, int] = {}
for s in sides.values():
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "giant"
version = "0.3.24"
version = "0.3.25"
description = "Geant4 step-function surrogate via conditional flow matching"
readme = "README.md"
requires-python = ">=3.12"
+22
View File
@@ -193,6 +193,28 @@ def test_build_context_resolves_prediction_ranges():
assert ctx.pred_top_sec_pdgs # secondaries present in the fixture
def test_build_context_resolves_prediction_ranges_when_subsampled():
"""Regression test: `sample_rows` smaller than the prediction row count
must still work `_row_subsample`'s hash key (`pre_E`) has to survive
into the prediction branch's subsample call, not be projected away first
(see giant/analysis/context.py's predictions loop)."""
ctx = build_context(
[RolloutSpec("rollout", _rollout_frame())],
_reference_frame(),
predictions=[PredictionSpec("pred", _global_prediction_frame())],
n_energy_bins=2,
n_marginal_bins=10,
top_k_pdg=3,
sample_rows=2, # < the 3-row prediction fixture: forces the hash-filter branch
seed=0,
)
for var in PAIRED_SCALARS + ("cos_scatter", "cos_travel"):
lo, hi = ctx.pred_var_ranges[var]
assert np.isfinite(lo) and np.isfinite(hi) and lo < hi
lo, hi = ctx.pred_residual_ranges[var]
assert np.isfinite(lo) and np.isfinite(hi) and lo < hi
def test_prediction_specs_compute_valid_reduced():
ctx = _ctx_with_predictions()
bundle = Bundle.open(
Generated
+1 -1
View File
@@ -825,7 +825,7 @@ wheels = [
[[package]]
name = "giant"
version = "0.3.24"
version = "0.3.25"
source = { editable = "." }
dependencies = [
{ name = "numpy" },