Repository navigation
text2hdf5: process rates for counting bins, fnmatch rateParams, rate check for shape cards (#200, items 1, 2, 6) - #220
Conversation
Counting bins without a shapes line now take each process yield from DatacardParser.rates; the observation is only the data yield (was the observation for every process). The bin and process fields of rateParam lines are fnmatch patterns as in Combine, and a bin in which the process does not exist is not affected (was: KeyError, or patterns taken literally). Explicit rates for processes with a template are now checked against the template integral with Combine's tolerance; rate -1 still means 'take it from the template' (was: silently ignored). tests/test_datacard_converter.py covers the conversion of small counting and shape cards, the rateParam placement, the rate check, and the rabbit_text2hdf5 command line entry point. Co-Authored-By: DeepSeek (dsh-tui) <[email protected]> Refs WMass#200
There was a problem hiding this comment.
Issues Found
Critical (P0/P1)
- [P1] Apply the rateParam to every matched process, not only the first (
rabbit/datacard_converter.py:315-325)- When an fnmatch process pattern matches more than one process in the same channel (e.g.
r rateParam ch bkg* 1.0with bkg1 and bkg2 in ch, orr rateParam ch * 1.0), this passes the whole matched list toTensorWriter.add_norm_systematictogether with the scalar kappa. That writer wraps the scalar into a one-element list and iteratesfor p, u in zip(process, uncertainty)(tensorwriter.py:745), so the zip truncates to a single pair and only the first matched process receives the unconstrained rate scaling; all other matching processes silently get no rateParam, despite the writer's own error text stating a scalar uncertainty applies to every process in the list. Before this change such multi-process patterns failed loudly (the literal pattern was looked up indict_normand raised a KeyError), whereas now the card converts successfully and produces a silently wrong model; the new tests only exercise patterns matching a single process per channel, so this is not caught. Book the systematic per matched process instead.
- When an fnmatch process pattern matches more than one process in the same channel (e.g.
Summary
Total issues: 1 critical, 0 important, 0 minor.
Overall Verdict
Status: Patch has blocking issues
Explanation: The counting-bin rates and shape-card rate check are correct against the parser and writer contracts, and the unmatched-rateParam error is intentional. However, the new fnmatch process matching passes a multi-process list with a scalar kappa to TensorWriter.add_norm_systematic, which zips the two lists and therefore silently applies the rateParam to only the first matched process in each channel — cards that previously failed loudly now convert to silently wrong models.
Review details
- Model:
cern/Kimi-K3-FLATIRON
Review metrics: 17 turns · 22 tool calls · 12m 23s
- Tokens:
534.3Kinput ·484.0Kcached (91%) ·20.9Koutput
There was a problem hiding this comment.
Bot review seat (auto-review per owner directive; head 76e6d43). Static review of the diff against the PR claims, plus verification of the hodor findings. This does not replace human review.
Claim-by-claim check of the diff
-
Counting bins use process rates (
datacard_converter.py:271-281) — expected and understood. The counting branch now takesparser.rates[(bin_name, process_name)]for MC processes and keepsobservationsonly fordata_obs. Verified against the parser:bin_process_map/ratesare built strictly from theprocess/ratecard lines (datacard_parser.py:152-158), sodata_obscan never appear as a key and the directrates[...]lookup is safe for well-formed cards. The old behavior (every process gets the bin's observation) is visible in the diff, and the card from #200 indeed goes 160/160/1500 → sig 10 / bkg 100 / CR bkg 1000, exactly astest_counting_experiment_uses_process_ratesasserts. -
fnmatch rateParams (
datacard_converter.py:309-329) — expected and understood, modulo the P1 below. Per-channel expansion overbin_process_map[channel]is the right semantics (a bin where the process does not exist is simply unaffected), and — important — a*process pattern cannot matchdata_obs, sincedata_obsis not inbin_process_map. The raise-on-no-match (ValueErrorat 325-329) is deliberate per the description, and both no-match cases are tested. Matches Combine's wildcard treatment as described. -
Shape-card rate check (
datacard_converter.py:255-269) — expected and understood.rate > 0is checked against the bin-content sum of the template with tolerance0.01 * max(1, rate);rate == -1andrate <= 0are skipped, consistent with the documented zero-rate caveat left as follow-up. The bin-content sum is the correct quantity to compare against a Combinerateline.
hodor review: CONFIRM the P1, with an augmentation
Hodor's blocking finding on the fnmatch/multi-process path is correct — I verified it in tensorwriter.py:734-746:
- A scalar
uncertaintyis wrapped as[kappa](734-735), so the guardlen(uncertainty) != 1 and ...(737) passes for any number of processes. for p, u in zip(process, uncertainty)(746) then iterates once — with the new code atdatacard_converter.py:315-324, a pattern matching ≥2 processes in one channel (e.g.r rateParam ch bkg* 1.0withbkg1,bkg2) silently books the rateParam on the first process only. The new tests only exercise single-match patterns (WILDCARD_CARDhas onesiginch1and onebkgper channel), so it is indeed uncaught.
Augmentation, two nuances:
- Hodor's "previously failed loudly" holds for literal patterns (old code looked the literal
bkg*up indict_norm→KeyError), but the old*-path was already silently wrong in exactly the same way:processes = self.parser.processes(the whole card list) with the same scalar kappa truncated to the first process in card order. This PR generalizes the exposure from*to any multi-match pattern; the real defect is the zip-instead-of-broadcast inTensorWriter.add_norm_systematic, whose own error text implies scalar-means-all-processes. Hodor's recommended fix (book the systematic per matched process, oneadd_norm_systematiccall each) resolves both generations; alternatively broadcast the scalar inside the writer and add a regression test there. - The writer defect is worth tracking beyond this PR regardless of how the fix lands here, since other callers may assume scalar-broadcast semantics.
CI / validation products
- Gate jobs on the linked CI run are green so far:
linting,setenv,make-tensor,make-abcd-tensors,symmerizations— so the CI card corpus still converts with the new counting/rate logic.all-unit-testsis still running as I write this; its outcome fortests/test_datacard_converter.pyis the open dependency question the PR itself flags (unconditionalimport tqdmindatacard_converter.pyandimport uprootin the test;tqdmis not inpyproject.toml). A collection failure there would show as a per-file error in that job. - No EOS validation products are available for this PR:
copy-cleanhas failed on every run since the lxplus8s10 reboot on Nov 4 wiped the runner's ambient kerberos ccache (/eos/user/c/cmsmwbot/www/Rabbit/PRValidation/PR220/does not exist). That outage and its fix are #221; this review is therefore log- and static-analysis-based. For this diff that is proportionate: the changed code paths are unit-tested logic, not numerics that shift validation histograms.
Watch before merge
- The hodor P1 (multi-process rateParam patterns) — fix or an explicit decision to defer with a tracking issue.
all-unit-testscompleting green for the new converter tests in the CI environment (tqdm/uproot).copy-cleanon #221 to restore EOS products for future PR comparisons.
uproot writes the test ROOT files but is not installed on the CI runner, so the file failed to collect there. Refs WMass#200 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01U7Dn1MUu97igKuLKFW9exA
There was a problem hiding this comment.
Issues Found
No issues found.
Summary
Total issues: 0 critical, 0 important, 0 minor.
Overall Verdict
Status: Patch is correct
Explanation: The incremental delta only swaps a top-level import uproot in tests/test_datacard_converter.py for pytest.importorskip("uproot"). The placement is safe because rabbit/datacard_converter.py imports uproot lazily inside a method, so the converter import cannot raise before the skip takes effect, and the bound uproot name still covers the single later uproot.recreate use. Module-wide skipping is the documented intent, so no findings qualify.
Review details
- Model:
cern/Kimi-K3-FLATIRON
Review metrics: 2 turns · 3 tool calls · 19s
- Tokens:
10.7Kinput ·4.7Kcached (44%) ·1.8Koutput
|
The
Before we implement anything, I think we should decide how to handle these:
@davidwalter2, what do you prefer, especially for 2? I have the |
|
Thanks Sitian. For both I'd go for the variant that tests the converter in CI:
The zero-rate follow-up can stay out of this PR, as you suggested. |
TensorWriter.add_norm_systematic wraps a scalar kappa into a one-element list and zips it with the processes, so a pattern matching several processes in one channel (bkg*, *) was booked on the first one only. Pass one kappa per process. Also, as discussed on the PR: declare tqdm in the conversion extra, and import uproot plainly again, since the CI venv now has both and a runner without them should fail rather than skip. Refs WMass#200 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01U7Dn1MUu97igKuLKFW9exA
There was a problem hiding this comment.
Issues Found
No issues found.
Summary
Total issues: 0 critical, 0 important, 0 minor.
Overall Verdict
Status: Patch is correct
Explanation: The delta fixes the previously reported P1 exactly as agreed in the MR notes: the converter now passes one kappa per matched process, which satisfies TensorWriter.add_norm_systematic's scalar-wrap-and-zip contract so every fnmatch-matched process is booked. The new test's expectations (np.log(1.01) per matched process under the writer's default log_normal type, none on the unmatched process) are consistent with the writer and parser contracts, and declaring tqdm in the conversion extra matches its module-level import in the converter.
Review details
- Model:
cern/Kimi-K3-FLATIRON
Review metrics: 6 turns · 9 tool calls · 2m 9s
- Tokens:
72.5Kinput ·54.8Kcached (76%) ·7.1Koutput
|
CI status — workflow |
Fixes items 1, 2 and 6 of #200 in the datacard converter (
rabbit_text2hdf5), with its first tests. Items 3–5 are left for a follow-up: 4 and 5 (rateParam initial value and range,paramlines) need TensorWriter support and a design decision, and 3 (formula androot:wsrateParams) needs expression evaluation.Changes (
rabbit/datacard_converter.py)shapesline gave every process the bin's observation as its yield;DatacardParser.rateswas parsed but never used. Each process now takes itsrate, and onlydata_obstakes the observation. The card in rabbit_text2hdf5: counting-experiment yields, rateParam wildcards and param lines in the datacard converter #200 now converts to SR: sig 10, bkg 100 and CR: bkg 1000 (was 160, 160, 1500).fnmatchpatterns, as in Combine. A bin in which the process does not exist is not affected. It raises only if no bin matching the pattern has a matching process (was:KeyErrorfor a process missing from a bin, and patterns other than*taken literally).|integral - rate| > 0.01 * max(1, rate)→ValueError("Mismatch in normalizations ...").-1still means "take the integral" (was: any rate silently ignored).Tests (
tests/test_datacard_converter.py, new)Small counting and shape cards written on the fly, the shape templates in a ROOT file made with uproot:
-1and a matching rate pass;rabbit_text2hdf5on the command line, end to end.8 pass on this branch. 6 of them fail on main; the other 2 cover cases that were already right.
Notes for review
datacard_converter.pyimportstqdmunconditionally, buttqdmis not inpyproject.toml; this predates this PR. The new test also needsuproot(theconversionextra). Locally the tests ran with both added throughuv run --with tqdm --with uproot. If the CI runner lacks either, this file fails at collection. Addingtqdmto the dependencies could be a one-line follow-up.rate <= 0, but a zero-rate process with a template still enters with the template's yield. Combine drops zero-rate processes. Left as is, as a possible follow-up.Lint: black, isort and flake8 with the CI's
--selectlist are clean.Written with DeepSeek (dsh-tui), reviewed and tested by Claude Code.
Refs #200
🤖 Generated with Claude Code
https://claude.ai/code/session_01U7Dn1MUu97igKuLKFW9exA