Full workflow walkthrough¶
This is the end-to-end tour of the pipeline: every stage, in order, with the command that runs it. Read it once to understand the chain; in day-to-day work you usually run only the last stage you need and let LAW produce the rest (see the shortcut).
The commands use FLAF.AnaProd.tasks.* for production stages and FLAF.Analysis.tasks.* for
analysis stages — the fully-qualified task paths the framework registers.
flowchart TD
S0["Stage 0: InputFileTask<br/>(file list per dataset)"]
S1["Stage 1: AnaTupleFileTask, then AnaTupleMergeTask<br/>(per-file, then merged anaTuples)"]
C["AnalysisCacheTask<br/>(payload producers, only if needed)"]
S2["Stage 2: HistTupleProducerTask<br/>(histTuples)"]
S3["Stage 3: HistFromNtupleProducerTask, then HistMergerTask<br/>(histograms, one merged file per variable)"]
S4["Stage 4: HistPlotTask<br/>(plots)"]
S5["Stage 5: StatInference / dhi<br/>(datacards, limits)"]
S0 --> S1
S1 --> S2
S1 -.-> C
C -.-> S2
S2 --> S3
S3 --> S4
S3 -- "read as input" --> S5
Launching a stage makes LAW run the stages above it that are missing; the dashed path runs only when the analysis needs it (Stage 2). Stage 5 is the exception: it reads the merged histograms but does not produce them.
Setup recap¶
cd HH_bbtautau # your analysis repository
source env.sh # once per shell
voms-proxy-info # confirm a valid proxy
# Pick a data-taking era and a label for this production:
ERA=Run3_2022
VER=dev
Throughout, --era $ERA selects the era (several, separated by commas,
run every stage for all of them: Running several eras)
and --version $VER namespaces the outputs. Add --workflow local
to run on this machine; switch to --workflow htcondor to scale up
(HTCondor guide). Always pass --workflow: without it, LAW submits to HTCondor.
Stage 0 — Resolve the input files¶
InputFileTask turns "the datasets for this era" into a concrete list of NanoAOD files per
dataset. It lists them from the dataset's own fs_nanoAOD if it has one; otherwise from the era's
fs_nanoAOD (the HLepRare skims) when nanoAODVersions selects HLepRare for data or MC (the
default when it is unset), or through Rucio for the DAS dataset that the dataset's nanoAOD: entry
names for the selected version
(see Where the inputs come from).
Everything else depends on it, so it runs first — automatically when you launch a later stage, or
explicitly:
It always runs locally and is fast and cheap. If a from-scratch run unexpectedly stays in
InputFileTask or fails here, suspect a wrong --era/--version or an expired proxy.
Stage 1 — Produce and merge analysis ntuples (anaTuples)¶
AnaTupleFileTask runs the producer (AnaProd/anaTupleProducer.py, driven by the analysis's
anaTupleDef) over each NanoAOD file — one branch per file — applying the object selections
and corrections (a Corrections object is built per dataset, era and stage) and writing a slimmed
anaTuple plus a JSON report. It runs in the FLAF environment, or inside CMSSW when the analysis
sets use_cmssw_env_AnaTupleProduction: true (HH→bb̄ττ does). Before the first production jobs,
AnaTupleCostProbeTask times the producer on a few thousand events of one file per dataset so
that branches can be
packed into jobs by cost (unless
anaTuple_scheduling.probe_enabled is false).
AnaTupleFileListBuilderTask then reads the reports and writes a merge plan per dataset, and
AnaTupleMergeTask merges the per-file pieces following it into files of about nEventsPerFile
events (anaTuple_<N>.root, one branch per merged file), so a dataset usually ends up with
several. All data datasets of the era are merged into one sample, data. See
How the anaTuples are merged.
# Produce per-file anaTuples (heavy; normally on HTCondor):
law run FLAF.AnaProd.tasks.AnaTupleFileTask --era $ERA --version $VER --workflow local
# Build the merge plans and merge (runs the steps above first if needed):
law run FLAF.AnaProd.tasks.AnaTupleMergeTask --era $ERA --version $VER --workflow local
Test on a few files first
--test 1000 keeps only the first input file of every dataset and processes at most 1000
events of it. --branches 0,1,2 additionally restricts AnaTupleFileTask to its first three
branches — with --test, the first file of each of the first three datasets. Give test runs
their own --version: their outputs have the same paths as a full run's
(see --test).
Stage 2 — Compute analysis observables (histTuples)¶
HistTupleProducerTask runs Analysis/HistTupleProducer.py with the analysis's histTupleDef
over each merged anaTuple file — one branch per merged file — computing the analysis variables
and final event weights and writing histTuples:
Analysis caches: payload producers run first
If a variable of the active histTuple flavour is delivered by a payload producer (an entry
of payload_producers in global.yaml, e.g. a DNN score, with columns named
<producer>_<column>), AnalysisCacheTask runs that producer first, one branch per merged
anaTuple file, and HistTupleProducerTask reads its output alongside the anaTuple. All three
analyses define payload producers, and whether these run depends on the flavour's variables.
A global producer that a correction names as its normCacheProducer runs whatever the
variables; if it also needs aggregation — today HH→bb̄WW's BtagShape b-tag shape
normalisation — its output is summed per dataset by AnalysisCacheAggregationTask. These tasks are
pulled in automatically and can be time-consuming; each producer sets its own n_cpus and
max_runtime. See
Payload producers and the analysis cache
and the Task reference.
Stage 3 — Fill and merge histograms¶
HistFromNtupleProducerTask fills histograms of the requested variables from the histTuples —
one branch per (dataset, file-chunk), including the Up/Down variations when
compute_unc_histograms is on. If the booked histogram count (with Up/Down) exceeds
hist_from_ntuple_max_hists, or their memory (cells × thread slots, plus a fixed cost per
histogram) exceeds hist_from_ntuple_max_hist_memory_mb, the producer fills them in batches. HistMergerTask then merges the
pieces of each variable into one file with the histograms per process — one branch per
variable — ready for plotting and fitting.
# Fill histograms (restrict variables with --variables, set files/job with --n-files-per-job):
law run FLAF.Analysis.tasks.HistFromNtupleProducerTask --era $ERA --version $VER --workflow local
# Merge them:
law run FLAF.Analysis.tasks.HistMergerTask --era $ERA --version $VER --workflow local
Which variables are produced is controlled by the analysis config and can be narrowed with the
--variables a,b parameter (on HistFromNtupleProducerTask, HistMergerTask and HistPlotTask)
or the variables: list in user_custom.yaml.
Stage 4 — Make the plots¶
HistPlotTask produces the final plots — one branch per variable:
law run FLAF.Analysis.tasks.HistPlotTask --era $ERA --version $VER --workflow local
# one variable only:
law run FLAF.Analysis.tasks.HistPlotTask --era $ERA --version $VER --workflow local --branches 0
This is the task you most often launch directly: asking for the plots makes LAW produce every upstream product that is missing.
Stage 5 — Statistical inference¶
The two HH analyses turn the merged histograms into datacards and then run limits and diagnostics
with Combine, via the
StatInference and inference (dhi) submodules. H→μμ does not include this stage. There are two
routes; the exact configs and options are analysis-specific — see each analysis's Statistical
inference page (linked from Analyses) and the
cms-hh inference docs.
The StatInference law chain¶
StatInference provides a law chain, which HH→bb̄WW uses. An analysis enables it by listing
StatInference.law.tasks in its config/law.cfg and naming its datacard configuration under
StatInference: config: in global.yaml (HH→bb̄WW: config/Datacards/x_hh_bbww_DL_run3.yaml):
law run PlotResonantLimitsTask --version $VER --hists-version <VERSION_OF_THE_MERGED_HISTS> \
--era Run3_2022,Run3_2022EE,Run3_2023,Run3_2023BPix --workflow local
--era must hold every real era behind the configuration's cards, here the four of Run3_Early
(Eras).
It runs PreprocessShapesTask (only when the configuration declares a preprocess: step), then
CreateDatacardsTask, ResonantLimitsTask and PlotResonantLimitsTask; PlotPullsAndImpactsTask
draws pulls and impacts, and ResonantLimitsAndHistPlotTask (what the HH→bb̄WW CI runs) asks for
the limits together with HistPlotTask for every era of --era. The chain reads the
merged histograms of --hists-version (default: --version) as an external input, so produce
them first. It runs from the normal FLAF shell: the tasks run the steps that need CMSSW inside it
themselves.
Step by step¶
This is the route HH→bb̄ττ documents. The datacard script needs the CMSSW environment, so prefix
it with cmsEnv. The limits and pulls & impacts are the dhi tasks PlotResonantLimits and
PlotPullsAndImpacts (no Task suffix) from inference.dhi.tasks, which both HH analyses list
in config/law.cfg:
# 1) Create datacards from the produced shapes:
cmsEnv python3 StatInference/dc_make/create_datacards.py \
--input <PATH_TO_SHAPES> \
--output <PATH_TO_CARDS> \
--config <PATH_TO_CONFIG> # the analysis's datacard configuration
# 2) Run resonant limits:
law run PlotResonantLimits --version $VER --datacards '<PATH_TO_CARDS>/*.txt' --xsec fb --y-log
# 3) Pulls & impacts (per mass point — point at a single card):
law run PlotPullsAndImpacts --version $VER --datacards "<PATH_TO_CARDS>/<one_card>.txt" ...
The shortcut: just ask for the end¶
You rarely run the stages one by one. Because every task knows its dependencies, launching a late stage runs all missing upstream stages automatically:
This holds for stages 0–4; the Stage 5 chain does not produce the merged histograms it reads. Run the individual stages explicitly only when you want to stop at an intermediate product (e.g. produce anaTuples for someone else to use), or to inspect/debug one stage.
See progress and redo selectively¶
# Status of the whole tree (task depth 3, target-collection depth 1) — also prints output paths:
law run FLAF.Analysis.tasks.HistPlotTask --era $ERA --version $VER --workflow local --print-status 3,1
# Force one stage to be recomputed (remove its outputs, then run it) — keep --workflow, or the
# re-run goes to HTCondor:
law run FLAF.Analysis.tasks.HistMergerTask --era $ERA --version $VER --workflow local --remove-output 0,a,y
See Command arguments for the full option list, and Running on HTCondor
to take any of these commands to the batch system by swapping --workflow local for
--workflow htcondor.