EventNtuple is a Mu2e (Fermilab) art/ROOT analysis ntuple package. It fills a flat ROOT ntuple from Mu2e reconstruction output (KalSeeds, CaloCluster, CRV, etc.) and provides a Python/ROOT analysis library (RooUtil) and distributed job runner (roodask).
Framework: art (Fermilab) + cetmodules CMake layer + fhiclcpp configuration.
Languages: C++17 (modules, helpers, structs), FHiCL (job config), Python 3.12+ (helpers/tools).
Environment must be set up via Muse before building:
mu2einit
muse setup # or: muse setup AnalysisMDC2020 / AnalysisMDC2025
muse build -j4 --mu2eCompactPrintAlternative Spack-based build:
spack develop event-ntuple@main
spack concretize -f && spack installRun the ntuple maker:
mu2e -c EventNtuple/fcl/from_mcs-mockdata.fcl -S your-art-filelist.txtAll validation scripts are in validation/. There is no unit test framework; tests
run the full art job and compare histograms.
# Quick smoke test: runs from_mcs-mockdata.fcl, creates histograms
bash validation/quick_test.sh
# Test all FCL files for a campaign
bash validation/test_fcls.sh MDC2020
bash validation/test_fcls.sh MDC2025
bash validation/test_fcls.sh Run1B
# Test RooUtil library
bash validation/test_rooutil.sh
# Test all RooUtil example macros
bash validation/test_rooutil_examples.sh
# Test Python ntuplehelper
python validation/ntuplehelper-test.py
# Test roodask distributed runner
bash validation/roodask_test.shTo run a single FCL test manually:
mu2e -c EventNtuple/fcl/<specific>.fcl -S filelist.txt -n 100Branch configuration is defined in fcl/prolog.fcl. Each track type is a FHiCL table:
DeM : {
input : "MergeKKDeM" # input KalSeedPtr collection tag
branch : "dem" # output ROOT branch name
options : {
fillMC : true # toggle MC truth filling for this branch
fillHits : true # toggle hit-level info
genealogyDepth : -1 # MC genealogy depth (-1 = all)
matchDepth : -1 # MC match depth (-1 = all)
}
trkQualTags : ["TrkQualDeM"]
trkPIDTags : ["TrkPIDDeM"]
}
Global MC switch lives in EventNtupleMaker config: FillMCInfo : true/false.
Per-branch MC is controlled by options.fillMC. Both must be true for MC to fill.
Current issue: Branch on/off and MC on/off are scattered across individual track
tables in prolog.fcl and duplicated in every campaign FCL. When adding a new
track type or toggling MC for a subset of branches, edits are required in many places.
Preferred direction for improvement:
- Define a single
BranchOptionsprolog table with canonical defaults. - Use
@table::inheritance for per-branch overrides rather than full re-specification. - Consider a top-level
MCOptionstable that can be overlaid to flip allfillMCflags. - Campaign FCLs should
#include prolog.fcland only override what differs.
- Headers:
PascalCase.hhininc/ - Sources:
PascalCase.ccinsrc/ - Module plugins:
PascalCase_module.cc
All production code lives in namespace mu2e {}.
RooUtil library uses namespace rooutil {}.
- Ntuple info structs:
PascalCaseending inInfoorInfoMC(e.g.TrkInfo,CaloClusterInfoMC) - Each struct lives in its own
inc/<StructName>.hhheader
- Module private members: leading underscore (
_conf,_fillmc,_ntuple) - Struct data members:
snake_case(no underscore suffix); initialized to sentinel values- Integers:
int status = -1; - Floats:
float chisq = -1;orfloat mom = std::numeric_limits<float>::min(); - Booleans:
bool flag = false;
- Integers:
namespace mu2e {
struct FooInfo {
// required comment on every leaf for ntuplehelper autodoc
int status = -1; // fit status
float mom = -1; // momentum at tracker entrance (MeV/c)
void reset() { *this = FooInfo(); }
};
}Every leaf must have an inline // comment — this is parsed by ntuplehelper --list-all-branches.
- Mu2e Offline headers (
Offline/…) - art framework headers (
art/…,canvas/…,fhiclcpp/…) - ROOT headers
- Local EventNtuple headers (
EventNtuple/inc/…) - C++ standard library (
<vector>,<string>, etc.)
- Throw via
cet::exception:throw cet::exception("EventNtuple") << "message"; - Do not use raw
std::exceptionorexit()in art modules - Warnings via
mf::LogWarning("EventNtuple") << "message";
struct Config {
fhicl::Atom<bool> fillMC { Name("FillMCInfo"), Comment("Fill MC info"), true };
fhicl::Table<SubConf> sub { Name("SubConfig") };
fhicl::Sequence<art::InputTag> tags { Name("InputTags") };
};- Python 3.12+; no type annotations required but welcome
snake_casefor all functions, methods, and variables- Class names:
PascalCase - No f-strings required; format strings acceptable
roodask/roodask.pyis intentionally a single-file script — do not split it
- Use
BEGIN_PROLOG/END_PROLOGfor all reusable tables - Use
@local::for prolog references;@table::for struct inheritance/merging - Do not duplicate full table bodies — inherit with
@table::Basethen override fields - One
#includeper dependency; include Offline prologs before EventNtuple prologs - Comment all numeric constants with units:
MaxDE : 500.0 # MeV - Boolean flags:
true/false(lowercase, no quotes)
See doc/developers.md for the full walkthrough. Summary:
- Create
inc/FooInfo.hhwith commented struct (see struct pattern above) - Add fill logic to
src/InfoStructHelper.cc(orInfoMCStructHelper.ccfor MC) - Register branch in
src/EventNtupleMaker_module.cc - Add prolog table to
fcl/prolog.fclusing@table::inheritance - Regenerate
doc/branches.md:ntuplehelper --list-all-branches --export-to-md - Run
bash validation/quick_test.shto confirm no runtime errors
| File | Role |
|---|---|
src/EventNtupleMaker_module.cc |
Main art::EDAnalyzer; branch registration and filling |
src/InfoStructHelper.cc |
Reco → struct fill logic |
src/InfoMCStructHelper.cc |
MC truth → struct fill logic |
inc/*Info*.hh |
One ntuple branch struct per file |
fcl/prolog.fcl |
Master FHiCL prolog (track types, branch configs, MC options) |
fcl/from_mcs-mockdata.fcl |
Default/reference job FCL |
helper/ntuplehelper.py |
Python ntuple inspection tool |
rooutil/inc/RooUtil.hh |
ROOT analysis library |
rooutil/roodask/roodask.py |
Dask distributed job runner (single-file) |
validation/quick_test.sh |
Primary smoke test |
doc/developers.md |
Developer guide for adding branches |
See rooutil/roodask/.github/copilot-instructions.md for detailed architecture.
Key points: single-file CLI, auto-generates C++ main() wrapper, shared NFS filesystem,
full environment propagated to Dask workers. Do not split roodask.py into multiple files.