Imported from Mu2e/EventNtuple (
AGENTS.md). Install upstream withnpx skills add Mu2e/EventNtuple. Copyright stays with the author.
AGENTS.md — EventNtuple Coding Agent Instructions
Project Overview
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).
Build Commands
Environment must be set up via Muse before building:
mu2einit
muse setup # or: muse setup AnalysisMDC2020 / AnalysisMDC2025
muse build -j4 --mu2eCompactPrint
Alternative Spack-based build:
spack develop event-ntuple@main
spack concretize -f && spack install
Run the ntuple maker:
mu2e -c EventNtuple/fcl/from_mcs-mockdata.fcl -S your-art-filelist.txt
Test / Validation Commands
All 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.sh
To run a single FCL test manually:
mu2e -c EventNtuple/fcl/<specific>.fcl -S filelist.txt -n 100
FHiCL Configuration Structure
Branch / MC Data Configuration (Key Area for Improvement)
Branch 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)
}
trkQualLeaves : [ { leafname : "" inputTag : "TrkQualDeM:ANN" modelVersion : "ANN1_v2" } ]
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.
C++ Code Style
File Naming
- Headers:
PascalCase.hhininc/ - Sources:
PascalCase.ccinsrc/ - Module plugins:
PascalCase_module.cc
Namespaces
All production code lives in namespace mu2e {}.
RooUtil library uses namespace rooutil {}.
Struct / Class Naming
- Ntuple info structs:
PascalCaseending inInfoorInfoMC(e.g.TrkInfo,CaloClusterInfoMC) - Each struct lives in its own
inc/<StructName>.hhheader
Member Naming
- 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:
Info Struct Pattern (required for ntuplehelper compatibility)
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.
Includes (C++ ordering)
- Mu2e Offline headers (
Offline/…) - art framework headers (
art/…,canvas/…,fhiclcpp/…) - ROOT headers
- Local EventNtuple headers (
EventNtuple/inc/…) - C++ standard library (
<vector>,<string>, etc.)
Error Handling
- Throw via
cet::exception:throw cet::exception("EventNtuple") << "message"; - Do not use raw
std::exceptionorexit()in art modules - Warnings via
mf::LogWarning("EventNtuple") << "message";
FHiCL Config Structs (in modules)
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 Code Style
- 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
FHiCL Style Guidelines
- 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)
Adding a New Branch (Developer Checklist)
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
Key Files Reference
| 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 |
roodask Sub-tool (rooutil/roodask/)
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.