Skip to content

Compute JES/JER-varied MELA probabilities - #400

Open
GlennnLiu wants to merge 1 commit into
CJLST:Run3from
GlennnLiu:Run3
Open

Compute JES/JER-varied MELA probabilities#400
GlennnLiu wants to merge 1 commit into
CJLST:Run3from
GlennnLiu:Run3

Conversation

@GlennnLiu

@GlennnLiu GlennnLiu commented Sep 3, 2026

Copy link
Copy Markdown

Dear Experts,

I (and codex) managed to address the computation of JES/JER-varied MELA probabilities. Only 24 * 11 MELA probabilities are additionally selected; no intermediate variables like varied leading / subleading jet indices are saved.

Tests have been performed with 2022EE ggH125, VBFH125 and partially ZZTo4l. For ggH125, VBFH125, I performed with or without JES/JER-varied probabilities. The runtime and file sizes are reported below:

Sample Nominal job-hours With 24 variations Slowdown Nominal memory Varied memory Memory increase
ggH125 4.72 20.37 4.32× 819.9 MiB 893.5 MiB +9.0%
VBFH125 1.39 9.26 6.68× 222.3 MiB 253.7 MiB +14.1%

We can see that the runtime is significantly slower, but in any case this is the price we have to pay, no matter computing them here or in the preprocessor.

Besides, comparison plots of the up/down variations with the nominal ones are made. Plots showing the distributions are shown in [1], and ratios of variations divided by the nominal ones are shown in [2], for the three processes.

Please let me know if the results are acceptable.

Besides, in nanoZZ4lAnalysis.py, jetVetoMap is put after JERC.

Thanks!

Yours Sincerely,
Geliang

[1] https://geliu.web.cern.ch/HZZ4l_Run3/JetVariedMELA/jet_varied_mela/
[2] https://geliu.web.cern.ch/HZZ4l_Run3/JetVariedMELA/jet_varied_mela_ratio/

@namapane namapane left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @GlennnLiu for this development! I have put some comments/suggestions.

@pianonick411 : you may want to have a look to changes in MELAProbHelper. As far as I can see, there is nothing affecting the computation of LHE probabilities.

ADD_ALLEVENTS = getConf("ADD_ALLEVENTS", IsSIGNAL) # if true, add a separate tree with gen-level variables for all events (not just those passing the candidate selection); by default, this is done for signal samples
ADD_LHE_PROB = getConf("ADD_LHE_PROB", ADD_ALLEVENTS) # Add LHE angles and probabilities. This is in general the case whenever ADD_ALLEVENTS is true (ie for signals)
JES_SPLITTING = getConf("JES_SPLITTING", True) # Whether to split JES variations into 11 components (if false, only up/down variations are produced, by summing all components in quadrature)
COMPUTE_JET_VARIATIONS_MELA = getConf("COMPUTE_JET_VARIATIONS_MELA", True) # Compute MELA probabilities for JES/JER shifted jets

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we leave the default to False to avoid changing the current behaviour for other users?
For example, VBS people use some jet probabilities (pyFragments/VBS_probs.py) but may not want to activate this, especially given the large overhead and the fact that they implemented variations in their preprocessor-equivalent code.

We could add COMPUTE_JET_VARIATIONS_MELA=True it in the STXS pyFragment like we do for bestCandByMELA to have it on automatically for our processing without having to change the csv files.
(It is now possible to add default variables in the format line of csv fles, so we could simply modify 1 line in each csv file, but I prefer the PyFragment way as it concentrates analysis-specific customizations in a single place).

if recoProbFiller is not None and COMPUTE_JET_VARIATIONS_MELA:
recoProbFiller.setJetVariations(jetCorrector)
elif recoProbFiller is not None:
print("***RecoProbFiller: jet-varied MELA computation disabled", flush=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would actually skip this message - the logic of initialization messages is that everything that is set up issues a message (and the message in this case is already issued by RecoProbFiller), so no message -> not done

py = pt * math.sin(phi)
pz = pt * math.sinh(eta)
energy = math.sqrt(max(mass * mass + px * px + py * py + pz * pz, 0.))
return Mela.SimpleParticle_t(pdgId, px, py, pz, energy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the code goes through p4() which internally uses TLorentzVector [here]. The math is obviously the same but the different implementation in C++ and python may give small numerical effects and in principle it's not so nice to do nominal in one way and variations in another. I would have a small preference to adopt an as close as possible implementation in the two cases, although any numerical effect is in principle already covered by your validation tests.

selected = []
for idx, jet in enumerate(jets):
pt = getattr(jet, variation + "_pt")
mass = getattr(jet, variation + "_mass")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since jets is the full list of jets with no selection, it would make sense to:

  • check jet.jetId first so that no computation is done for jets that are discarded anyhow
  • move the creation of the variable names (variation + "_pt", variation + "_mass") outside the loop so that it is done only once per variation and not for every jet for every variation
  • move the extraction of mass below the current L137 so that it is not done for jets that fail other cuts

mass = getattr(jet, variation + "_mass")
leptonPt = jet.ZZLepEF * jet.pt
overlapsLeptons = pt <= 0. or leptonPt / pt > 0.5
if overlapsLeptons or jet.jetId != 6 or pt <= jet.ptThreshold:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One potentially dangerous thing here is that if for some reason somebody (other than you) decides to change ID or cleaning criteria in jetFiller.py, it is practically certain (s)he will not realize a matching change is required here. There is no quick workaround, but at the very least we should put a big reminder in jetFiller.py where ID cut and EFthreshold are defined.

if overlapsLeptons or jet.jetId != 6 or pt <= jet.ptThreshold:
continue
selected.append((pt, idx, mass))
selected.sort(key=lambda item: item[0], reverse=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selected.sort(key=itemgetter(0), reverse=True) (with from operator import itemgetter) would save the overhead of defining and calling the lambda.
[Note: the code in jetFiller.py picks the 2 elements manually avoiding sort on purpose, to avoid the cost of sorting all elements when we only need the first two. Of course that does not make sense here]

0, pt, jet.eta, jet.phi, mass
))
for idx in (aCand.extraLep1Idx, aCand.extraLep2Idx):
if idx < 0:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra leptons don't change across jet variations: the extra leptons' SimpleParticle_t could be built once for each candidate and cached before the loop on variations, so that they do not need to be remade 22 times.

@namapane

namapane commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

A general comment on your very nice comparison plots:
I would argue that at least a good fraction of these systematic variations will lead to a negligible systematic uncertainty on results. Of course we need to prove it - let's see...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants