Skip to content

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:

  1. a dataset with its own fs_nanoAOD (custom samples, CI inputs) is listed there, in its dirName (default: the dataset name);
  2. otherwise the era's NanoAOD version applies — nanoAODVersions.data / .mc in the era's global.yaml, HLepRare when unset. For HLepRare the dataset's directory is listed on the era's fs_nanoAOD (the HLepRare skims); for any other version (e.g. v15, 22Sep23) the dataset's nanoAOD.<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 --long for 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 CMS events field 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, default false) — 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, default 20; 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 by HistFromNtupleProducerTask and 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 of era_groups:): one branch per variable that every era of the group merges. It requires HistMergerTask over the eras and sums, bin by bin, their merged files of the variable (read through MergedHistsTask) 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

law run FLAF.Analysis.tasks.PreHistTupleProductionTask --version <v> --era <era> --workflow local

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.yaml or --customisations, default true): false draws no signal on any plot. Signals are never drawn in the plot_control_regions (below).
  • Meta-era (--era Run3_22_23): plots the sum HistMergerTask makes over the meta-era, into <version>/Plots/<meta-era>/…, with the processes and uncertainty sources of every era of the group and the labels of config/plot/<meta-era>.yaml. See Meta-eras.
  • Blinding (per variable, in config/plot/histograms.yaml): blind: true shows no data on the plots of the variable outside the plot_control_regions (below). blind_ranges hides 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 numbers lo < hi. A bin is hidden when it overlaps a window by more than a point — partly overlapping bins included, while a bin that ends at lo or starts at hi is 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_ranges is for 1D variables only. Any other key starting with blind (in any letter case) on a variable, and any such key in data_hist of cms_stacked.yaml or <era>.yaml, is an error, so that a key no code reads cannot pass for blinding.
m_mumu(_.*)?:
  x_bins: "1500|0:300"
  blind_ranges: [ 115, 135 ]          # or [ [ 115, 120 ], [ 130, 135 ] ]

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 with blind: true in histograms.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>: false in global.yaml or the task's --customisations (default true) 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 list custom_regions names, 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 ${…} in name replaced 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 -. color defaults to the colour of the group's first process present in that plot (its channel, region and era); a color given 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 a name and a list of processes, every process naming the same parameters as the name does, none listed twice, and no keys but name, processes and color; 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.

  • StatInference law chain (StatInference.law.tasks, used by HH→bb̄WW and its CI): PreprocessShapesTask (only when the datacard configuration has a preprocess block) → CreateDatacardsTask → ResonantLimitsTask → PlotResonantLimitsTask / PlotPullsAndImpactsTask, plus ResonantLimitsAndHistPlotTask (limits together with the HistPlotTask plots of their input histograms). The chain reads the Hists_merged files of --hists-version (default: --version) as external inputs and never schedules their production — only ResonantLimitsAndHistPlotTask, through HistPlotTask, does. Eras, masses and variables come from the datacard configuration named by StatInference.config in global.yaml. CreateDatacardsTask and PreprocessShapesTask are one workflow each, with one branch per card of the configuration's eras:, and --era must hold every real era behind those cards (Eras).
  • dhi tasks from inference.dhi.tasks (e.g. PlotResonantLimits, PlotPullsAndImpacts, without the Task suffix), run directly on existing datacards. HH→bb̄ττ documents this manual route, after creating its datacards with StatInference/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.