Skip to content

Architecture

FLAF is a shared framework used by several analyses. This page explains how the pieces fit together — which repositories exist, how they are nested as git submodules, and what is "common" versus "analysis-specific".

Repositories

Repository Role Host
FLAF The framework: task definitions, config system, environment, CI. GitHub
Corrections Object corrections & systematics (pileup, b-tag, triggers, …). GitHub
StatInference Datacard creation and limit/fit tooling. GitHub
inference The HH combine-based inference tooling (dhi). CERN GitLab
HH_bbtautau, HH_bbWW, H_mumu The analysis repositories. GitHub

The first four are shared — every analysis uses the same FLAF and Corrections; the two HH analyses also use StatInference and inference. They are pulled into each analysis as git submodules pinned to a specific commit, so an analysis always builds against a known version of the framework.

Submodule hierarchy

You clone an analysis repository; it brings the shared framework and the analysis-specific tools with it. For example, HH→bb̄ττ looks like this:

HH_bbtautau/                  ← the analysis repository (you clone this)
├── FLAF/                     ← shared framework (submodule)
│   ├── PlotKit/              ← plotting toolkit: matplotlib/mplhep (+ROOT/cmsstyle); submodule
│   └── RunKit/               ← workflow utilities (vendored directory, not a submodule)
├── Corrections/              ← shared corrections (submodule)
├── StatInference/            ← shared stat tooling (submodule; HH analyses only)
├── inference/                ← HH combine tooling (submodule; HH analyses only)
├── ClassicSVfit/ SVfitTF/    ← analysis-specific physics tools (submodules)
├── HHKinFit2/ HHbtag/        ← analysis-specific physics tools (submodules)
├── SyncTool/                 ← analysis-specific (submodule)
├── Analysis/ AnaProd/        ← analysis-specific task/producer code
├── config/                   ← analysis-specific configuration (signals, channels, …)
├── env.sh                    ← the entry point you `source`
└── data/                     ← local working area (proxy, small outputs)

Each analysis carries a different set of physics submodules:

  • HH_bbtautau — SVfit (ClassicSVfit, SVfitTF), HHKinFit2, HHbtag, SyncTool.
  • HH_bbWW — DeepHME, SyncTool.
  • H_mumu — the simplest: just FLAF and Corrections (no StatInference/inference).

See Analyses for what each one adds on top of the framework.

Common vs analysis-specific

Understanding this split tells you where to look for any given thing.

flowchart TD
    subgraph Shared ["Shared framework (pinned submodules)"]
        F["FLAF<br/>tasks, config loader, env,<br/>common datasets, CI"]
        C["Corrections<br/>corrections & systematics"]
        S["StatInference + inference<br/>(HH analyses only)"]
    end
    subgraph Ana ["Analysis repository"]
        AC["config/<br/>signals, channels,<br/>observables"]
        AA["Analysis/ AnaProd/<br/>producers, definitions"]
        SUB["physics submodules<br/>SVfit, DeepHME, ..."]
    end
    F --> Ana
    C --> Ana
    S --> Ana
  • In FLAF: the machinery — the task classes (AnaProd/tasks.py, Analysis/tasks.py), the configuration loader (Common/Setup.py), the environment (env.sh), cross-sections and the SM-background/data dataset lists common to all analyses, and the shared CI workflows.
  • In the analysis repository: the physics — which signals to run, the channels and categories, the analysis-specific observables and producers, and the analysis-specific configuration that overrides or extends the framework defaults.

The configuration system is what merges these two layers together at run time, and the data flow is what the shared tasks actually do with them.

Developing the shared submodules

Because FLAF/Corrections are pinned submodules, editing them inside an analysis checkout requires care so your edits are actually picked up. The environment supports pointing FLAF_PATH/CORRECTIONS_PATH at an edited copy — see The environment and Contributing.

Corrections in the analysis hooks

There is no global Corrections instance. Each payload builds its own, one per dataset, era and stage, and passes it to the analysis hooks as the required keyword corrections=:

  • anaTupleProducer.py constructs Corrections(...) itself (stage AnaTuple, with the dataset's processors and trigger class) and passes it to addAllVariables(..., *, corrections) in anaTupleDef;
  • HistTupleProducer.py and AnalysisCacheProducer.py build it with Utilities.CreateCorrections(setup, dataset_name, stage, aggregated_caches=None) and pass it to GetDfw(df, setup, dataset_name, *, corrections[, stage]) and DefineWeightForHistograms(*, ..., corrections) in histTupleDef; an analysis-cache producer with its own create_dfw (BtagShapeProducer) receives the same instance;
  • MergeAnaTuples.py constructs Corrections(...) itself as well (stage AnaTupleMerge), a new one for each merge of a simulated dataset, for the normalisation weights; no analysis hook is called there.

A hook that needs a correction reads it from that argument, never from a module-level object. The instance must stay referenced for as long as the dataframe lives: the C++ providers it creates are referenced by address, so a Corrections object that is dropped while its dataframe is still being evaluated crashes. HistTupleProducer.py and the analysis-cache producers keep it on the DataFrameWrapper (dfw.corrections); anaTupleProducer.py and MergeAnaTuples.py hold it in a local variable of the function that runs the event loop. Inside Corrections the POG campaign is called campaign (campaign_names) and the FLAF era era.