CMS scouting VVV analysis workflow for pileup reweighting, branch conversion with jet energy corrections, optional sample-entry mixing, BDT/NN training, signal-region optimization, QCD ABCD estimation, systematic uncertainties, data/MC plotting, and CMS combine significance/limits.
mode=1: build pileup-weight CSV files.mode=0withselections/convert_ttbar_cr/config.json, thenselections/jms_jmr/jms_jmr_fit.py: convert the semileptonic ttbar control region with the same jet corrections (without JMS/JMR) and fit the AK8 soft-drop mass scale and resolution (JMS/JMR) that the signal-region conversion applies.mode=0: convert the selected NanoAOD-style inputs into the five analysis treesboosted3,boosted2_resolved,boosted2_unresolved,boosted2_leptonic_1lep, andboosted2_leptonic_2lep, with the official jet energy corrections, Type-1 MET, JMS/JMR, and, for MC, one<tree>__<variation>tree per jet systematic variation in the same files (see Jet energy corrections).mode=6: shuffle each selected MC sample across its ROOT chunks while preserving chunk sizes and filenames, using a deterministic block shuffle that keeps ROOT reads mostly sequential.mode=2: train the BDT or PyTorch NN and save the model plus copied configs. The checked-in config reads the unmixed converted dataset (input_pattern = {input_root}/{sample_group}/{sample}.root) and usesmodel_type: "bdt".mode=3: scan the test split and writesignal_region.csv.mode=5: run the QCD ABCD validation on the MC test split and write the combine inputs.mode=8,mode=9,mode=11,mode=12,mode=13,mode=14: theory, pileup, JES, JER, JMS, and JMR yield ratios per MC sample, tree, and signal region.mode=4: make data/MC comparison plots.mode=7: run the CMS combine wrapper to compute expected significance and AsymptoticLimits per signal class / signal sample / combined, both with the MC-true QCD class yields and with the merged ABCD QCD prediction, with every systematic uncertainty of Systematic uncertainties.
selections/b_veto/ and systematics/ are separate studies and are not launched through run.py.
Usage:
python3 run.py <mode> [config.json] [sample1 sample2 ...] [--slurm ...]run.py is the job dispatcher; the older run.sh does not link correctionlib and cannot build the current convert_branch.C. For the C++ modes it compiles the tool in its directory (auto-detecting OpenMP, and correctionlib through python3 -c "import correctionlib", whose library directory is embedded as an rpath) and, for local runs, deletes the compiled binary at exit; selections/convert/convert_branch is a tracked file, so build test binaries elsewhere. Logs go to log.txt in each mode's directory (or log_<config stem>.txt for a non-default config).
Examples:
python3 run.py 1
python3 run.py 0
python3 run.py 0 selections/convert/config.json www qcd_ht2000
python3 run.py 0 selections/convert_ttbar_cr/config.json
python3 run.py 6
python3 run.py 2
python3 run.py 3 selections/signal_region/config.json
python3 run.py 5 background_estimation/config.json
python3 run.py 8
python3 run.py 11
python3 run.py 4 plotting/config.json
python3 run.py 7 combine/config.jsonModes:
-
mode=0: compile and runselections/convert/convert_branch.C. Input:selections/convert/config.json,selections/convert/branch.json,selections/convert/selection.json,src/sample.json, the pileup CSVs frommode=1, the jet-correction files of thejet_pt_correctionblock, and the JMS/JMR fit result. Output: converted ROOT files under{output_root}/{signal|bkg|data}/, plusselections/convert/log.txt. Each file holds the five nominal analysis trees and, for MC, the<tree>__<variation>jet-systematic trees (see Jet energy corrections).run.py mode=0asksconvert_branch.Cfor the batch count and invokes it once per batch, so each program invocation reads at mostactual_thread_count * 32input files (orCONVERT_FILES_PER_BATCH;--slurm-files-per-jobfor SLURM). Each batch is written first to{output_root}/{sample_group}_tmp/{sample}_X.root; the final batch updatesraw_entriesonce using the whole-sample entry total accumulated across batch bookkeeping and then merges those batch files into the normal finaloutput_patternwhile still honoringmax_output_file_size_gb.resume_successful_batchesdefaults totrue; when enabled, mode 0 requires exactly one actually selected sample and skips only existing batch outputs whose ROOT file opens with every configured output tree and whose.raw_entriessidecar is readable, preserving those files for the final merge. Remote ROOT input open failures are retried five times before the batch fails and the run continues to the next batch. Convert resolves configured output, pileup, branch, and selection paths to absolute paths at startup to reduce sensitivity to stale cwd state in long AFS/NFS jobs. The final writer streams each tree through aTChainclone owned by the chain itself, so branch addresses stay correct when ROOT advances across temp-file boundaries, and it aborts on any chunk/tree entry-count mismatch instead of silently writing too few or repeated entries. The convert expression engine includes collection helpers such assum,max_value,min_value,nth_max_value(collection, expr, rank, default),value_at_max(collection, key_expr, value_expr, default), andvalue_at_nth_max(collection, key_expr, value_expr, rank, default); the checked-in output branch config writes per-AK8WvsQCDandVvsQCDscores, event-levelScoutingFatPFJetRecluster_{WvsQCD,VvsQCD}_{sum,max}summaries, and AK4 b-tag summaries includingScoutingPFJetRecluster2_scoutUParT_probb_max_pt,ScoutingPFJetRecluster2_scoutUParT_probb_sum,ScoutingPFJetRecluster2_scoutUParT_probb_second_max, andScoutingPFJetRecluster2_scoutUParT_probb_second_max_ptfor every tree.selections/convert/check_conversion.pychecks the batch and merged outputs, including every configured variation tree of an MC sample and its listed branches. The pileup root files can be obtained from using wget on these links: https://cms-service-dqmdc.web.cern.ch/CAF/certification/Collisions24/PileUp/dataPileupHistogram-2024CDEFGHI_Golden-66000ub.root, https://cms-service-dqmdc.web.cern.ch/CAF/certification/Collisions24/PileUp/dataPileupHistogram-2024CDEFGHI_Golden-69200ub.root, https://cms-service-dqmdc.web.cern.ch/CAF/certification/Collisions24/PileUp/dataPileupHistogram-2024CDEFGHI_Golden-72400ub.root -
mode=1: compile and runselections/weight/weight.C. Input:selections/weight/config.jsonandsrc/sample.json. Output: pileup CSV files under the configuredoutput_root, plusselections/weight/log.txt. -
mode=6: compile and runselections/mix/mix.C. Input:selections/mix/config.json,src/sample.json, and the converted MC ROOT files frommode=0. Output: shuffled ROOT files under the configuredoutput_root/{signal_mixed|bkg_mixed}/, preserving the original chunk filenames and per-chunk entry counts while applying a deterministic per-tree block shuffle for each selected MC sample, plusselections/mix/log.txt. Missing input files for a requested sample are fatal. The implementation scans input chunk entry counts first (warming ROOT serially, then using OpenMP across the remaining files when available) and then rewrites each tree through aTChainusing shuffled contiguous entry blocks plus an in-block cyclic rotation. Each non-empty output chunk is cloned from theTChainitself so ROOT keeps branch addresses synced across input-file boundaries, and the writer validates both per-chunk and total entry counts before closing. The block size is chosen asclamp(total_entries / 512, min_block_entries, max_block_entries); the checked-in defaults are32and4096. This keeps the output format unchanged while avoiding the old fully random entry-by-entry ROOT read pattern. -
mode=2: runselections/BDT/train.py. Input:selections/BDT/config.json, the ROOT files resolved from itsinput_root/input_pattern(the checked-in config points to the mixedmode=6output under../../dataset/{sample_group}_mixed), andsrc/sample.json. Output: one trained model directory per tree underoutput_root. Withmodel_type: "bdt"it contains{tree}_model.jsonand{tree}_model_stage1.json; withmodel_type: "nn"it contains PyTorch checkpoints{tree}_model.ptand{tree}_model_stage1.ptand requires PyTorch at training/inference time. The rest of the output layout is shared:feature_corr.pdf, loss plots (loss_mlogloss.pdf/loss_classification.pdf/loss_total.pdffor BDT orloss_weighted_ce.pdf/loss_objective.pdffor NN, plusloss_decorrelation.pdf; NN x-axes are epochs, BDT x-axes are boosting rounds), stage-tagged comparison plots such asimportance_cls.pdf/importance_decorr.pdf,roc_*_cls.pdf/roc_*_decorr.pdf,score_*_cls.pdf/score_*_decorr.pdf,decor_corr_{train,test}_cls.pdf/decor_corr_{train,test}_decorr.pdf,decor_score_vs_branch_cls.pdf/decor_score_vs_branch_decorr.pdf, anddecor_branch_shapes_by_signal_score_cls.pdf/decor_branch_shapes_by_signal_score_decorr.pdf. BDT feature importance uses XGBoost gain; NN feature importance uses deterministic permutation importance while preserving the same filenames. The output also includes abranches/subdirectory with one normalized per-class input-distribution PDF per training branch, copiedconfig.json/branch.json/selection.json,test_ranges.json, and the saved test-set prediction referencestest_reference_signal_region.npz,test_reference_qcd_est.npz, andtest_reference_qcd_est_full.npz, plusselections/BDT/log.txt. Both model types keep the same sample splitting, clipping/log preprocessing, event-weight construction, dynamic learning-rate reduction, prediction-reference validation, and downstream score interface. Per-treeqcd_ht_training_weight_stepscales only the internal train.py training/evaluationweightforqcd_ht*samples in increasing HT order before the existing class balancing; saved prediction-reference weights keep the original physicsweight_physics. When a per-treedecorrelatebranch also appears inselection.jsonthresholds, that branch's threshold is normally omitted from the training/early-stopping objective splits and their class balancing; for decorrelate branches namedScoutingFatPFJetRecluster_msoftdrop_*, the training view instead applies a loose(0, decor_msoftdrop_training_max)window when that branch has a selection threshold. Ordinary branch, ROC, score, feature-importance, feature-correlation, and downstream reference outputs still use the full threshold set, while decorrelation diagnostics use the training-threshold view. BDT logs sum-scaleclassification_lossand stage 1 monitors testclassification_loss; NN uses class-balanced mini-batches with sampling-corrected event weights forbackward()and logs epoch-end eval-modetrain_objective_loss/test_objective_loss, where stage 1 usesobjective_loss = weighted_CEand stage 2 usesobjective_loss = weighted_CE + scaled_smooth_CvM_decorrelation. NN early stopping monitors epoch-end testobjective_lossin both stages, and AdamWweight_decayremains native decoupled optimizer behavior rather than a logged loss term. In NN mode, per-tree{tree}_nnblocks (e.g.boosted3_nn) configure the PyTorch MLP (hidden_layers,dropout,batch_norm,batch_size, learning rates, weight decay, epochs, and permutation-importance event count);batch_sizeis the total size of each class-balanced mini-batch, the checked-in NN blocks setbatch_norm: false, and smooth-CvM decorrelation is supported in the second NN stage with decorrelation scale calibrated against the stage-1 batch-averaged weighted CE. -
mode=3: runselections/signal_region/signal_region.py. Input:selections/signal_region/config.jsonand one trained model directory frommode=2. Output:sr_score_*.pdf,scores_no_regions.pdf,scores.pdf,scores_no_regions_radial_equalized.pdf,scores_radial_equalized.pdf, andsignal_region.csvinside the configuredoutput_dir, plusselections/signal_region/log.txt. The 2D score PDFs use the shared class palette, hide coordinate axes/ticks/frames, and include both the original regular-polygon simplex projection and a radial-equalized projection that keeps polygon vertices fixed while spreading points outward. The script builds one shared candidate pool of general high-dimensional rectangles over the configured BDTscore_axes(allin the checked-in config), where every score axis has an independent[low, high)interval and can therefore represent simultaneous lower and upper cuts. Candidate generation combines dense per-axis boundary sets from total/signal/background weighted quantiles plus tail-heavy quantiles, explicit low/mid/high-tail single-axis seeds fromseed_quantiles, exact single-axis bounded interval seeds, optional bounded multi-axis seed combinations, deterministic beam coordinate search where every per-axis update scans exact[edges[a], edges[b])intervals, compatibility expansion that optimizes regions constrained to be non-overlapping with high-Z anchor candidates, and local event-threshold refinement. Independent edge building, beam updates, compatibility updates, local-refinement workers, and event-mask canonicalization can usemax_threads, but each stage merges results in deterministic scan order so the candidate set is unchanged by threading. No candidate-pool or global-candidate count limit is applied; candidates are removed only by rounded geometry duplicates and by final exact event-mask/canonical-box deduplication. Before global selection, every retained candidate is shrunk to an event-preserving minimal score box; candidates are deduped only when they select the same exact event mask and shrink to the same canonical box. The final selection is global rather than sequential: it searches K mutually non-overlapping candidates and maximizes the existing combined objectivesqrt(sum Z_i^2), with Python and OpenMP compatibility checks using the same exact half-open[low, high)overlap semantics as event-mask membership. The OpenMP helper first finds a beam incumbent and then runs branch-and-bound; if the search completes, the result is exact for the finite candidate list, while node caps produce a conservative upper-bound certificate. After the K non-overlapping SRs are selected, an empty-bin expansion step runs in order SR1, SR2, …: each SR has its signal-class score-axis upper bounds pushed toward1.0and its background-class score-axis lower bounds pushed toward0.0, but only into space that is empty in MC under the SR's other-axis cuts and only as far as the expanded box remains geometrically non-overlapping with every other selected SR. The expansion is constrained to keep each SR's exact selected event mask, so per-binS/B/Zare numerically unchanged; only the empty-side score-axis bounds insignal_region.csvare widened. Before scanning, the script reloads the saved model, reproduces the saved full-threshold signal-region test prediction, and aborts if it does not matchtest_reference_signal_region.npzwithin the stored tolerances. -
mode=4: runplotting/data_mc.py. Input:plotting/config.json,plotting/branch.json, the converted ROOT files frommode=0, and one trained model directory frommode=2. Output: one physical-normalization PDF per plotted branch under the configuredoutput_root, plus a matching{tree_name}_{branch}_normal.pdfwhere the data histogram integral and total stacked MC histogram integral are each scaled to 1 withA.U.on the y-axis, plusplotting/log.txt. Ordinary ROOT branches are discovered from the convert output config and are not limited to the BDT input branch list. MC uses the configured trained-model input pattern for ordinary ROOT branches and reads at most the copied BDT config'sentries_per_sampleentries per sample while keeping the same full-sample normalization. Data reads all configured entries from the non-mixed data directory: if the copied pattern contains{sample_group}_mixed, data resolves it as{sample_group}and therefore usesdata, notdata_mixed. Missing sample files, empty MC trees/classes before filtering, and non-positive MCraw_entriesare treated as fatal errors. In addition to ROOT branches, one derived model score branch per class is plotted asscore_{class_name}. For those score branches, MC is read frombdt_root/test_ranges.json, validated againsttest_reference_signal_region.npz, and normalised to the same full-sample target total while data is predicted from the full configureddata_samplesinput. The script loads.json/.pklBDT models or.ptNN checkpoints according to the copiedmodel_type. -
mode=5: runbackground_estimation/qcd_est.py. Input:background_estimation/config.json, one trained model directory frommode=2, and thesignal_region.csvwritten bymode=3. Output: ABCD summary PDFs and one ROOT file under the configuredoutput_dir, plusbackground_estimation/log.txt. The validation PDFs keep the finite-MC / ABCD-propagated uncertainties based onsum(w^2). Theqcd_abcd_region_counts.pdfandqcd_abcd_region_fractions.pdfsummaries are stacked by the BDTclass_groupsclasses with a shared fixed class palette; additional colors are generated deterministically when there are more classes than base colors, andqcd_abcd_region_counts_linear.pdfsaves the same counts with a linear y axis. The QCD-merged summaries (qcd_abcd_region_counts_qcd_merged.pdf,qcd_abcd_region_counts_qcd_merged_linear.pdf, andqcd_abcd_region_fractions_qcd_merged.pdf) combine everyclass_groupsclass whose name containsqcdcase-insensitively into oneQCDstack color and legend entry, leaving non-QCD classes unchanged. Optional per-treea_region_shape_branchesentries add normalized branch-shape diagnostics underoutput_dir/a_region_shapes/: for each configured branch, qcd_est writes one PDF for every signal bin plus one PDF for the full A-union, with one unit-area weighted step histogram per class and no ABCD prediction scaling; values<= -10are dropped before binning/normalization so missing-slot values such as-99do not set the visible range. The ROOT file stores combine-facing bundles for every saved category (samples/*,groups/*,qcd_predict,qcd_true,total_predict, andtotal_true) assrN/yield,srN/stat_error, andsrN/scale_errorone-bin histograms plus a category-levelcovariance_totalTH2. The combine-facing convention isstat_error = sqrt(yield),scale_error = 0, and diagonalcovariance_total = diag(yield), so the weighted yield is treated as the Poisson event count. Every SR of every category also storessrN/mc_stat_error = sqrt(sum w^2)of the test-split MC, the input of combine'sautoMCStats; forqcd_predict(andtotal_predict) it is the per-SR part of the ABCD MC statistics, from the A-region fractions, andsrN/mc_scale_erroradds the SR-correlated part from the B/C/D statistics.metadata/abcd_closure(one row) storesraw_scale,raw_scale_error,multiplier,final_scale(the ABCD scalek = pred_union / A_union),final_scale_error,pred_union,pred_union_error, and the QCDqcd_a_union,qcd_b,qcd_c,qcd_dsums with their*_error. QCD classes are identified byclass_groupsnames containingqcdcase-insensitively; all samples from those classes are summed for the ABCDA/B/C/Dtotals,qcd_true,qcd_predict, andtotal_predict, while the per-classgroups/*outputs remain MC-true. The signal-region score axes are detected from the CSV{axis}_low/{axis}_highcolumns, so the ABCD step can consume either the legacy independent axes or the new all-score-axis regions. The non-score ABCD dimension is configured by required per-treeabcd_branches; every listed branch must have a threshold in the copied BDTselection.json, A/B require all listed branches to pass, C/D require all listed branches to fail, and partial pass/fail events are excluded. Before building the ABCD regions, the script reloads the saved model, validates againsttest_reference_qcd_est_full.npzwhen present (falling back to the legacy filteredtest_reference_qcd_est.npz), and aborts on mismatch. -
mode=7: compile and runcombine/combine.C. Input:combine/config.json(lists one or more channels, each pointing to aqcd_abcd_yields.rootfrommode=5) plus one trainedbdt_rootdirectory frommode=2, and the*_syst_yields.jsonof modes 8, 9, and 11–14.combine.Creadsclass_groupsfrombdt_root/config.json, then resolves that copied config'ssample_configthe same way asqcd_est.pyso the signal/background-class split still comes fromsample.json. Group names fromclass_groupsare matched againstgroups/*in the ROOT file by the same slugified lowercase convention used byqcd_est.py, and QCD classes are the class names containingqcdcase-insensitively. Must be executed inside a CMSSW area that hasHiggsAnalysis/CombinedLimitbuilt, so thatcombineandcombineCards.pyare on$PATH(tested with combine v10.0.2 in CMSSW_14_1_0; aCMSSW_*area inside the repository is ignored by.gitignore). Output:significance.csv,limits.csv,significance_abcd_mc.csv,limits_abcd_mc.csv,significance_by_channel.csv,limits_by_channel.csv,significance_by_channel_abcd_mc.csv, andlimits_by_channel_abcd_mc.csvunder the configuredoutput_dir, pluscombine/log.txt. Each CSV has one row per signal scenario (combined / per-signal-class / per-signal-sample); the per-channel CSVs add achannelcolumn and rerun the same scenarios using one channel at a time. Every channel card is a shape card with one bin per SR (<channel>_sr<N>): one-bin histograms per process carry the rate and, as bin error, the MC statisticssqrt(sum w^2)(srN/mc_stat_error) used by* autoMCStats <mc_stat_threshold> 0 1; rates and the Asimov observation (the sum of the process rates) are also written explicitly, so a process without yield in an SR drops out of that bin. combine runs on that Asimov data (-t -1). The_abcd_mc.csvCSVs replace all QCD classes with the single merged ABCD prediction, processbkg_qcd_abcd(an MC-true QCD class keeps itsbkg_<class>name), while keeping the non-QCD processes on their MC-true group/sample blocks. The nuisances are listed in Systematic uncertainties; each enabled nuisance needs its input, and a missing input, a non-positive lnN kappa, or an unknownenabled_nuisancesname stops the wrapper with an error. The wrapper validates every required signal sample/group and aborts on missing inputs or invalid shapes instead of regularizing the yields. Ifuse_root_covariance=true(only with themcstatnuisance disabled, since both model the statistical uncertainty of the predictions), the full per-process covariance between signal regions is additionally injected via an eigen-decomposition of each process'scovariance_total, one Gaussian shape nuisance per retained eigenmode. In that optional mode, whenrescale_shape_modes_to_positiveistrue(default), any background process whose nominal yields and covariance are identically zero across all SRs is dropped before writing the datacard, and any eigenmode whose raw±1σtemplates would make a varying bin negative or a total template norm non-positive is shrunk to the largest safe step below that boundary; the datacardshapecoefficient is rescaled by1/a, zero-valued bins are allowed as long as the varied templates stay non-negative and keep strictly positive total norms, and every drop/rescale is written as a warning tocombine/log.txt. If a per-sample signal process is identically zero in some channels, those channels are skipped with a warning; if it is identically zero in every channel, the wrapper records0significance andinfexpected limits for that row and continues. IfAsymptoticLimitsfinishes successfully but its ROOT output still lacks the expected quantiles, the wrapper logs a warning, records0for that row's significance andinffor its expected limits in the CSVs, and continues. A work directory is kept underoutput_dir/work/with the generated datacards, shape ROOT files, and combine outputs. -
mode=8: runselections/theory_weights/theory_syst.py(PDF, scale, ISR, and FSR ratios of the samples withhas_theory_weights, from the nominal trees' theory-weight branches). -
mode=9: runselections/pileup_syst/pileup_syst.py(weight_pu_up/weight_pu_downoverweight_pu). -
mode=10: runplotting/class_shapes.py. -
mode=11,mode=12,mode=13,mode=14: runselections/{jes,jer,jms,jmr}_syst/<syst>_syst.py, thin wrappers aroundselections/jet_syst_common/jet_variation_common.pythat compare each<tree>__<syst>_{up,down}tree with its nominal tree in the same converted files. Every syst script writes<name>_syst_yields.{json,csv}under itsoutput_dirwith, per MC sample and tree, the inclusive up/down ratios and aregionsentry per signal region; its regions are the ABCD A regions ofqcd_est.py(the BDTselection.jsonthresholds, the ABCD window included, with sentinel values failing, and thesignal_region.csvscore boxes). A region with fewer thanmin_region_events(10) nominal MC events takes the inclusive ratios and is flaggedfallback_to_inclusive: with fewer events the ratio measures the MC fluctuation rather than the variation. Missing files, trees, branches, or configured inputs are errors.merge_shards.pyin each jet-syst directory merges per-sample SLURM shard outputs.
Sample arguments:
- Extra sample names are supported only for
mode=0,mode=1, andmode=6. - If no sample names are given,
run.pyusessubmit_samplesfrom the chosen config. - If
submit_samplesis empty or missing, all MC samples insrc/sample.jsonare used.
Log archiving:
- After the final
[finished …]line is written,run.pycopies the per-modelog.txtinto the program's configured output directory for local runs. - Applies to
mode=1(output_root),mode=2/mode=4(per-treeoutput_rootexpanded oversubmit_trees), andmode=3/mode=5/mode=7(output_dir).mode=0(convert_branch) andmode=6(mix) are intentionally skipped. - The copy runs on both success and failure; if the resolved output dir does not yet exist (e.g., the run aborted before creating it), the copy is skipped with a warning instead of erroring.
src/sample.json: master sample registry withname,path,sample_ID,is_MC,is_signal, optionalhas_theory_weights,xsection,lumi, andraw_entries. The MC entries (names, cross sections,raw_entries,genweight_mean,weight_pu*_mean) are those of the 2026-10 reconversion; the VH samples keep their dataset-style names such asZH-Zto2Q-Hto2Wto4QandWplusH-Wto2Q-Hto2Wto4Qand the cross sections confirmed for this analysis, not the short names and cross sections ofScoutingVVVTools_sf/src/sample.json.data_2024is the 2024G/H/I data of the analysis;2024G_UParT_v6is the 2024G data used for the data/MC plots and the ttbar control region.selections/weight/config.json: pileup histogram inputs and pileup-weight output paths.selections/convert/config.json: convert-step paths, threading, file-size splitting,resume_successful_batchesfor single-sample batch resumption, pileup CSV pattern, and thejet_pt_correctionblock (see Jet energy corrections).selections/convert_ttbar_cr/config.json: the ttbar control-region conversion for the JMS/JMR fit; itsjet_pt_correctionblock applies the same corrections withapply_jms_jmr: falseand no variations. It converts the2024G_UParT_v6data with the golden JSONselections/convert/Cert_Collisions2024_378981_386951_Golden.json(lumi_mask); thedata_samplesofplotting/config_ttbar_cr.json, whichjms_jmr_fit.pyreads throughsamples_from(the fit takes the luminosity fromsrc/sample.json), name the same data.selections/mix/config.json: mix-step tree selection, input/output ROOT roots and patterns, sample config, threading for the input-chunk scan, deterministicrandom_state, and the block-size boundsmin_block_entries/max_block_entries(default32/4096).selections/convert/selection.json: event preselection (input scalars only, evaluated before the jet corrections), runtime collections, and the tree split into the five analysis trees.selections/convert/branch.json: input branches to read and output branches to write.selections/BDT/config.json: model inputs,input_root/input_pattern, class groups, training settings, output directories, top-levelmodel_type(bdtornn, defaultbdt), top-leveldecor_loss_mode,decor_lambda,decor_n_bins,decor_n_thresholds,decor_score_tau,decor_bin_tau_scale,decor_msoftdrop_training_max(positive upper edge for the loose(0, max)training window used only when a decorrelate branch namedScoutingFatPFJetRecluster_msoftdrop_*also has aselection.jsonthreshold), per-tree BDT hyperparameters (n_estimators,n_estimators_decorr,max_depth,learning_rate,learning_rate_decorr, optionalmin_learning_rate/lr_reduce_patience,gamma,reg_lambda,reg_alpha,min_child_weight,subsample,colsample_bytree, optionalcolsample_bynode,early_stopping_rounds), per-tree NN hyperparameters in{tree}_nnblocks (e.g.boosted3_nn) (epochs,epochs_decorr,hidden_layers,activation,dropout,batch_norm,batch_size,learning_rate,learning_rate_decorr, optionalmin_learning_rate/lr_reduce_patience,weight_decay, optionalgrad_clip_normdefaulting off,early_stopping_rounds,permutation_importance_events; checked-in NN values uselearning_rate: 0.001,learning_rate_decorr: 0.0005, andmin_learning_rate: 0.00002),decorrelate,decor_efficiencies,event_reweight_branches, and per-treeqcd_ht_training_weight_stepfor additive QCD HT training-weight scaling.selections/signal_region/config.json: signal-region scan settings:lumi,n_signal_regions/N,bdt_root,output_dir,score_axes,min_bkg_weight,min_signal_weight, optional entry minima,max_edge_candidates_per_axis,beam_width,top_intervals_per_axis,coordinate_rounds,seed_intervals_per_axis,multi_axis_seed_max_axes,multi_axis_seed_max_seeds,compatibility_seed_anchors,compatibility_seed_rounds,local_refine_rounds,local_refine_neighbor_edges,local_refine_top_candidates,local_refine_diverse_masks,local_refine_candidate_overscan,global_beam_width,branch_bound_max_nodes,branch_bound_time_limit_seconds,deduplicate_event_masks,require_exact_n_regions,max_threads,progress_every_seconds, andseed_quantiles.background_estimation/config.json:qcd_est.pysettings, includingbdt_root,signal_region_csv,output_dir,root_file_name, required per-treeabcd_branchesentries whose thresholds are read from the copied BDTselection.json, and optional per-treea_region_shape_branchesentries for normalized per-class branch-shape plots in each signal bin and the A-union.plotting/config.json:data_mc.pysettings, includingbdt_root,output_root,data_samples(["2024G_UParT_v6"]), and per-treeevent_reweight_branches(applied to MC events only; data weights stay 1.0). Ordinary MC branch reads useentries_per_samplefrom the copied BDT config underbdt_root; data reads are not capped and use the non-mixeddatadirectory when the copied BDT pattern points MC at{sample_group}_mixed. Each branch writes both the physical-normalization plot and a_normal.pdfunit-area comparison where data and total MC are separately normalized to 1.plotting/branch.json: per-tree plot overrides such asskip_branches,bins,x_range,y_range,logx, andlogy; derived model score branches use names likescore_VVVand accept the same overrides.combine/config.json:combine.Csettings:channels(list of{name, root_file, bdt_root}— each ROOT file is theqcd_abcd_yields.rootoutput ofmode=5for one channel, and eachbdt_rootis that channel's trained tree output directory),output_dir, optionalcombine_cmd/combine_cards_cmd,use_root_covariance(defaultfalse; combine uses binned Poisson statistics from the yields and does not turn ROOT covariance into nuisances),eigen_rel_cutoff(used only whenuse_root_covariance=true, dropping eigenmodes withλ_k ≤ cutoff × max(diag(cov)); default1e-10),rescale_shape_modes_to_positive(used only when covariance nuisances are enabled; defaulttrue),keep_work(keep the generated datacards underoutput_dir/work/; defaulttrue),lumi_uncandtrigger_unc(flat lnN fractions,0.016and0.03),mc_stat_threshold(autoMCStatsevent threshold, default10), thetheory_syst_json,pileup_syst_json,jes_syst_json,jer_syst_json,jms_syst_json, andjmr_syst_jsoninputs, andenabled_nuisances(empty = all; otherwise any oftheory_pdf,theory_scale,theory_ps_isr,theory_ps_fsr,pileup,jes,jer,jms,jmr,lumi,trigger,mcstat,abcd_nonclosure,abcd_mcstat).selections/{theory_weights,pileup_syst,jes_syst,jer_syst,jms_syst,jmr_syst}/config.json: syst-script inputs (input_root,input_pattern,sample_config, per-treebdt_rootandsignal_region_csv),submit_trees,submit_samples,min_region_events(10),output_dir, and for the jet systematicsweight_branch. The removedinput_root_up/input_root_downkeys are rejected.
mode=1writes the pileup CSVs used bymode=0for MC samples.mode=0with the ttbar-CR config writes thettbar_crtrees thatselections/jms_jmr/jms_jmr_fit.pyfits intoselections/jms_jmr/output/jms_jmr_results.json, the JMS/JMR input of the signal-region conversion.mode=0writes the five converted analysis trees used directly bymode=4, and as the input source formode=6ormode=2; the MC files also hold the<tree>__<variation>trees read bymode=11–mode=14.mode=6can rewrite those converted MC trees into{signal_mixed|bkg_mixed}directories under the same dataset root with the same chunk layout using a deterministic block shuffle, which the checked-in model-training config uses by default.mode=2writes the model, copied configs, and saved test-set prediction references used bymode=3,mode=4, andmode=5.mode=3writessignal_region.csv, which defines the A-region score bins formode=5.mode=5writes the ABCD validation ROOT file and PDFs for the chosen tree.mode=4writes one physical-normalization data/MC comparison PDF per branch plus a_normal.pdfunit-area version.mode=7reads one or moremode=5ROOT files (one per channel) plus the BDT and sample configs, generates per-scenario datacards, and callscombineto fillsignificance.csv,limits.csv,significance_abcd_mc.csv, andlimits_abcd_mc.csvunder itsoutput_dir; it also reruns the same scenarios per individual channel and writes the matching*_by_channel*.csvfiles. Combined/per-class scenarios use the storedgroups/*/srN/yieldone-bin histograms, per-sample scenarios usesamples/*/srN/yield, and ABCD-mode scenarios replace every QCD class with the mergedqcd_predict/srN/yield. Each SR is one bin of a one-bin-per-SR shape card whose histogram errors carry the MC statistics forautoMCStats; the lnN nuisances come from themode=8,9,11–14JSON files, the flat lumi/trigger values, and the ABCD scale factors inmetadata/abcd_closure. Settinguse_root_covariance=true(withmcstatdisabled) adds the optional eigen-decomposed covariance shape nuisances.mode=8,mode=9, andmode=11–mode=14read the converted trees, the trained model, andsignal_region.csv, and write the*_syst_yields.jsonratios read bymode=7.
convert_branch.C (JetPtCorrector) corrects the AK4 ScoutingPFJetRecluster2 and AK8 ScoutingFatPFJetRecluster jets (plain PF jets, neither CHS nor PUPPI, matching AK4PFHLT/AK8PFHLT) and the MET before any selection or output reads an event. The jet_pt_correction block of the convert config configures it.
- JEC (MC and data): the official HLT MC-truth JEC
Winter24HLT_V1_MC_L1L2L3Res_{AK4,AK8}PFHLT(JECDatabase); there is no residual (data/MC) correction (L2L3Residualreturns 1.0), and the JERC group recommends these JECs for the 2024 simulation and the 2024CDEFGHI data. The JSON files are copied from/eos/cms/store/group/phys_jetmet/ExpJEC/json_files_HLT/Run3Winter24/intoselections/convert/jec/Run3Winter24/. The AK4 inputs already carry this JEC from the ScoutingNano production (raw = stored × (1 −rawFactor) for pT and mass), so the stored AK4 pT and mass are the nominal JEC result; the AK8 inputs are stored raw and get the JEC. - Event rho: the inputs do not store rho, so it is recovered from the AK4 production JEC: stored / raw = C(JetA, JetEta, JetPhi, raw pT, rho) falls with rho up to the L1FastJet clamp at 70 GeV, one jet gives rho, and every AK4 jet must reproduce its stored factor within 0.5 GeV of it (a jet whose stored eta or phi lies on a JEC bin edge may use the neighbouring bin). An event whose AK4 jets have no common rho stops the conversion with an error naming the event. On the v4 data, which store
ScoutingRho_fixedGridRhoFastjetAll, recovered − true rho has a mean of −0.004 GeV and an RMS of 0.018 GeV, without failure in 20000 events. - JER (MC): the offline proxy
Summer23BPixPrompt23_RunD_JRV1_MC_{PtResolution,ScaleFactor}_AK4PFPuppi(JME-POG2024_Summer24/jet_jerc.json.gz) with the stochasticJERSmeartool (jer_smear.json.gz,GenPt = −1), for AK4 and AK8 alike: CMS provides no JER for scouting jets and none for AK8. The nominal MC is smeared with thenomscale factor. The resolution lookup clamps rho below 52.05, the end of its Rho axis. JERSmear draws its random number from (JetPt, JetEta, Rho, EventID), which are the same in every configuration, sojer_up/jer_downchange only the scale factor. Data are not smeared. - JES uncertainty (MC): a flat ±10% per jet (
jes_shift) on pT, mass, and soft-drop mass after the JER smearing, one nuisance for AK4 and AK8 (fully correlated). Source: the JERC recommendation for scouting jets quotes the closure of the online HLT JECs on scouting jets as "within < 2% forScoutingPFJetand within < 5-10% forScoutingPFJetRecluster" (presentations: https://indico.cern.ch/event/1661768/, https://indico.cern.ch/event/1667359/); both our collections are reclustered, and 10% is the upper edge. The value covers only the non-closure of the MC-truth correction: residual (data/MC) corrections are neither applied nor covered, and CMS gives no closure for scouting AK8 jets, so using the same value for AK8 is our choice. - AK8 soft-drop mass: every factor of the jet pT except L1FastJet (grooming removes most of the pileup), i.e. L2L3, the JER smearing, and the JES shift, and, for MC with
apply_jms_jmr, the CMS-standard JMS scale and stochastic JMR smearingm × JMS × (1 + N(0,1) × sqrt(max(JMR² − 1, 0)) × σ_MC/μ_MC)(no smearing for JMR ≤ 1), with JMS, JMR, and the MC W-peak width σ_MC/μ_MC fromselections/jms_jmr/output/jms_jmr_results.json. The checked-in fit predates these corrections (JMS = 1.071, +7% on the MC soft-drop mass; JMR = 0.52); re-measure it from the corrected ttbar control region. - Type-1 MET: MET = raw MET − Σ (corrected − L1FastJet-only) muon-subtracted pT (muon fraction
muEF) over the AK4 jets whose corrected, unsmeared, muon-subtracted pT is above 15 GeV and whose EM fractionchEmEF + neEmEFis below 0.9. The JER and JES variations move the MET through the same jets.
Single pass, variation trees. Each MC event is read once and corrected with the nominal configuration and each of the variations (jes_up, jes_down, jer_up, jer_down, jms_up, jms_down, jmr_up, jmr_down). Every configuration rebuilds the collections, re-applies the tree selections, and fills <tree>__<variation> trees in the same output file. A variation tree books the variation_branches list of its tree (the BDT features, thresholds, and decorrelation branches plus run, luminosityBlock, event, weight_pu) and evaluates only the scalar outputs those branches depend on. The variation trees are identical, event by event, to the nominal trees of a conversion with debug_nominal_configuration set to that variation (a validation setting that fills the nominal trees with one configuration). Data get the nominal configuration only.
Keys. enabled, nominal_correction (hlt_jec, or the legacy scouting_to_offline response SF of corrections_file with ak4_tag_threshold / ak8_tag_threshold), jec_ak4_file, jec_ak8_file, jec_ak4_name, jec_ak8_name, jec_ak4_l1_name, jec_ak8_l1_name, jes_jer_file, jer_smear_file, jer_resolution_name, jer_scale_factor_name, jes_shift, apply_jms_jmr, jms_jmr_results_file, variations, variation_branches, and debug_nominal_configuration. The keys variation, jes_unc_name, and jer_rho_fallback of the removed one-variation-per-conversion scheme are rejected; selections/convert/config_{jes,jer}_{up,down}.json are kept for reference only. The event preselection runs before the corrections on input scalars and must not read ScoutingMET_pt / ScoutingMET_phi. variation_branches must list, per tree, every BDT feature (selections/BDT/branch.json), threshold branch (selection.json), and decorrelation branch (config.json decorrelate) of the trained model, plus run, luminosityBlock, event, and weight_pu; after a change of the BDT inputs, update it (the jet syst scripts stop with an error naming a missing branch).
Cost. On one QCD HT1500–2000 file (about 200k preselected events) the corrections add about 12% CPU to a nominal-only conversion (7% for data), and the eight MC variation configurations bring the total to about 4.2 times the nominal-only conversion: each variation costs about 40% of a separate conversion, because the input is read once and the variation trees evaluate only their needed outputs. Runtime collections independent of the jet corrections (the GenPart-based w_quarks, wz_had_quarks) are built once per event (logged at startup). Plan the MC conversion jobs for that CPU time.
Environment. convert_branch.C builds in the CMSSW_16_1_0_pre4 (el9_amd64_gcc13) environment with CMSSW's correctionlib 2.7.0. run.py locates correctionlib through python3 -c "import correctionlib", so a user-site copy (~/.local/lib/python3.9/site-packages/correctionlib) takes precedence over CMSSW's; check python3 -c "import correctionlib; print(correctionlib.__version__)" before building.
combine.C fits the Asimov pseudo-data (the sum of the nominal process rates, -t -1). Every channel card contains:
| Nuisance | Type, value | Processes | Correlation |
|---|---|---|---|
lumi |
lnN 1.016 (lumi_unc) |
MC processes | all processes, SRs, and channels |
trigger |
lnN 1.03 (trigger_unc), a flat value set by the user; no trigger scale factor is measured |
MC processes | all |
pileup |
lnN from weight_pu_up / weight_pu_down (mode 9) |
MC processes | all |
theory_pdf |
lnN, symmetric-Hessian PDF (+ αs when stored) spread (mode 8) | MC processes; samples without theory weights count with ratio 1 | all |
theory_scale |
lnN, 7-point μR/μF envelope (mode 8) | as theory_pdf |
all |
theory_ps_isr, theory_ps_fsr |
lnN, parton-shower murfac 0.5 (up) and 2.0 (down) (mode 8) |
as theory_pdf |
all |
jes |
lnN, flat ±10% per jet (mode 11) | MC processes | all |
jer |
lnN, JER scale factor up/down (mode 12) | MC processes | all |
jms |
lnN, JMS ± its uncertainty (mode 13) | MC processes | all |
jmr |
lnN, JMR ± its uncertainty (mode 14; no effect while JMR ± σ ≤ 1) | MC processes | all |
MC statistics (mcstat) |
* autoMCStats 10 0 1 from the bin errors sqrt(sum w^2) of the test split (include-signal = 0 still models the signal statistics) |
all processes; for bkg_qcd_abcd the per-SR part |
per bin |
abcd_nonclosure_<channel> |
lnN 1 + |k − 1|, k = final_scale = pred_union / A_union |
bkg_qcd_abcd |
all SRs of the channel; independent between channels |
abcd_mcstat_<channel> |
lnN 1 + σ(pred_union) / pred_union, the B/C/D MC statistics | bkg_qcd_abcd |
all SRs of the channel; independent between channels |
The per-sample ratios come from the syst scripts (per SR; regions with fewer than 10 MC events take the inclusive ratio). A process kappa in an SR is the yield-weighted mean of its samples' ratios, weighted by the test-split SR yields of qcd_est.py; asymmetric kappas are written κ_down/κ_up. A non-positive kappa (a variation removing the whole yield) is an error. The MC-based nuisances do not act on the ABCD prediction bkg_qcd_abcd, which in this Asimov study is built from MC B/C/D regions and takes the ABCD rows instead; in the MC-true scenarios the QCD class is an MC process with every MC nuisance.
Not included:
- Residual (data/MC) JEC and its uncertainty, and a JES split into sources: no HLT residuals exist for scouting jets.
- A scouting or AK8 JER, and the hybrid (gen-matched) JER smearing: only the offline AK4PFPuppi proxy exists, and GenJetAK8 is available only in the
jetMatchFixsamples. - The unclustered-energy MET uncertainty: the Type-1 MET propagates only the jet corrections and their variations.
- Jet-tagging scale factors (the AK8 ParT W/V tagging and the AK4 b-veto): none exist for scouting jets.
- Lepton identification, isolation, and veto efficiencies.
- Cross-section (normalisation) uncertainties of the signal and background processes.
- Data statistics of the ABCD B/C/D regions: the ABCD prediction uses MC; with data, those statistics replace
abcd_mcstat_<channel>.