Task reference¶
A concise reference for every FLAF task: what it does, what it branches over, and its task-specific
parameters. The common parameters (--version, --era, --workflow, --branches,
--test, …) apply to all of them and are documented in Command arguments.
Production tasks live in FLAF/AnaProd/tasks.py (invoke as FLAF.AnaProd.tasks.<Name>); analysis
tasks live in FLAF/Analysis/tasks.py (invoke as FLAF.Analysis.tasks.<Name>). For the order in
which they run, see the walkthrough and
data flow.
Eras. Every task below takes --era with one era. The production and analysis tasks of this
page also take several, separated by commas (--era Run3_2022,Run3_2022EE): every stage up to the
launched one then runs for all of them, as one workflow per stage over the eras that still need work
there, with branches numbered era id × 1 000 000 + the number in the era's own map. A branch is
always one era's: its inputs, outputs, paths and logs are those of a run over that era alone. The
StatInference tasks take several eras too, but their branches are the datacard cards, which may
read several eras each (see below). The other tasks (BundleTask, which stays per era, and the
analyses' Studies tasks) take one era and refuse several with <Task> takes one era. See
Running several eras. A meta-era of era_groups:
(--era Run3_22_23) is the sum of its eras for HistMergerTask and HistPlotTask, and stands
for the list of its eras for every other task; see Meta-eras.
Production tasks (AnaProd)¶
InputFileTask¶
Resolves the concrete list of NanoAOD files of every dataset of the era. Branches over
datasets and writes one JSON file list per dataset to the local data/ area. Runs locally (it
is a LocalWorkflow only and forces --workflow local) and is cheap. Every downstream task
depends on it, so it runs first.
Where the files come from is decided per dataset:
- a dataset with its own
fs_nanoAOD(custom samples, CI inputs) is listed there, in itsdirName(default: the dataset name); - otherwise the era's NanoAOD version applies —
nanoAODVersions.data/.mcin the era'sglobal.yaml,HLepRarewhen unset. ForHLepRarethe dataset's directory is listed on the era'sfs_nanoAOD(the HLepRare skims); for any other version (e.g.v15,22Sep23) the dataset'snanoAOD.<version>DAS name is resolved through Rucio, which also checks that each file is available on disk at some site.
Files that match the dataset's fileNamePattern for that NanoAOD version (default: every
.root file) are kept. A Rucio
file with no available replica stops the task, unless ignore_missing_nanoAOD_files is set for
the dataset's process group, in which case it is listed as inactive and skipped.
Its output also records what each file contains, under file_info:
size— always, taken from the directory listing that the task performs anyway (gfal-ls --longfor a storage path, the Rucio file list for a DAS dataset).n_events— for datasets discovered through Rucio, from a single DAS query per dataset (Rucio itself leaves the CMSeventsfield empty). One query covers thousands of files in about a second.
Both are advisory inputs to job-cost estimation. A missing event count is normal — for HLepRare skims there is no DAS record — and the cost model falls back to the file size.
AnaTupleCostProbeTask¶
Times the producer on a short prefix of one file per dataset (probe_events, default 5000) and
records the per-event cost. Branches over datasets. Runs before AnaTupleFileTask and takes a
few minutes; what it buys is job composition based on measurement rather than guesswork, because
per-event cost varies by more than an order of magnitude between datasets and depends on the
analysis selection.
Results live at <version>/AnaTupleCost/<nano-source>/<dataset>.json on fs_anaTuple, keyed by
version and nano source but not by era, so a dataset probed for one era is not probed again
for the others: they find the result and skip this stage. In one run over several eras whose
results do not exist yet, each era has its own probe branch for a dataset it shares (the eras' branch
maps are not merged), and these branches write the same file.
A probe that fails is retried once and, if it still fails, writes a result marked not ok and
prints a warning: calibration is an optimisation and never blocks production. That result counts
as the task's output, so to re-probe a dataset after fixing the cause, delete its json. Set
anaTuple_scheduling.probe_enabled: false to skip the stage entirely.
AnaTupleFileTask¶
Runs the analysis producer AnaProd/anaTupleProducer.py over one input file and fuses its
outputs (AnaProd/FuseAnaTuples.py) into one per-file anaTuple plus a JSON report, under
<version>/AnaTuples_split/<era>/<dataset>/ on fs_anaTuple. Branches over input files (one
branch per NanoAOD file) — the workflow you most often submit to a batch system. With --test N
only the first file of each dataset is used, and at most N events of it. Branches are grouped
into jobs by estimated cost rather than in fixed-size chunks; see
job composition.
The producer runs in the FLAF environment, or inside CMSSW when the analysis sets
use_cmssw_env_AnaTupleProduction: true in its global.yaml (only HH→bb̄ττ does). An input file
with no events, or one that turns out to be corrupted, gives an empty anaTuple with a report
marked invalid, which the merge plan skips.
Each job fuses the outputs of the central selection and of every systematic shift into one file (layout), in which the columns sharing the text before their first underscore form one collection. The step stops if a prefix mixes scalar and array columns, or if any column would come out with another type than it went in; the fix is to rename the columns in the analysis anaTuple definition.
The analysis global.yaml can list the columns that no shift changes:
anaTuple_shift_invariant_columns: # regular expressions, matched with re.search
- "^weight_(gen|xs)$"
- "^LHE_"
Those columns are stored in the central tree only; the central placeholder rows (events selected
only by a shift) get them from a shift that selected the event. The fuse step checks that every
variation selecting an event carries bit-identical values of every listed column and stops otherwise,
naming the column, the variation and an event. An array collection has to be listed as a whole. The
list must be the same for every file merged together, and AnaTupleMergeTask stops otherwise:
changing it means producing all anaTuples again under a new --version.
AnaTupleFileListBuilderTask / AnaTupleFileListTask¶
Build the merge plan. AnaTupleFileListBuilderTask has one branch per MC dataset and a single
branch data for all data datasets; it reads the reports of the dataset's per-file anaTuples,
drops files marked invalid and, for MC, the fewest events' worth of files needed so that no
luminosity block appears in two files, and groups the rest into outputs of about
nEventsPerFile events (per process group in global.yaml; default 1 000 000 for data,
100 000 otherwise). It can run on a batch system and writes the plan and the combined reports to
<version>/AnaTupleFileList/<era>/ and <version>/AnaTupleFileList_reports/<era>/ on
fs_anaTuple. AnaTupleFileListTask always runs locally and only copies each plan into the
local data/ area, where AnaTupleMergeTask reads it to build its branch map (on which the
analysis tasks' branch maps are built in turn). Both are pulled
in automatically; you rarely call them directly.
The same copy is made without running AnaTupleFileListTask when a checkout reuses an existing
production (--anaTuple-version/--ana-version with fs_anaTuple at its storage, see
Per-task version overrides): as soon as a
task built from the plans is scheduled or its status printed, the plans missing from data/ are
copied there, several at a time, if all of that version's plans exist on fs_anaTuple. So the
first --print-status in a new checkout writes into data/ once. If they do not all exist,
nothing is copied and the downstream tasks require the whole upstream chain. Batch jobs never
copy; they read the plans from the AnaTupleFileList bundle or the shared data area. A copy goes
through a temporary file beside its output, renamed onto it, so an interrupted copy never leaves a
partial plan that counts as complete.
AnaTupleMergeTask¶
Merges per-file anaTuples according to the merge plan. One branch per merge-plan item; each
writes one or more files of about nEventsPerFile events, anaTuple_<N>.root under
<version>/AnaTuples/<era>/<dataset>/ on fs_anaTuple, so a dataset usually has several branches
and several files. All data datasets are merged into the single sample data (files named
anaTuple_<eraLetter><eraVersion>_<N>.root), filtered to the plan item's runs and with duplicate events
(same run, luminosity block and event) removed.
For MC the merge also computes the normalisation weight weight_base (sign of the generator
weight × luminosity × cross-section × shape weights / denominator), because the denominators of
the whole dataset are only known here — or, for a process whose processor (such as a
stitcher) declares dependency_level: {AnaTupleMerge: process}, of
all the process's datasets;
in the 2024+ shared-MC eras it also writes weight_base_cmb. Columns matching the regular
expressions in anaTupleMerge_drop_columns are left out of the output.
- Parameter:
--delete-inputs-after-merge(bool, defaultfalse) — meant to remove the per-file inputs once the merge has succeeded, to save space.
--delete-inputs-after-merge does not work in the current code
The deletion loop iterates over the keys of each input's output dictionary instead of its
targets, so the task fails with an AttributeError right after the merged files have been
written, and no input is removed. The merged outputs are complete, so a re-run marks the
branch done.
Analysis tasks (Analysis)¶
HistTupleProducerTask¶
Runs Analysis/HistTupleProducer.py with the analysis's histTupleDef module over one merged
anaTuple file and writes one histTuple to <version>/HistTuples/<era>/<dataset>/ on
fs_HistTuple: the analysis variables, the final event weights and the binned columns the
histogram step reads. One branch per merged anaTuple file (data as the single sample data;
plan items without events are skipped); several branches are grouped per batch job
(--tasks-per-job, default 10).
Payload-producer columns (DNN scores and the like) are not computed here: they are read from the
AnalysisCacheTask outputs, which this task requires for every histTuple
variable named <producer>_<column>, and from the aggregated caches of
AnalysisCacheAggregationTask where one applies.
HistFromNtupleProducerTask¶
Fills histograms of the requested variables from the histTuples, with the Up/Down
variations when compute_unc_histograms is true (from the histTuple flavour, else global.yaml /
user_custom.yaml; default false). Branches over (dataset, file-chunk): each job reads its
chunk of input files and fills every active variable, one output file per variable under
<version>/Hists_split/<era>/<variable>/<dataset>/. Large datasets are parallelized by splitting
their files into chunks.
If the number of histograms booked in one RDataFrame pass — variables × selections ×
(Central + every Up/Down) — exceeds hist_from_ntuple_max_hists (default 30000), the
producer repeats the event loop in batches instead of holding every histogram at once.
Every pass costs a full graph build and event loop, so the count is kept high: memory is bounded
by the memory budget below. Set the threshold in global.yaml / user_custom.yaml, or pass
--max-hists to the producer (0 removes the count cap; a single pass needs both caps at 0).
LAW branches stay file-chunks; batching is inside the job.
A count does not bound memory by itself: RDataFrame keeps one copy of every booked histogram
per thread slot (--nMT; on HTCondor/CRAB the task runs one thread per requested core,
nMT = n_cpus, and 8 locally), and a 2D histogram can have a hundred times the cells of a 1D
one. A batch therefore also stays within hist_from_ntuple_max_hist_memory_mb (default
1500), counted per histogram as a fixed ~6 kB (the booked action) plus cells × 16 B for each
thread slot. Variables are packed largest first, so large 2D/3D variables share passes with the
1D ones; a variable that alone exceeds the budget is split over its selections and Up/Down.
Pass --max-hist-memory-mb to the producer to override it (0 disables it). Each batch's line
in the log gives its histograms, cells and estimated memory.
- Parameters:
--variables(string; restrict which variables),--n-files-per-job(int, default20; input files processed per branch).
HistMergerTask¶
Merges the per-chunk histograms into per-process histograms ready for plotting and fitting,
one file per variable at <version>/Hists_merged/<era>/<variable>/<variable>.root. Each branch
(one per variable) merges all uncertainty sources in a single pass: every input file is read
once and all histograms are written directly to the final output file. The uncertainty sources
are those of weights.yaml when compute_unc_histograms is true (minus the era's
uncs_to_exclude), only Central otherwise.
- Parameter:
--variables(string; restrict which variables). - Signal-specific variables: a histogram config entry with
signal_params: [ { mass: <M>, spin: <S> }, ... ]is produced byHistFromNtupleProducerTaskand merged here only for datasets matching at least one entry. A dataset matches an entry if, for every key of the entry, it lacks that key or has that value, so backgrounds always match. Any dataset key can be listed; a single dict is a one-entry list. - Meta-era (
--era Run3_22_23, a group ofera_groups:): one branch per variable that every era of the group merges. It requiresHistMergerTaskover the eras and sums, bin by bin, their merged files of the variable (read throughMergedHistsTask) into<version>/Hists_merged/<meta-era>/<variable>/<variable>.root. An era without a variation of a process (a source decorrelated between the eras) contributes its central histogram to it. See Meta-eras.
MergedHistsTask¶
An external task: the merged histograms of one variable of one era, at HistMergerTask's
output path (<version>/Hists_merged/<era>/<variable>/<variable>.root on that era's
fs_HistTuple). It produces nothing; it is what a branch of HistMergerTask over a meta-era
requires of each era, so that the branch checks the eras' merged files without building their
HistMergerTask branch maps (HistMergerTask over the eras is the requirement of the workflow,
which makes them).
- Parameter:
--variable(the variable whose merged file it is; required).
AnalysisCacheTask¶
Runs one payload producer (Analysis/AnalysisCacheProducer.py) and stores its per-event
output — typically a DNN score — for later stages. Payload producers are listed under
payload_producers in the analysis's global.yaml, and all three analyses define some
(HH→bb̄ττ: ggF_DNN; H→μμ: DNN, VBFNet, PostVBFNetDNN; HH→bb̄WW: SingleLepHME, DoubleLepHME,
DNN, TwoStageDNN, DeepHME and BtagShape). Same branches as HistTupleProducerTask (one per merged anaTuple file); output at
<version>/AnalysisCache/<producer>/<era>/<dataset>/ on fs_anaCacheTuple.
Pulled in automatically by HistTupleProducerTask for every histTuple variable named
<producer>_<column> (a column listed under the producer's columns), so whether it runs depends
on the variables of the active histTuple flavour; also for an is_global producer that a
correction names as its normCacheProducer (through AnalysisCacheAggregationTask when the
producer also sets needs_aggregation). A producer's dependencies run first, and its
n_cpus, max_runtime (default 2 h), crab_memory (CRAB memory in MB, default the most CRAB grants for the cores; it overrides --AnalysisCacheTask-crab-memory), save_as (file type, default root) and cmssw_env
(run inside CMSSW) come from its payload_producers entry. A producer that runs an ONNX model
creates its sessions with FLAF.Common.OnnxSession, so that its outputs do not depend on the core
count of the node (NN outputs and the CPU of the node).
- Parameter:
--producer-to-run(which payload producer to run; required). - Caveat: on a cold cache this can be time-consuming (≈ 1 h per branch). Reuse it across runs via a per-task version override.
AnalysisCacheAggregationTask¶
Aggregates all AnalysisCacheTask outputs of one producer for one dataset (data for all data)
into a single aggregatedCache file, which every HistTupleProducerTask branch of that dataset
receives. One branch per dataset of the producer's target_groups. Used for producers marked
is_global and needs_aggregation that a correction names as its normCacheProducer — today
HH→bb̄WW's BtagShape, the b-tag shape normalisation.
- Parameter:
--producer-to-aggregate(required).
PreHistTupleProductionTask¶
Runs the entire AnaTuple + AnalysisCache production for a version in one command, without
producing histTuples. It shares HistTupleProducerTask's dependency graph but writes only a small
per-branch completion marker, so a single
forces every AnaTupleMergeTask and AnalysisCacheTask (plus their aggregation) to run — handy
to pre-compute and then freeze/share those caches (as AnaTupleMergeTask outputs already can be),
instead of submitting each AnalysisCacheTask --producer-to-run individually.
HistPlotTask¶
Produces the final plots via the PlotKit submodule
(matplotlib + mplhep by default; optional ROOT + cmsstyle). Branches over variables (one branch
per variable, skipping variables configured with plot_task: false).
- Parameter:
--variables(string; restrict which variables). plot_wantSignals(global.yamlor--customisations, defaulttrue):falsedraws no signal on any plot. Signals are never drawn in theplot_control_regions(below).- Meta-era (
--era Run3_22_23): plots the sumHistMergerTaskmakes over the meta-era, into<version>/Plots/<meta-era>/…, with the processes and uncertainty sources of every era of the group and the labels ofconfig/plot/<meta-era>.yaml. See Meta-eras. - Blinding (per variable, in
config/plot/histograms.yaml):blind: trueshows no data on the plots of the variable outside theplot_control_regions(below).blind_rangeshides the data points inside windows of its x axis, in the plot and in the ratio panel, in every region, control regions included: one window[lo, hi]or a list of them,[[lo, hi], ...], with numberslo < hi. A bin is hidden when it overlaps a window by more than a point — partly overlapping bins included, while a bin that ends atloor starts athiis shown — and so is a bin into which the rebinning (x_rebin) or the overflow merge moves events of a stored bin that overlaps a window.blind_rangesis for 1D variables only. Any other key starting withblind(in any letter case) on a variable, and any such key indata_histofcms_stacked.yamlor<era>.yaml, is an error, so that a key no code reads cannot pass for blinding.
Plot styling comes from the analysis config/plot/*.yaml files (cms_stacked.yaml,
histograms.yaml, <era>.yaml), read as the legacy renderer read them. The CMS label line draws
scope_text (e.g. "CMS Preliminary"), as "Simulation \<scope>" when a plot shows no data, and
is drawn smaller when it would run into the luminosity; the optional style settings are listed in
the PlotKit README. Signal overlays are scaled by
signal_plot_scale in global.yaml: a fixed factor (e.g. 100) or bkg to normalise each
signal's integral to the summed background (shape comparison; the legend then reads
… (norm. to bkg), or a single sgn_hist.norm_legend_note entry; see the PlotKit README for this
and the other optional style settings). PlotKit can also render outside FLAF; see its README for
the standalone python -m PlotKit.cli entry point.
Two optional global.yaml keys shape what is drawn. They, and signal_plot_scale, are read from
the merged configuration of each era, as every task reads its settings: config/global.yaml, the
era's config/<era>/global.yaml, the --user-custom file and the task's --customisations all
apply. For a meta-era the eras
must agree on them.
plot_control_regions(list of custom regions, e.g.[ TT_CR, DY_CR ]): no signal is overlaid in these regions, and their data is shown even for variables withblind: trueinhistograms.yaml(a blinded variable hides the data in the other regions only). Data is shown only where it is plotted at all:plot_wantData_<var>: falseinglobal.yamlor the task's--customisations(defaulttrue) removes it from every plot of<var>. The value must be a YAML list: written without brackets it is one string, which is an error. A name that is not one of the analysis' regions (the listcustom_regionsnames, e.g.QCDRegions) is warned about, and that region stays blinded.plot_process_groups(list of{name, processes, color}): sums processes into one legend entry each. Background groups are stacked in list order from the bottom; signal groups become one curve each. A process may carry parameters: a mass,GluGluToRadion_bbWW_2L_${MASS}, or couplings,GluGluToHHto2B2Tau_${kl}_${kt}_${c2}. The group then makes one entry per set of values found, with each${…}innamereplaced by its value. Entries come in increasing order, the parameters compared in the order the pattern names them: numbers by value, written plainly or in the CMS form (1p74= 1.74,m0p758= −0.758), then any other value as text. A value is made of letters, digits,_,.and-.colordefaults to the colour of the group's first process present in that plot (its channel, region and era); acolorgiven for a group with parameters applies to every one of its entries. A group's processes are all backgrounds or all signals; processes no group takes are plotted as before, after the groups, and a process an earlier group took is not taken again. Without the key nothing is grouped. A group must have anameand a list ofprocesses, every process naming the same parameters as thenamedoes, none listed twice, and no keys butname,processesandcolor; a histogram matched by two processes of one group, or a group mixing backgrounds, signals and data, is an error.
A process without events in a channel, region and category has an empty histogram there: as a
member of a group it leaves the group's name as it is (t#bar{t} + t#bar{t}V stays so when
ttV is empty), and an entry, grouped or not, whose every bin is empty is not drawn and gets no
legend entry. Data is drawn whenever it has a histogram, empty or not. A selected process without
a histogram in that directory at all (no events there, e.g. data in a channel its primary
dataset does not feed) counts as empty, with a warning naming the histogram and the directory.
An object that is not a histogram is an error.
plot_control_regions: [ TT_CR, DY_CR ]
plot_process_groups:
- { name: "Single top", processes: [ ST ] }
- { name: "t#bar{t} + t#bar{t}V", processes: [ TT, ttV ] }
- name: "X #rightarrow HH M_{X} = ${MASS} GeV"
processes: [ "GluGluToRadion_bbWW_2L_${MASS}", "GluGluToRadion_bbWW_1L_${MASS}" ]
A per-process signal_plot_scale dict is keyed by the plotted entry, so with signal groups it must
name the group entry: the members present in the plot, joined with + in the order of the
group's processes (e.g. GluGluToRadion_bbWW_2L_400+GluGluToRadion_bbWW_1L_400), or the single
member's name when only one is present. An entry the dict does not name is drawn unscaled
(factor 1), without a warning.
Statistical-inference tasks¶
Datacards and limits are not FLAF tasks: they come from the StatInference and
inference/dhi submodules, which only the two HH analyses include, and need the CMSSW/Combine
environment.
StatInferencelaw chain (StatInference.law.tasks, used by HH→bb̄WW and its CI):PreprocessShapesTask(only when the datacard configuration has apreprocessblock) →CreateDatacardsTask→ResonantLimitsTask→PlotResonantLimitsTask/PlotPullsAndImpactsTask, plusResonantLimitsAndHistPlotTask(limits together with theHistPlotTaskplots of their input histograms). The chain reads theHists_mergedfiles of--hists-version(default:--version) as external inputs and never schedules their production — onlyResonantLimitsAndHistPlotTask, throughHistPlotTask, does. Eras, masses and variables come from the datacard configuration named byStatInference.configinglobal.yaml.CreateDatacardsTaskandPreprocessShapesTaskare one workflow each, with one branch per card of the configuration'seras:, and--eramust hold every real era behind those cards (Eras).dhitasks frominference.dhi.tasks(e.g.PlotResonantLimits,PlotPullsAndImpacts, without theTasksuffix), run directly on existing datacards. HH→bb̄ττ documents this manual route, after creating its datacards withStatInference/dc_make/create_datacards.py.
See each HH analysis's Statistical inference page (via Analyses) and the walkthrough.
Discover parameters from the command line
law run <Task> --help lists every parameter a task accepts, including the ones inherited from
the base classes.