Metadata injection, physical-weight based reweighting, and optional full-ntuple output for centrally produced inputs#142
Metadata injection, physical-weight based reweighting, and optional full-ntuple output for centrally produced inputs#142Zhuyuanda wants to merge 12 commits into
Conversation
Resolve hist.py and reweight.py conflicts: keep PW-weighted histograms, HIST_FLOOR/RW_CAP workflow, and upstream float64/np.divide warning fixes. Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #142 +/- ##
==========================================
+ Coverage 95.38% 96.35% +0.97%
==========================================
Files 24 25 +1
Lines 2298 2444 +146
==========================================
+ Hits 2192 2355 +163
+ Misses 106 89 -17 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
afroch
left a comment
There was a problem hiding this comment.
A changelog entry still seems to be missing — could you add one? It would also be good to document the usage of the new --metadata stage and the keep_all_variables flag in the docs.
| safe_hist = np.maximum(hist, HIST_FLOOR) | ||
| weights = np.zeros_like(hist, dtype=float) | ||
| np.divide( | ||
| all_targets[rw_group][rw_rep], | ||
| hist, | ||
| out=weights, | ||
| where=hist > 0, | ||
| ) | ||
| output_weights[rw_group][rw_rep]["weights"][cls] = weights | ||
| if idx_below_min is None: | ||
| idx_below_min = this_idx_below_min | ||
| else: | ||
| idx_below_min |= this_idx_below_min | ||
| # If we have any bins where we have 0 of a given flavour, we set all the | ||
| # weights to 0 | ||
| if np.any(idx_below_min): | ||
| for cls in all_histograms[rw_group][rw_rep]["histograms"]: | ||
| output_weights[rw_group][rw_rep]["weights"][cls][idx_below_min] = 0 | ||
| np.divide(target, safe_hist, out=weights, where=hist > HIST_FLOOR) | ||
| output_weights[rw_group][rw_rep]["weights"][cls] = np.clip(weights, 0, RW_CAP) |
There was a problem hiding this comment.
This changes reweighting for all configs, not just the physicalWeight path: the old "zero a bin across all classes when any class is empty" logic is gone and weights are now capped at RW_CAP=1e4. Could you confirm this is intended and validate against current outputs? Also safe_hist is effectively dead — where=hist > HIST_FLOOR means it's only used where safe_hist == hist, so the floor never applies. And PW_CAP (L181) / RW_CAP are the same value defined twice; consider a single named constant.
| self.variables = VariableConfig( | ||
| self.config["variables"], self.jets_name, self.is_test, selectors | ||
| self.config["variables"], | ||
| self.jets_name, |
There was a problem hiding this comment.
This swaps the 3rd VariableConfig arg from is_test to keep_all_variables, so test splits no longer get "keep all vars" for free via combined(). It's patched back in rw_merge/split_containers, but could you double-check every other combined()/.keep_all consumer (normalisation, resampling, merging) still behaves for the test split?
- Relocate metadata/flavours/variables-r10 configs under upp/configs/MetadataRW/; revert default variables/xbb/single-b configs to main - split_containers: gate physicalWeight append on column presence / keep_all - reweight: restore idx_below_min zeroing, unify WEIGHT_CAP applied only on the physicalWeight path, drop dead safe_hist and debug prints - metadata_injector: guard sow==0 / non-finite metadata, collect per-file failures and re-raise a summary instead of log-and-continue - preprocessing_config: keep_all = keep_all_variables or is_test (restore test split) - Condense inline block comments; add changelog entry and document --metadata stage and keep_all_variables flag Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # upp/classes/preprocessing_config.py
Weight --plot histograms by physicalWeight (capped) and reweight columns when present, and label plots "Pre/Post Reweighting" instead of "Resampling" on the reweight route. Default resampling route stays unweighted. Adds unit tests and updates changelog/docs. Co-authored-by: Cursor <cursoragent@cursor.com>
| output_weights[rw_group][rw_rep]["weights"][cls] = np.where( | ||
| hist > 0, all_targets[rw_group][rw_rep] / hist, 0 | ||
| ) |
There was a problem hiding this comment.
all_targets[...] / hist is evaluated over the whole array before np.where selects, so the hist == 0 bins divide by zero and re-emit the RuntimeWarning: divide by zero that #140 specifically removed. The masked np.divide keeps it warning-free:
weights = np.zeros_like(hist, dtype=float)
np.divide(all_targets[rw_group][rw_rep], hist, out=weights, where=hist > 0)
output_weights[rw_group][rw_rep]["weights"][cls] = weightsNon-blocking, but it reintroduces a warning we'd already silenced.
There was a problem hiding this comment.
Switched back to the masked np.divide (out=weights, where=hist > 0), so empty bins no longer trigger the divide-by-zero warning
| jets = f["jets"][:] | ||
| if "physicalWeight" not in jets.dtype.names: | ||
| jets2 = rfn.append_fields( | ||
| jets, | ||
| "physicalWeight", | ||
| np.ones(jets.shape[0], dtype="f4"), | ||
| usemask=False, | ||
| ) | ||
| del f["jets"] | ||
| f.create_dataset("jets", data=jets2) |
There was a problem hiding this comment.
Since the plot route keys off the presence of physicalWeight, injecting it into every mock means the default resampling integration tests now traverse the weighted / "Pre Reweighting"-labelled plotting branch too. The unit test covers _reweight_weight_fields directly, so this isn't blocking — but could you either gate this on the reweight test(s) only, or add a brief comment on why all mocks need the column? Otherwise the genuinely-unweighted default route is no longer exercised end-to-end.
There was a problem hiding this comment.
generate_mock now only injects physicalWeight when with_physical_weight=True (default off), so the default resampling route stays exercised unweighted end-to-end; the weighted route is covered by test_run_rw.py.
|
|
||
| # 6. Delete and recreate the dataset while restoring attributes | ||
| del f["jets"] | ||
| new_ds = f.create_dataset("jets", data=updated_jets, compression="gzip") |
There was a problem hiding this comment.
The recreated jets dataset is hard-coded to compression="gzip" even if the original was uncompressed (or used different chunking/shuffle), so the metadata stage silently changes the storage layout. Consider preserving the source dataset's compression/compression_opts/chunks/shuffle from old_jets_ds alongside the attrs you already restore.
There was a problem hiding this comment.
Now preserving the source dataset's compression/compression_opts/chunks/shuffle on recreation instead of hard-coding gzip.
- reweight: use masked np.divide so empty bins no longer re-emit the divide-by-zero RuntimeWarning that umami-hep#140 removed - configs: restore mcCampaignYear resampling bins in xbb / xbb-gn3x - test_run: only inject physicalWeight on demand so the default (unweighted) resampling route stays exercised end-to-end - metadata_injector: preserve source compression/chunks/shuffle instead of hard-coding gzip on the recreated jets dataset Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
This PR extends UPP to support a metadata → split → reweight → rw-merge workflow for centrally produced ntuples from TDD that lack pre-computed cross-section metadata and physicalWeight. It also adds an opt-in keep_all_variables flag so train/val/test VDS outputs preserve all top-level HDF5 datasets (tracks, towers, flow, …) while still appending reweight columns on jets.
Motivation
Centrally produced .h5 ntuples by TDD often need:
Changes
1) New
--metadatastageMetadataInjector.--metadatainupp/main.py..bakbackupftag.find_metadata.MetadataFinderphysicalWeightto thejetsstructured array using:jetsdataset attributes after dataset recreationFiles:
upp/stages/metadata_injector.pyupp/main.py2) Physical-weight based reweighting
physicalWeightif present; otherwise falls back to uniform weights.RW_CAP = 1e4and usesHIST_FLOOR = 1e-6.Files:
upp/stages/reweight.py3) Weighted histogram support
bin_jets(..., weights=...)supports weighted histograms (countwhen weights isNone,sumotherwise).Files:
upp/stages/hist.py4)
global.keep_all_variables(full top-level dataset passthrough)global.keep_all_variables: true(defaultfalse)PreprocessingConfig→VariableConfig(keep_all=...)jets.Files:
upp/classes/preprocessing_config.pyupp/stages/split_containers.pyupp/stages/rw_merge.pyupp/stages/normalisation.py(keeps norm computation consistent with declared inputs/labels)Config example:
upp/configs/metadata.yamlupp/configs/flavours-r10.yamlConformity