diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..2ca9c37 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,225 @@ +# 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: + +```bash +mu2einit +muse setup # or: muse setup AnalysisMDC2020 / AnalysisMDC2025 +muse build -j4 --mu2eCompactPrint +``` + +Alternative Spack-based build: + +```bash +spack develop event-ntuple@main +spack concretize -f && spack install +``` + +Run the ntuple maker: + +```bash +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. + +```bash +# 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: + +```bash +mu2e -c EventNtuple/fcl/.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) + } + 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 `BranchOptions` prolog table with canonical defaults. +- Use `@table::` inheritance for per-branch overrides rather than full re-specification. +- Consider a top-level `MCOptions` table that can be overlaid to flip all `fillMC` flags. +- Campaign FCLs should `#include prolog.fcl` and only override what differs. + +--- + +## C++ Code Style + +### File Naming +- Headers: `PascalCase.hh` in `inc/` +- Sources: `PascalCase.cc` in `src/` +- Module plugins: `PascalCase_module.cc` + +### Namespaces +All production code lives in `namespace mu2e {}`. +RooUtil library uses `namespace rooutil {}`. + +### Struct / Class Naming +- Ntuple info structs: `PascalCase` ending in `Info` or `InfoMC` (e.g. `TrkInfo`, `CaloClusterInfoMC`) +- Each struct lives in its own `inc/.hh` header + +### 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;` or `float mom = std::numeric_limits::min();` + - Booleans: `bool flag = false;` + +### Info Struct Pattern (required for `ntuplehelper` compatibility) +```cpp +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) +1. Mu2e Offline headers (`Offline/…`) +2. art framework headers (`art/…`, `canvas/…`, `fhiclcpp/…`) +3. ROOT headers +4. Local EventNtuple headers (`EventNtuple/inc/…`) +5. C++ standard library (``, ``, etc.) + +### Error Handling +- Throw via `cet::exception`: `throw cet::exception("EventNtuple") << "message";` +- Do not use raw `std::exception` or `exit()` in art modules +- Warnings via `mf::LogWarning("EventNtuple") << "message";` + +### FHiCL Config Structs (in modules) +```cpp +struct Config { + fhicl::Atom fillMC { Name("FillMCInfo"), Comment("Fill MC info"), true }; + fhicl::Table sub { Name("SubConfig") }; + fhicl::Sequence tags { Name("InputTags") }; +}; +``` + +--- + +## Python Code Style + +- Python 3.12+; no type annotations required but welcome +- `snake_case` for all functions, methods, and variables +- Class names: `PascalCase` +- No f-strings required; format strings acceptable +- `roodask/roodask.py` is intentionally a **single-file script** — do not split it + +--- + +## FHiCL Style Guidelines + +- Use `BEGIN_PROLOG` / `END_PROLOG` for all reusable tables +- Use `@local::` for prolog references; `@table::` for struct inheritance/merging +- Do not duplicate full table bodies — inherit with `@table::Base` then override fields +- One `#include` per 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: + +1. Create `inc/FooInfo.hh` with commented struct (see struct pattern above) +2. Add fill logic to `src/InfoStructHelper.cc` (or `InfoMCStructHelper.cc` for MC) +3. Register branch in `src/EventNtupleMaker_module.cc` +4. Add prolog table to `fcl/prolog.fcl` using `@table::` inheritance +5. Regenerate `doc/branches.md`: `ntuplehelper --list-all-branches --export-to-md` +6. Run `bash validation/quick_test.sh` to 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. diff --git a/fcl/CompareDeTracks.fcl b/fcl/CompareDeTracks.fcl index 190695c..7190369 100644 --- a/fcl/CompareDeTracks.fcl +++ b/fcl/CompareDeTracks.fcl @@ -55,5 +55,5 @@ physics.end_paths : [ EndPath ] physics.producers.TrkQualDe.KalSeedPtrCollection : "MergeKKDeCalib" services.TimeTracker.printSummary: true services.TFileService.fileName: "nts.owner.EventNtupleDeCalib.version.sequence.root" -physics.analyzers.EventNtuple.KalSeedMCAssns : @nil # module which created the MC truth matching (KalSeedMC). including both primary and 2ndary tracks. +physics.analyzers.EventNtuple.trk.mc.kalSeedMCAssns : @nil # module which created the MC truth matching (KalSeedMC). including both primary and 2ndary tracks. diff --git a/fcl/TrkAnaLineFromDigis.fcl b/fcl/TrkAnaLineFromDigis.fcl index 8e0f67c..cabea8d 100644 --- a/fcl/TrkAnaLineFromDigis.fcl +++ b/fcl/TrkAnaLineFromDigis.fcl @@ -38,17 +38,7 @@ physics : @table::EventNtupleMaker FitType : KinematicLine diagLevel : 2 - FillMCInfo : true - FillTrkPIDInfo : false - FillHitInfo : true FillTriggerInfo : false - branches : [ - { input: "KKLine" - branch : "kl" - options : { fillMC : true genealogyDepth : 5 } - } - ] - ExtraMCStepCollectionTags : [ "compressDigiMCs:protonabsorber", "compressDigiMCs:stoppingtarget" ] } @table::TrkAnaReco.analyzers @@ -81,6 +71,13 @@ physics.producers.KKLine.ModuleSettings.SaveAllFits : true physics.producers.KKLine.ExtensionSettings.BFieldCorrection : false physics.end_paths : [ EndPath ] +physics.analyzers.TrkAnaLine.trk.fits : [ + { input: "KKLine" + branchname : "kl" + options : { genealogyDepth : 5 } + } +] +physics.analyzers.TrkAnaLine.mcsteps.extraMCStepTags : [ "compressDigiMCs:protonabsorber", "compressDigiMCs:stoppingtarget" ] services.TimeTracker.printSummary: true services.TFileService.fileName: "nts.owner.TALineDigis.version.sequence.root" #include "Offline/CRVResponse/fcl/epilog_extracted.fcl" diff --git a/fcl/from_dig-OnSpill.fcl b/fcl/from_dig-OnSpill.fcl index d0a8ac2..f8b54a1 100644 --- a/fcl/from_dig-OnSpill.fcl +++ b/fcl/from_dig-OnSpill.fcl @@ -16,44 +16,24 @@ physics : { # apr EventNtupleTTMCApr : { @table::EventNtupleMakerTTMC - branches : [ { - @table::TTMCBranch - input : "MergeTTApr" - } ] - KalSeedMCAssns: "TTAprKSFMC" RecoCountTag : "TTAprKSFMC" SelectEvents : [ "Digitize:apr_highP*" ] } # tpr EventNtupleTTMCTpr : { @table::EventNtupleMakerTTMC - branches : [ { - @table::TTMCBranch - input : "MergeTTTpr" - } ] - KalSeedMCAssns: "TTTprDeKSFMC" RecoCountTag : "TTTprDeKSFMC" SelectEvents : [ "Digitize:tprDe_highP*" ] } # cpr EventNtupleTTMCCpr : { @table::EventNtupleMakerTTMC - branches : [ { - @table::TTMCBranch - input : "MergeTTCpr" - } ] - KalSeedMCAssns: "TTCprDeKSFMC" RecoCountTag : "TTCprDeKSFMC" SelectEvents : [ "Digitize:cprDe_highP*" ] } # mpr EventNtupleTTMCMpr : { @table::EventNtupleMakerTTMC - branches : [ { - @table::TTMCBranch - input : "MergeTTMpr" - } ] - KalSeedMCAssns: "TTMprDeKSFMC" RecoCountTag : "TTMprDeKSFMC" SelectEvents : [ "Digitize:mprDe_highP*" ] } @@ -64,6 +44,15 @@ physics : { } #physics.analyzers.TAapr.InfoMCStructHelper.SimParticleCollectionTag: "compressDetStepMCs" +physics.analyzers.EventNtupleTTMCApr.trk.fits : [ { @table::TTMCBranch input : "MergeTTApr" } ] +physics.analyzers.EventNtupleTTMCApr.trk.mc.kalSeedMCAssns : "TTAprKSFMC" +physics.analyzers.EventNtupleTTMCTpr.trk.fits : [ { @table::TTMCBranch input : "MergeTTTpr" } ] +physics.analyzers.EventNtupleTTMCTpr.trk.mc.kalSeedMCAssns : "TTTprDeKSFMC" +physics.analyzers.EventNtupleTTMCCpr.trk.fits : [ { @table::TTMCBranch input : "MergeTTCpr" } ] +physics.analyzers.EventNtupleTTMCCpr.trk.mc.kalSeedMCAssns : "TTCprDeKSFMC" +physics.analyzers.EventNtupleTTMCMpr.trk.fits : [ { @table::TTMCBranch input : "MergeTTMpr" } ] +physics.analyzers.EventNtupleTTMCMpr.trk.mc.kalSeedMCAssns : "TTMprDeKSFMC" + end_paths : [ EndPath ] physics.trigger_paths : [ "TrigPath" ] services.TFileService.fileName: "nts.owner.trkana-triggerMC.version.sequencer.root" diff --git a/fcl/from_dig-calo.fcl b/fcl/from_dig-calo.fcl index e7df47e..d3318d7 100644 --- a/fcl/from_dig-calo.fcl +++ b/fcl/from_dig-calo.fcl @@ -24,34 +24,33 @@ physics.end_paths : [ e1 ] #Tags to fill PBI information #physics.analyzers.EventNtuple.PBTTag : "EWMProducer" -#physics.analyzers.EventNtuple.PBTMCTag : "EWMProducer" +#physics.analyzers.EventNtuple.mc.PBTMCTag : "EWMProducer" physics.analyzers.EventNtuple.RecoCountTag : "" physics.analyzers.EventNtuple.PBTTag : "" -physics.analyzers.EventNtuple.PBTMCTag : "" -physics.analyzers.EventNtuple.SimParticlesTag : "CaloShowerStepMaker" +physics.analyzers.EventNtuple.mc.PBTMCTag : "" +physics.analyzers.EventNtuple.mc.simParticlesTag : "CaloShowerStepMaker" #Turn off tracker and other branches -physics.analyzers.EventNtuple.branches : [ ] -physics.analyzers.EventNtuple.FillTrkQual : false +physics.analyzers.EventNtuple.trk.fill : false physics.analyzers.EventNtuple.FillTriggerInfo : false -physics.analyzers.EventNtuple.FillHitInfo : false -physics.analyzers.EventNtuple.FillCRVCoincs : false +physics.analyzers.EventNtuple.crv.fillCoincs : false #Toggle calo branches -physics.analyzers.EventNtuple.FillCaloClusters : false -physics.analyzers.EventNtuple.FillCaloHits : false -physics.analyzers.EventNtuple.FillCaloRecoDigis : false -physics.analyzers.EventNtuple.FillCaloDigis : true +physics.analyzers.EventNtuple.calo.fillClusters : false +physics.analyzers.EventNtuple.calo.fillHits : false +physics.analyzers.EventNtuple.calo.fillRecoDigis : false +physics.analyzers.EventNtuple.calo.fillDigis : true -physics.analyzers.EventNtuple.CaloShowerSimTag : "CaloShowerROMaker" -physics.analyzers.EventNtuple.CaloDigisTag : "CaloDigiMaker" +physics.analyzers.EventNtuple.calo.mc.showerSimTag : "CaloShowerROMaker" +physics.analyzers.EventNtuple.calo.digisTag : "CaloDigiMaker" #Toggle calo MC branches -physics.analyzers.EventNtuple.FillMCInfo : false -physics.analyzers.EventNtuple.FillCaloMC : true -physics.analyzers.EventNtuple.FillCaloClustersMC : false -physics.analyzers.EventNtuple.FillCaloHitsMC : false -physics.analyzers.EventNtuple.FillCaloSimInfos : false -physics.analyzers.EventNtuple.FillCaloDigiSimInfos : true -physics.analyzers.EventNtuple.FillCaloDigisMC : true +# mc.fill is false so tracker/CRV MC and evtinfomc are disabled; +# calo MC is controlled independently via calo.mc.fill (independent of mc.fill) +physics.analyzers.EventNtuple.mc.fill : false +physics.analyzers.EventNtuple.calo.mc.fillClusters : false +physics.analyzers.EventNtuple.calo.mc.fillHits : false +physics.analyzers.EventNtuple.calo.mc.fillSim : false +physics.analyzers.EventNtuple.calo.mc.fillDigiSim : true +physics.analyzers.EventNtuple.calo.mc.fillDigis : true services.TFileService.fileName: "nts.owner.description.version.sequencer.root" diff --git a/fcl/from_mcs-Run1B.fcl b/fcl/from_mcs-Run1B.fcl index 311cc8f..3f0aad3 100644 --- a/fcl/from_mcs-Run1B.fcl +++ b/fcl/from_mcs-Run1B.fcl @@ -1,13 +1,9 @@ # we start from mcs-extracted since Run-1B wants the straight line track fit #include "EventNtuple/fcl/from_mcs-extracted.fcl" -physics.analyzers.EventNtuple.FillMCInfo : true -physics.analyzers.EventNtuple.StepPointMCTags : [ "compressRecoMCs:virtualdetector" ] # we add the mcsteps_virtualdetector branch -physics.analyzers.EventNtuple.FillTimeClusterInfo : true -physics.analyzers.EventNtuple.TimeClustersTag : "SimpleTimeCluster" # Store time clusters for straight line track finding -physics.analyzers.EventNtuple.FillCaloMC : true -physics.analyzers.EventNtuple.FillCaloClustersMC : true -physics.analyzers.EventNtuple.FillCaloSimInfos : true +physics.analyzers.EventNtuple.mcsteps.stepPointMCTags : [ "compressRecoMCs:virtualdetector" ] # we add the mcsteps_virtualdetector branch +physics.analyzers.EventNtuple.timeclusters.fill : true +physics.analyzers.EventNtuple.timeclusters.tag : "SimpleTimeCluster" # Store time clusters for straight line track finding services.GeometryService.inputFile : "Offline/Mu2eG4/geom/geom_common.txt" # we can use the standard geometry physics.EventNtupleEndPath : [ @sequence::EventNtuple.EndPath ] # add back genCountLogger diff --git a/fcl/from_mcs-ceSimReco.fcl b/fcl/from_mcs-ceSimReco.fcl index 02bc670..c32c755 100644 --- a/fcl/from_mcs-ceSimReco.fcl +++ b/fcl/from_mcs-ceSimReco.fcl @@ -1,7 +1,7 @@ #include "EventNtuple/fcl/from_mcs-mockdata.fcl" -physics.analyzers.EventNtuple.branches : [ @local::De ] -physics.analyzers.EventNtuple.branches[0].branch : "trk" +physics.analyzers.EventNtuple.trk.fits : [ @local::De ] +physics.analyzers.EventNtuple.trk.fits[0].branchname : "trk" physics.analyzers.EventNtuple.FillTriggerInfo : false physics.EventNtuplePath : [ MergeKKDe, PBIWeight, TrkQualDe, TrkPIDDe ] physics.EventNtupleEndPath : [ EventNtuple, genCountLogger ] diff --git a/fcl/from_mcs-ceSimRecoVal.fcl b/fcl/from_mcs-ceSimRecoVal.fcl index 4a5fb3d..3a00f06 100644 --- a/fcl/from_mcs-ceSimRecoVal.fcl +++ b/fcl/from_mcs-ceSimRecoVal.fcl @@ -1,7 +1,5 @@ #include "EventNtuple/fcl/from_mcs-ceSimReco.fcl" -physics.analyzers.EventNtuple.branches[0].options.fillHits : true -physics.analyzers.EventNtuple.FillHitInfo : true -physics.analyzers.EventNtuple.FillHitCalibInfo : true +physics.analyzers.EventNtuple.trk.fillHitCalibs : true physics.analyzers.EventNtuple.FillTriggerInfo : true -physics.analyzers.EventNtuple.TriggerProcessName : "ceTrig" +physics.analyzers.EventNtuple.TriggerProcessName : "ceTrig" \ No newline at end of file diff --git a/fcl/from_mcs-extracted.fcl b/fcl/from_mcs-extracted.fcl index 98ea00b..330f0ef 100644 --- a/fcl/from_mcs-extracted.fcl +++ b/fcl/from_mcs-extracted.fcl @@ -1,8 +1,8 @@ #include "EventNtuple/fcl/from_mcs-mockdata.fcl" physics.EventNtuplePath : [ @sequence::EventNtuple.PathExt ] # path for extracted position cosmics -physics.analyzers.EventNtuple.branches : [ @local::Ext ] +physics.analyzers.EventNtuple.trk.fits : [ @local::Ext ] physics.analyzers.EventNtuple.FitType : KinematicLine -physics.analyzers.EventNtuple.FillTrkQual : false +physics.analyzers.EventNtuple.trk.fillTrkQual : false services.GeometryService.inputFile: "Production/JobConfig/cosmic/geom_cosmic_extracted.txt" diff --git a/fcl/from_mcs-mixed_trkQualCompare.fcl b/fcl/from_mcs-mixed_trkQualCompare.fcl index 24474ba..f9a4787 100644 --- a/fcl/from_mcs-mixed_trkQualCompare.fcl +++ b/fcl/from_mcs-mixed_trkQualCompare.fcl @@ -14,4 +14,4 @@ physics.EventNtuplePath : [ @sequence::EventNtuple.Path, TrkQualAllV10, TrkQualA physics.analyzers.EventNtuple.branches[0].trkQualTags : [ "TrkQualAllV10", "TrkQualAllV11", "TrkQualAllV2" ] # Removin hits -physics.analyzers.EventNtuple.FillHitInfo : false +physics.analyzers.EventNtuple.trk.fillHits : false diff --git a/fcl/from_mcs-mockdata_crvInf.fcl b/fcl/from_mcs-mockdata_crvInf.fcl index 8fc85d1..4f57491 100644 --- a/fcl/from_mcs-mockdata_crvInf.fcl +++ b/fcl/from_mcs-mockdata_crvInf.fcl @@ -9,4 +9,4 @@ physics.producers.CrvInference : { } physics.EventNtuplePath : [ @sequence::EventNtuple.Path, CrvInference ] -physics.analyzers.EventNtuple.CrvInferenceTag : "CrvInference" +physics.analyzers.EventNtuple.crv.inferenceTag : "CrvInference" diff --git a/fcl/from_mcs-mockdata_noMC.fcl b/fcl/from_mcs-mockdata_noMC.fcl index c51fd75..72698c7 100644 --- a/fcl/from_mcs-mockdata_noMC.fcl +++ b/fcl/from_mcs-mockdata_noMC.fcl @@ -1,9 +1,7 @@ #include "EventNtuple/fcl/from_mcs-mockdata.fcl" -physics.analyzers.EventNtuple.FillMCInfo : false -physics.analyzers.EventNtuple.FillCaloMC : false -physics.analyzers.EventNtuple.FillCaloClustersMC : false -physics.analyzers.EventNtuple.FillCaloSimInfos : false +physics.analyzers.EventNtuple.mc.fill : false +physics.analyzers.EventNtuple.calo.mc.fill : false physics.EventNtuplePath : [ @sequence::EventNtuple.PathNoMC ] physics.EventNtupleEndPath : [ @sequence::EventNtuple.EndPathNoMC ] diff --git a/fcl/from_mcs-mockdata_separateTrkBranches.fcl b/fcl/from_mcs-mockdata_separateTrkBranches.fcl index 75330dc..84b6d0d 100644 --- a/fcl/from_mcs-mockdata_separateTrkBranches.fcl +++ b/fcl/from_mcs-mockdata_separateTrkBranches.fcl @@ -1,5 +1,5 @@ #include "EventNtuple/fcl/from_mcs-mockdata.fcl" -physics.analyzers.EventNtuple.branches : [ @local::DeM, @local::DeP, @local::UeM, @local::UeP, +physics.analyzers.EventNtuple.trk.fits : [ @local::DeM, @local::DeP, @local::UeM, @local::UeP, @local::DmuM, @local::DmuP, @local::UmuM, @local::UmuP ] physics.EventNtuplePath : [ @sequence::EventNtuple.PathSeparate ] diff --git a/fcl/from_mcs-primary_addVDSteps.fcl b/fcl/from_mcs-primary_addVDSteps.fcl index 85906f2..2e1ffa2 100644 --- a/fcl/from_mcs-primary_addVDSteps.fcl +++ b/fcl/from_mcs-primary_addVDSteps.fcl @@ -1,3 +1,3 @@ #include "EventNtuple/fcl/from_mcs-primary.fcl" -physics.analyzers.EventNtuple.StepPointMCTags : [ "compressRecoMCs:virtualdetector" ] +physics.analyzers.EventNtuple.mcsteps.stepPointMCTags : [ "compressRecoMCs:virtualdetector" ] diff --git a/fcl/from_mcs-reflection.fcl b/fcl/from_mcs-reflection.fcl index ae0c176..f116d4d 100644 --- a/fcl/from_mcs-reflection.fcl +++ b/fcl/from_mcs-reflection.fcl @@ -8,15 +8,13 @@ EN : { @table::EventNtupleMaker FitType : LoopHelix diagLevel : 2 - FillMCInfo : true - FillTrkPIDInfo : false - FillHitInfo : false FillTriggerInfo : false - SurfaceStepCollectionTag : "compressRecoMCs" } +EN.trk.fillHits : false +EN.trk.fillTrkQual : false +EN.trk.fillTrkPID : false ENBranch : { branch : "trk" - options : { fillMC : true genealogyDepth : -1 } } END_PROLOG process_name: ENeflect @@ -62,25 +60,10 @@ physics : ENe : { @table::EN SelectEvents : [ "eTrig" ] - branches : [ - { @table::ENBranch - trkQualTags : ["TrkQualReflecte"] - trkPIDTags : ["TrkPIDReflecte"] - input: "Reflecte" - } - ] } ENmu : { @table::EN SelectEvents : [ "muTrig" ] - branches : [ - { @table::ENBranch - trkQualTags : ["TrkQualReflectmu"] - trkPIDTags : ["TrkPIDReflectmu"] - input: "Reflectmu" - } - - ] } printModule : { module_type : PrintModule @@ -98,5 +81,19 @@ physics : physics.trigger_paths : [ "eTrig", "muTrig"] physics.end_paths : [ "eEnd", "muEnd", "CLPrint" ] +physics.analyzers.ENe.trk.fits : [ + { @table::ENBranch + trkQualTags : ["TrkQualReflecte"] + trkPIDTags : ["TrkPIDReflecte"] + input: "Reflecte" + } +] +physics.analyzers.ENmu.trk.fits : [ + { @table::ENBranch + trkQualTags : ["TrkQualReflectmu"] + trkPIDTags : ["TrkPIDReflectmu"] + input: "Reflectmu" + } +] services.TimeTracker.printSummary: true services.TFileService.fileName: "nts.owner.ENreflection.version.sequence.root" diff --git a/fcl/from_rec-crv-kpp.fcl b/fcl/from_rec-crv-kpp.fcl index d8fd9cf..a91c20c 100644 --- a/fcl/from_rec-crv-kpp.fcl +++ b/fcl/from_rec-crv-kpp.fcl @@ -1,19 +1,14 @@ #include "EventNtuple/fcl/from_mcs-mockdata_noMC.fcl" -physics.analyzers.EventNtuple.FillCRVDigis : false -physics.analyzers.EventNtuple.FillCRVPulses : false -physics.analyzers.EventNtuple.branches : [ ] # no track branches -physics.analyzers.EventNtuple.FillCaloClusters : false # no calorimeter -physics.analyzers.EventNtuple.FillCaloHits : false # no calorimeter -physics.analyzers.EventNtuple.FillCaloClustersMC : false # no calorimeter -physics.analyzers.EventNtuple.FillCaloSimInfos : false # no calorimeter +physics.analyzers.EventNtuple.trk.fill : false +physics.analyzers.EventNtuple.calo.fill : false # no calorimeter physics.analyzers.EventNtuple.RecoCountTag : "" physics.analyzers.EventNtuple.PBTTag : "" physics.analyzers.EventNtuple.hasCRV : false #false keeps events that have no CRV coincidences physics.analyzers.EventNtuple.FillTriggerInfo : false -physics.analyzers.EventNtuple.CrvDigisTag : "CrvDigi" -physics.analyzers.EventNtuple.CrvCoincidencesTag: "CrvCoincidenceClusterFinder" -physics.analyzers.EventNtuple.CrvRecoPulsesTag: "CrvRecoPulses" +physics.analyzers.EventNtuple.crv.digisTag : "CrvDigi" +physics.analyzers.EventNtuple.crv.coincidencesTag : "CrvCoincidenceClusterFinder" +physics.analyzers.EventNtuple.crv.recoPulsesTag : "CrvRecoPulses" services.GeometryService.inputFile: "Offline/Mu2eG4/geom/geom_common_extracted.txt" diff --git a/fcl/from_rec-crv-kpp_withCrvDigis.fcl b/fcl/from_rec-crv-kpp_withCrvDigis.fcl index 6cd03e4..1a1e466 100644 --- a/fcl/from_rec-crv-kpp_withCrvDigis.fcl +++ b/fcl/from_rec-crv-kpp_withCrvDigis.fcl @@ -1,3 +1,3 @@ #include "EventNtuple/fcl/from_rec-crv-kpp.fcl" -physics.analyzers.EventNtuple.FillCRVDigis : true +physics.analyzers.EventNtuple.crv.fillDigis : true diff --git a/fcl/prolog.fcl b/fcl/prolog.fcl index 00a4baa..e261866 100644 --- a/fcl/prolog.fcl +++ b/fcl/prolog.fcl @@ -97,12 +97,9 @@ TTProducersPath : [ MergeTTApr, MergeTTTpr, MergeTTCpr, MergeTTMpr ] genCountLogger: { module_type: GenEventCountReader } -AllOpt : { - fillMC : true - fillHits : true - genealogyDepth : -1 - matchDepth : -1 -} +# AllOpt is no longer needed: BranchOptConfig C++ defaults now match these values. +# Kept here only for reference / backwards compatibility with any external FCLs. +# AllOpt : { fillMC : true fillHits : true genealogyDepth : -1 matchDepth : -1 } # With move to using KalSeedPtr need to "merge" KalSeeds so that we get KalSeedPtr collections MergeKK : { module_type : MergeKalSeeds } @@ -191,167 +188,185 @@ MergeKKSeparatePath : [ MergeKKDeM, MergeKKUeM, MergeKKDmuM, MergeKKDeP, MergeKK DeM : { input : "MergeKKDeM" - branch : "dem" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "dem" trkQualTags : ["TrkQualDeM"] trkPIDTags : ["TrkPIDDeM"] } UeM : { input : "MergeKKUeM" - branch : "uem" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "uem" trkQualTags : [ ] trkPIDTags : [ ] } DmuM : { input : "MergeKKDmuM" - branch : "dmm" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "dmm" trkQualTags : [ ] trkPIDTags : [ ] } UmuM : { input : "MergeKKUmuM" - branch : "umm" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "umm" trkQualTags : [ ] trkPIDTags : [ ] } DeP : { input : "MergeKKDeP" - branch : "dep" - options : { fillMC : true genealogyDepth : -1 } + branchname : "dep" trkQualTags : [ ] trkPIDTags : [ ] } UeP : { input : "MergeKKUeP" - branch : "uep" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "uep" trkQualTags : [ ] trkPIDTags : [ ] } DmuP : { input : "MergeKKDmuP" - branch : "dmp" + branchname : "dmp" trkQualTags : [ ] trkPIDTags : [ ] } UmuP : { input : "MergeKKUmuP" - branch : "ump" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "ump" trkQualTags : [ ] trkPIDTags : [ ] } Ext : { input : "MergeKKLine" - branch : "trk" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 fillHits : true } + branchname : "trk" trkQualTags : [ ] trkPIDTags : [ ] } Off : { input : "MergeKKOff" - branch : "trk" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 fillHits : true } + branchname : "trk" trkQualTags : [ ] trkPIDTags : [ ] } All : { input : "MergeKKAll" - branch : "trk" - options : { fillHits : true fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "trk" trkQualTags : ["TrkQualAll"] trkPIDTags : ["TrkPIDAll"] } DeCalib : { input : "MergeKKDeCalib" - branch : "trk" - options : { fillMC : true fillHits : true genealogyDepth : -1 matchDepth : -1 } + branchname : "trk" trkQualTags : [ ] trkPIDTags : [ ] } De : { input : "MergeKKDe" - branch : "de" - options : { fillMC : true genealogyDepth : -1 matchDepth : -1 } + branchname : "de" trkQualTags : ["TrkQualDe"] trkPIDTags : ["TrkPIDDe"] } EventNtupleMaker : { module_type : EventNtupleMaker - branches : [ @local::All ] RecoCountTag : "SelectReco" PBITag : "PBISim" PBTTag : "EWMProducer" EWMTag : "EWMProducer" - PBTMCTag : "EWMProducer" - CrvCoincidencesTag : "SelectReco:CrvCoincidenceClusterFinder" - CrvCoincidenceMCsTag : "compressRecoMCs:CrvCoincidenceClusterMatchMC" - CrvRecoPulsesTag : "SelectReco" - CrvStepsTag : "compressRecoMCs" - SimParticlesTag : "compressRecoMCs" - MCTrajectoriesTag : "compressRecoMCs" - TimeClustersTag : "MHDe" - FillTimeClusterInfo : false - CaloClustersTag : "CaloClusterMaker" - CaloHitsTag : "CaloHitMaker" - CaloRecoDigisTag : "CaloRecoDigiMaker" - CaloDigisTag : "SelectReco" - FillCaloClusters : true - FillCaloHits : true - FillCaloRecoDigis : false - FillCaloDigis : false - CrvDigiMCsTag : "compressRecoMCs" - CrvDigisTag : "SelectReco" - CrvCoincidenceClusterMCAssnsTag : "CrvCoincidenceClusterMCAssns" - CrvPlaneY : @local::crvPlaneY.CRV_T - FillMCInfo : true - FillTrkQual : true - FillTrkPIDInfo : false - FillHitInfo : true - FillHitCalibInfo : false FillTriggerInfo : true TriggerProcessName : "Digitize" TriggerPathSuffix : "DigitizePath" - FillCRVCoincs : true - FillCRVPulses : false - FillCRVDigis : false - FillCaloMC : true - FillCaloClustersMC : true - FillCaloHitsMC : true - FillHelixInfo : false - FillCaloSimInfos : true - PrimaryParticleTag : "compressRecoMCs" - KalSeedMCAssns : "SelectReco" - CaloClusterMCTag : "compressRecoMCs" - CaloHitMCTag : "compressRecoMCs" - InfoMCStructHelper : { - SimParticleCollectionTag : "compressRecoMCs" - MinGoodMomFraction : 0.9 - MaxVDDt : 5 # ns - } -# ExtraMCStepCollectionTags : [ "compressRecoMCs:protonabsorber", "compressRecoMCs:stoppingtarget" ] - ExtraMCStepCollectionTags : [] - SurfaceStepCollectionTag : "compressRecoMCs" FitType : LoopHelix - StepPointMCTags : [ ] + diagLevel : 0 + # -- Event-level MC (gates tracker MC, CRV MC, and evtinfomc branch) ------ + mc : { + fill : true + PBTMCTag : "EWMProducer" + simParticlesTag : "compressRecoMCs" + mcTrajectoriesTag : "compressRecoMCs" + primaryParticleTag : "compressRecoMCs" + infoMCStructHelper : { + SimParticleCollectionTag : "compressRecoMCs" + MinGoodMomFraction : 0.9 + MaxVDDt : 5 # ns + } + } + # -- Tracker subsystem ----------------------------------------------------- + trk : { + fill : true + fillHits : true + fillHitCalibs : false + fillTrkQual : true + fillTrkPID : false + fits : [ @local::All ] + mc : { + kalSeedMCAssns : "SelectReco" + } + } + # -- Calorimeter subsystem ------------------------------------------------- + # calo.mc.fill is independent of mc.fill: calo-only jobs can fill calo MC + # without loading tracker/event-level MC products. + calo : { + fill : true + fillClusters : true + clustersTag : "CaloClusterMaker" + fillHits : true + hitsTag : "CaloHitMaker" + fillRecoDigis : false + recoDigisTag : "CaloRecoDigiMaker" + fillDigis : false + digisTag : "SelectReco" + mc : { + fill : true + fillTrackMatch : true + fillClusters : true + clusterMCTag : "compressRecoMCs" + fillHits : true + hitMCTag : "compressRecoMCs" + fillSim : true + fillDigis : false + fillDigiSim : false + showerSimTag : "compressRecoMCs" + } + } + # -- CRV subsystem --------------------------------------------------------- + crv : { + fill : true + fillCoincs : true + coincidencesTag : "SelectReco:CrvCoincidenceClusterFinder" + recoPulsesTag : "SelectReco" + stepsTag : "compressRecoMCs" + fillPulses : false + fillDigis : false + digisTag : "SelectReco" + planeY : @local::crvPlaneY.CRV_T + mc : { + coincidenceMCsTag : "compressRecoMCs:CrvCoincidenceClusterMatchMC" + digiMCsTag : "compressRecoMCs" + assnsTag : "CrvCoincidenceClusterMCAssns" + } + } + # -- Helix seeds ----------------------------------------------------------- + helices : { + fill : false + } + # -- Time clusters --------------------------------------------------------- + timeclusters : { + fill : false + tag : "MHDe" + } + # -- MC step collections --------------------------------------------------- + mcsteps : { + surfaceStepsTag : "compressRecoMCs" + } } # instance for processing trigger (digitization) output from simulation -EventNtupleMakerTTMC: { +EventNtupleMakerTTMC : { @table::EventNtupleMaker diagLevel : 2 - FillCRVCoincs : false - FillCaloMC : false FillTriggerInfo : true - FillTrkPIDInfo : false - FillHitInfo : true - PrimaryParticleTag: "compressDigiMCs" - MCTrajectoriesTag: "compressDigiMCs" FitType : LoopHelix - TriggerProcessName : "Digitize" - SimParticlesTag : "compressDigiMCs" - ExtraMCStepCollectionTags: [] - InfoMCStructHelper : { - MinGoodMomFraction : 0.9 - MaxVDDt : 5 # ns - SimParticleCollectionTag : "compressDigiMCs" - } } +EventNtupleMakerTTMC.crv.fill : false +EventNtupleMakerTTMC.calo.mc.fillTrackMatch : false +EventNtupleMakerTTMC.mc.primaryParticleTag : "compressDigiMCs" +EventNtupleMakerTTMC.mc.mcTrajectoriesTag : "compressDigiMCs" +EventNtupleMakerTTMC.mc.simParticlesTag : "compressDigiMCs" +EventNtupleMakerTTMC.mc.infoMCStructHelper.SimParticleCollectionTag : "compressDigiMCs" +# TTMCBranch: per-branch config for trigger MC branches. +# No options needed -- BranchOptConfig defaults are fillMC:true, fillHits:true, +# genealogyDepth:-1, matchDepth:-1. TTMCBranch : { - branch : "trk" - options : { fillMC : true fillHits : true genealogyDepth : -1 matchDepth : -1 } + branchname : "trk" + trkQualTags : [ ] + trkPIDTags : [ ] } EventNtuple : { @@ -383,15 +398,14 @@ ENDeCalib : { @table::EventNtupleMaker hasTracks : true FitType : LoopHelix - branches : [ - { input: "MergeKKDeCalib" - branch : "trk" - trkQualTags : ["TrkQualDe"] - trkPIDTags : ["TrkPIDDe"] - options : { fillMC : true fillHits : true genealogyDepth : -1 } - } - ] SelectEvents : [ TriggerPath ] } +ENDeCalib.trk.fits : [ + { input: "MergeKKDeCalib" + branchname : "trk" + trkQualTags : ["TrkQualDe"] + trkPIDTags : ["TrkPIDDe"] + } +] END_PROLOG diff --git a/src/EventNtupleMaker_module.cc b/src/EventNtupleMaker_module.cc index f2e15b7..ae89f9b 100644 --- a/src/EventNtupleMaker_module.cc +++ b/src/EventNtupleMaker_module.cc @@ -95,114 +95,192 @@ namespace mu2e { // Need this for the BaBar headers. using CLHEP::Hep3Vector; typedef KalSeedCollection::const_iterator KSCIter; - typedef size_t BranchIndex; + typedef size_t TrkFitBranchIndex; typedef size_t StepCollIndex; class EventNtupleMaker : public art::EDAnalyzer { public: - struct BranchOptConfig { + // ── Per-branch options (defaults match intended use; omit options in FCL + // unless you want to override a specific field) ────────────────────── + struct TrkFitOptConfig { using Name=fhicl::Name; using Comment=fhicl::Comment; - fhicl::Atom fillmc{Name("fillMC"), Comment("Switch to turn on filling of MC information for this set of tracks"), false}; - fhicl::Atom fillhits{Name("fillHits"), Comment("Switch to turn on filling of hit-level information for this set of tracks"), false}; - fhicl::Atom genealogyDepth{Name("genealogyDepth"), Comment("The depth of the genealogy information you want to keep"), 1}; - fhicl::Atom matchDepth{Name("matchDepth"), Comment("The depth into the MC true particle matching you want to keep"), 1}; + fhicl::Atom fillmc{Name("fillMC"), Comment("Fill MC information for this branch"), true}; + fhicl::Atom fillhits{Name("fillHits"), Comment("Fill hit-level information for this branch"), true}; + fhicl::Atom genealogyDepth{Name("genealogyDepth"), Comment("Depth of MC genealogy to keep (-1 = all)"), -1}; + fhicl::Atom matchDepth{Name("matchDepth"), Comment("Depth of MC truth matching to keep (-1 = all)"), -1}; }; - struct BranchConfig { + struct TrkFitConfig { using Name=fhicl::Name; using Comment=fhicl::Comment; - fhicl::Atom input{Name("input"), Comment("KalSeedCollection input tag")}; - fhicl::Atom branch{Name("branch"), Comment("Name of output branch")}; + fhicl::Atom branchname{Name("branchname"), Comment("Name of output branch")}; + fhicl::Atom enabled{Name("enabled"), Comment("Set false to skip this branch entirely (no collection reads, no output branches)"), true}; fhicl::Sequence trkQualTags{Name("trkQualTags"), Comment("Input tags for MVAResultCollection to use for TrkQuals")}; fhicl::Sequence trkPIDTags{Name("trkPIDTags"), Comment("Input tags for MVAResultCollection to use for TrkPID")}; - fhicl::Table options{Name("options"), Comment("Optional arguments for a branch")}; + fhicl::Table options{Name("options"), Comment("Per-branch fill options")}; + }; + + // ── Event-level MC config (tracker+CRV share these event products) ───── + struct EventMCConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill{Name("fill"), + Comment("Master gate for tracker MC, CRV MC, and event-level MC info branches. " + "Calo MC is controlled independently via calo.mc.fill."), true}; + fhicl::Atom PBTMCTag{Name("PBTMCTag"), Comment("Tag for ProtonBunchTimeMC object")}; + fhicl::Atom simParticlesTag{Name("simParticlesTag"), Comment("SimParticle Collection Tag")}; + fhicl::Atom mcTrajectoriesTag{Name("mcTrajectoriesTag"), Comment("MCTrajectory Collection Tag")}; + fhicl::Atom primaryParticleTag{Name("primaryParticleTag"), Comment("Tag for PrimaryParticle")}; + fhicl::Table infoMCStructHelper{Name("infoMCStructHelper"), Comment("Configuration for InfoMCStructHelper")}; + }; + + // ── Tracker subsystem config ─────────────────────────────────────────── + struct TrkConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + // master switch + fhicl::Atom fill{Name("fill"), Comment("Enable the tracker subsystem entirely"), true}; + // reco flags + fhicl::Atom fillHits{Name("fillHits"), + Comment("Global enable for hit-level branches; per-branch options.fillHits also required"), true}; + fhicl::Atom fillHitCalibs{Name("fillHitCalibs"), Comment("Fill hit calibration branches"), false}; + fhicl::Atom fillTrkQual{Name("fillTrkQual"), Comment("Fill TrkQual MVA branches"), false}; + fhicl::Atom fillTrkPID{Name("fillTrkPID"), Comment("Fill TrkPID MVA branches"), false}; + // per-branch configurations + fhicl::Sequence> fits{Name("fits"), Comment("KalSeed collections to write into a single track branch")}; + // tracker MC sub-config + struct MCConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill{Name("fill"), Comment("Master switch for all tracker MC branches; mc.fill must also be true"), true}; + fhicl::Atom kalSeedMCAssns{Name("kalSeedMCAssns"), Comment("Tag for KalSeedMCAssn")}; + }; + fhicl::Table mc{Name("mc"), Comment("Tracker MC filling options")}; + }; + + // ── Helix seed config (independent of per-branch track config) ───────── + struct HelixConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill{Name("fill"), Comment("Fill helix seed branches"), false}; + }; + + // ── Time cluster config (independent of per-branch track config) ─────── + struct TimeClusterConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill{Name("fill"), Comment("Fill time cluster branch"), false}; + fhicl::Atom tag {Name("tag"), Comment("Tag for time cluster collection")}; + }; + + // ── MC step collections config (independent of per-branch track config) ─ + struct MCStepsConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::OptionalSequence extraMCStepTags{Name("extraMCStepTags"), Comment("Tags for extra StepPointMCCollections associated with KalSeeds")}; + fhicl::OptionalAtom surfaceStepsTag{Name("surfaceStepsTag"), Comment("SurfaceStep collection tag; omit to disable surface step branches")}; + fhicl::OptionalSequence stepPointMCTags{Name("stepPointMCTags"), Comment("Tags for global StepPointMCCollections (not per-track)")}; + }; + + // ── Calorimeter subsystem config ─────────────────────────────────────── + // Calo MC is gated solely by calo.mc.fill; it is independent of mc.fill + // so that calo-only jobs can fill calo MC without loading tracker MC products. + struct CaloConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + // master switch + fhicl::Atom fill{Name("fill"), Comment("Enable the calorimeter subsystem entirely"), true}; + // reco flags + collocated tags + fhicl::Atom fillClusters {Name("fillClusters"), Comment("Fill calorimeter cluster branch"), true}; + fhicl::Atom clustersTag {Name("clustersTag"), Comment("Tag for CaloCluster collection")}; + fhicl::Atom fillHits {Name("fillHits"), Comment("Fill calorimeter hit branch"), false}; + fhicl::Atom hitsTag {Name("hitsTag"), Comment("Tag for CaloHit collection")}; + fhicl::Atom fillRecoDigis{Name("fillRecoDigis"), Comment("Fill calorimeter reco-digi branch"), false}; + fhicl::Atom recoDigisTag {Name("recoDigisTag"), Comment("Tag for CaloRecoDigi collection")}; + fhicl::Atom fillDigis {Name("fillDigis"), Comment("Fill calorimeter digi branch"), false}; + fhicl::Atom digisTag {Name("digisTag"), Comment("Tag for CaloDigi collection")}; + // MC sub-config + struct MCConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill{Name("fill"), Comment("Master switch for all calorimeter MC branches; independent of mc.fill"), true}; + // track-associated calo MC (the calohitmc. branch per track type) + fhicl::Atom fillTrackMatch{Name("fillTrackMatch"), Comment("Fill per-track calo cluster MC branch (calohitmc.)"), true}; + // standalone calo MC branches + fhicl::Atom fillClusters{Name("fillClusters"), Comment("Fill standalone caloclustersmc. branch"), true}; + fhicl::Atom clusterMCTag{Name("clusterMCTag"), Comment("Tag for CaloClusterMCCollection")}; + fhicl::Atom fillHits {Name("fillHits"), Comment("Fill standalone calohitsmc. branch"), true}; + fhicl::Atom hitMCTag {Name("hitMCTag"), Comment("Tag for CaloHitMCCollection")}; + fhicl::Atom fillSim {Name("fillSim"), Comment("Fill calomcsim. (sim particle info) branch"), true}; + fhicl::Atom fillDigis {Name("fillDigis"), Comment("Fill standalone calodigismc. branch"), false}; + fhicl::Atom fillDigiSim {Name("fillDigiSim"), Comment("Fill calodigisim. branch"), false}; + fhicl::Atom showerSimTag{Name("showerSimTag"), Comment("Tag for CaloShowerSim collection")}; + }; + fhicl::Table mc{Name("mc"), Comment("Calorimeter MC filling options")}; + }; + + // ── CRV subsystem config ─────────────────────────────────────────────── + struct CRVConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + // master switch + fhicl::Atom fill{Name("fill"), Comment("Enable the CRV subsystem entirely"), false}; + // reco flags + collocated tags + fhicl::Atom fillCoincs {Name("fillCoincs"), Comment("Fill CRV coincidence cluster branches"), false}; + fhicl::Atom coincidencesTag{Name("coincidencesTag"), Comment("Tag for CrvCoincidenceCluster collection")}; + fhicl::Atom recoPulsesTag {Name("recoPulsesTag"), Comment("Tag for CrvRecoPulse collection")}; + fhicl::Atom stepsTag {Name("stepsTag"), Comment("Tag for CrvStep collection")}; + fhicl::Atom fillPulses {Name("fillPulses"), Comment("Fill CRV reco pulse branches"), false}; + fhicl::Atom fillDigis {Name("fillDigis"), Comment("Fill CRV digi branch"), false}; + fhicl::Atom digisTag {Name("digisTag"), Comment("Tag for CrvDigi collection")}; + fhicl::Atom planeY {Name("planeY"), Comment("Y of center of top layer of CRV-T counters (mm)")}; + fhicl::OptionalAtom inferenceTag{Name("inferenceTag"), Comment("Tag for CrvInference Assns (art::Assns); omit to disable")}; + // MC sub-config (requires mc.fill also be true) + struct MCConfig { + using Name=fhicl::Name; + using Comment=fhicl::Comment; + fhicl::Atom fill {Name("fill"), Comment("Master switch for CRV MC branches; mc.fill must also be true"), true}; + fhicl::Atom coincidenceMCsTag {Name("coincidenceMCsTag"), Comment("Tag for CrvCoincidenceClusterMC collection")}; + fhicl::Atom digiMCsTag {Name("digiMCsTag"), Comment("Tag for CrvDigiMC collection")}; + fhicl::Atom assnsTag {Name("assnsTag"), Comment("Tag for CrvCoincidenceClusterMCAssns")}; + }; + fhicl::Table mc{Name("mc"), Comment("CRV MC filling options")}; }; struct Config { using Name=fhicl::Name; using Comment=fhicl::Comment; - // General control and config - fhicl::Atom diag{Name("diagLevel"),1}; + // General control + fhicl::Atom diag{Name("diagLevel")}; fhicl::Atom debug{Name("debugLevel"),0}; fhicl::Atom splitlevel{Name("splitlevel"),99}; fhicl::Atom buffsize{Name("buffsize"),32000}; fhicl::Atom hastrks{Name("hasTracks"), Comment("Require >=1 tracks to fill tuple"), false}; fhicl::Atom hascrv{Name("hasCRV"), Comment("Require CRV information to fill tuple"), false}; - // General event info - fhicl::Atom rctag{Name("RecoCountTag"), Comment("RecoCount"), art::InputTag()}; - fhicl::Atom PBITag{Name("PBITag"), Comment("Tag for ProtonBunchIntensity object") ,art::InputTag()}; - fhicl::Atom PBTTag{Name("PBTTag"), Comment("Tag for ProtonBunchTime object") ,art::InputTag()}; - fhicl::Atom EWMTag{Name("EWMTag"), Comment("Tag for EventWindowMarker object") ,art::InputTag()}; - fhicl::Atom filltrig{Name("FillTriggerInfo"),false}; + // General event info + fhicl::Atom rctag{Name("RecoCountTag"), Comment("RecoCount")}; + fhicl::Atom PBITag{Name("PBITag"), Comment("Tag for ProtonBunchIntensity object")}; + fhicl::Atom PBTTag{Name("PBTTag"), Comment("Tag for ProtonBunchTime object")}; + fhicl::Atom EWMTag{Name("EWMTag"), Comment("Tag for EventWindowMarker object")}; + // Trigger + fhicl::Atom filltrig{Name("FillTriggerInfo"), false}; fhicl::Atom trigProcessName{Name("TriggerProcessName"), Comment("Process name for Trigger")}; - fhicl::Atom trigpathsuffix{Name("TriggerPathSuffix"), "_trigger"}; // all trigger paths have this in the name - // core tracking - fhicl::Sequence > branches{Name("branches"), Comment("All the branches we want to write")}; - // Additional (optional) tracking information - fhicl::Atom fillhits{Name("FillHitInfo"),Comment("Global switch to turn on/off hit-level info"), false}; - fhicl::Atom fillhitcalibs{Name("FillHitCalibInfo"), Comment("Switch to turn on filling of hit-level information for this set of tracks"), false}; - fhicl::Atom fittype{Name("FitType"),Comment("Type of track Fit: LoopHelix, CentralHelix, KinematicLine, or Unknown"),"Unknown"}; - fhicl::Atom helices{Name("FillHelixInfo"),false}; - fhicl::Atom fillTimeClusters{Name("FillTimeClusterInfo"),Comment("Global switch to turn on/off time cluster info"), false}; - fhicl::Atom timeClustersTag{Name("TimeClustersTag"), Comment("Tag for time cluster collection"), art::InputTag()}; - // Calorimeter input - fhicl::Atom caloClustersTag{Name("CaloClustersTag"), Comment("Tag for Calorimeter cluster collection"), art::InputTag()}; - fhicl::Atom caloHitsTag{Name("CaloHitsTag"), Comment("Tag for Calorimeter hit collection"), art::InputTag()}; - fhicl::Atom caloRecoDigisTag{Name("CaloRecoDigisTag"), Comment("Tag for Calorimeter recodigi collection"), art::InputTag()}; - fhicl::Atom caloDigisTag{Name("CaloDigisTag"), Comment("Tag for Calorimeter digi collection"), art::InputTag()}; - fhicl::Atom caloShowerSimTag{Name("CaloShowerSimTag"), Comment("Tag for Calorimeter shower sim collection"), art::InputTag()}; - // Calorimeter flags - fhicl::Atom fillCaloClusters{Name("FillCaloClusters"),Comment("Flag for turning on Calo Clusters branch"), true}; - fhicl::Atom fillCaloHits{Name("FillCaloHits"),Comment("Flag for turning on Calo Hits branch"), false}; - fhicl::Atom fillCaloRecoDigis{Name("FillCaloRecoDigis"),Comment("Flag for turning on Calo RecoDigis branch"), false}; - fhicl::Atom fillCaloDigis{Name("FillCaloDigis"),Comment("Flag for turning on Calo Digis branch"), false}; - fhicl::Atom fillCaloDigisMC{Name("FillCaloDigisMC"),Comment("Flag for turning on Calo Digis MC branch"), false}; - // CRV -- input tags - fhicl::Atom crvCoincidencesTag{Name("CrvCoincidencesTag"), Comment("Tag for CrvCoincidenceCluster Collection"), art::InputTag()}; - fhicl::Atom crvRecoPulsesTag{Name("CrvRecoPulsesTag"), Comment("Tag for CrvRecopPulse Collection"), art::InputTag()}; - fhicl::Atom crvStepsTag{Name("CrvStepsTag"), Comment("Tag for CrvStep Collection"), art::InputTag()}; - fhicl::Atom crvDigiMCsTag{Name("CrvDigiMCsTag"), Comment("Tag for CrvDigiMC Collection"), art::InputTag()}; - fhicl::Atom crvDigisTag{Name("CrvDigisTag"), Comment("Tag for CrvDigi Collection"), art::InputTag()}; - // CRV -- flags - fhicl::Atom fillcrvcoincs{Name("FillCRVCoincs"),Comment("Flag for turning on crv CoincidenceClusterbranches"), false}; - fhicl::Atom fillcrvpulses{Name("FillCRVPulses"),Comment("Flag for turning on crvpulses(mc) branches"), false}; - fhicl::Atom fillcrvdigis{Name("FillCRVDigis"),Comment("Flag for turning on crvdigis branch"), false}; - // CRV -- other - fhicl::Atom crvPlaneY{Name("CrvPlaneY"),Comment("y of center of the top layer of the CRV-T counters"), 2751.485}; //This belongs in KinKalGeom as an intersection plane, together with the rest of the CRV planes FIXME - // CRV inference - fhicl::OptionalAtom crvInferenceTag{Name("CrvInferenceTag"), Comment("Tag for CrvInference associations (art::Assns)")}; - // MC truth - fhicl::Atom fillmc{Name("FillMCInfo"),Comment("Global switch to turn on/off MC info"),true}; - fhicl::Table infoMCStructHelper{Name("InfoMCStructHelper"), Comment("Configuration for the InfoMCStructHelper")}; - fhicl::Atom PBTMCTag{Name("PBTMCTag"), Comment("Tag for ProtonBunchTimeMC object") ,art::InputTag()}; - fhicl::Atom simParticlesTag{Name("SimParticlesTag"), Comment("SimParticle Collection Tag")}; - fhicl::Atom mcTrajectoriesTag{Name("MCTrajectoriesTag"), Comment("MCTrajectory Collection Tag")}; - fhicl::Atom primaryParticleTag{Name("PrimaryParticleTag"), Comment("Tag for PrimaryParticle"), art::InputTag()}; - fhicl::Atom kalSeedMCTag{Name("KalSeedMCAssns"), Comment("Tag for KalSeedMCAssn"), art::InputTag()}; - // extra MC - fhicl::OptionalSequence extraMCStepTags{Name("ExtraMCStepCollectionTags"), Comment("Input tags for any other StepPointMCCollections you want written out. This will only write out steps associated with the KalSeed")}; - // passive elements and Virtual Detector MC information - fhicl::OptionalAtom SurfaceStepsTag{Name("SurfaceStepCollectionTag"), Comment("SurfaceStep Collection")}; - fhicl::OptionalSequence stepPointMCTags{Name("StepPointMCTags"), Comment("Input tags for any other StepPointMCCollections you want written out. This will write out all steps in the collection")}; - // Calo MC - fhicl::Atom fillCaloMC{ Name("FillCaloMC"),Comment("Fill CaloMC information"), true}; - fhicl::Atom fillCaloClustersMC{ Name("FillCaloClustersMC"),Comment("Fill Calo Cluster MC information"), true}; - fhicl::Atom fillCaloHitsMC{ Name("FillCaloHitsMC"),Comment("Fill Calo Hit MC information"), true}; - fhicl::Atom fillCaloSimInfos{ Name("FillCaloSimInfos"),Comment("Fill Sim particles information associated with calo clusters"), true}; - fhicl::Atom fillCaloDigiSimInfos{ Name("FillCaloDigiSimInfos"),Comment("Fill Sim particles information associated with calo digis"), true}; - fhicl::Atom caloClusterMCTag{Name("CaloClusterMCTag"), Comment("Tag for CaloClusterMCCollection") ,art::InputTag()}; - fhicl::Atom caloHitMCTag{Name("CaloHitMCTag"), Comment("Tag for CaloHitMCCollection") ,art::InputTag()}; - // CRV MC - fhicl::Atom crvCoincidenceMCsTag{Name("CrvCoincidenceMCsTag"), Comment("Tag for CrvCoincidenceClusterMC Collection"), art::InputTag()}; - fhicl::Atom crvMCAssnsTag{ Name("CrvCoincidenceClusterMCAssnsTag"), Comment("art::InputTag for CrvCoincidenceClusterMCAssns")}; - // Pre-processed analysis info; are these redundant with the branch config ? - fhicl::Atom filltrkpid{Name("FillTrkPIDInfo"),false}; - fhicl::Atom filltrkqual{Name("FillTrkQual"),false}; + fhicl::Atom trigpathsuffix{Name("TriggerPathSuffix"), "_trigger"}; + // Fit type + fhicl::Atom fittype{Name("FitType"), Comment("Type of track fit: LoopHelix, CentralHelix, KinematicLine, or Unknown"), "Unknown"}; + // ── Subsystems ──────────────────────────────────────────────────────── + fhicl::Table mc {Name("mc"), Comment("Event-level MC config: gates tracker+CRV MC and provides shared event MC products")}; + fhicl::Table trk {Name("trk"), Comment("Tracker subsystem config")}; + fhicl::Table calo {Name("calo"), Comment("Calorimeter subsystem config")}; + fhicl::Table crv {Name("crv"), Comment("CRV subsystem config")}; + fhicl::Table helices {Name("helices"), Comment("Helix seed branches config")}; + fhicl::Table timeclusters{Name("timeclusters"),Comment("Time cluster branch config")}; + fhicl::Table mcsteps {Name("mcsteps"), Comment("MC step collection branches config")}; }; typedef art::EDAnalyzer::Table Parameters; @@ -216,7 +294,7 @@ namespace mu2e { private: Config _conf; - std::vector _allBranches; // configurations for all track branches + std::vector _allTrkFitBranches; // configurations for all track fit branches // main TTree TTree* _ntuple; TH1I* _hVersion; @@ -224,7 +302,7 @@ namespace mu2e { // general event info branch EventInfo _einfo; EventInfoMC _einfomc; - art::InputTag _recoCountTag, _PBITag, _PBTTag, _EWMTag, _PBTMCTag; + art::InputTag _recoCountTag, _PBITag, _PBTTag, _EWMTag; art::Handle _ewmh; // track control bool _hastrks; @@ -236,73 +314,58 @@ namespace mu2e { // track branches (inputs) std::vector > _allKSPCHs; // track branches (outputs) - std::map> _allTIs; - std::map>> _allTSIs; - std::map>> _allLHIs; - std::map>> _allCHIs; - std::map>> _allKLIs; - - std::map> _allTCHIs; + std::map> _allTIs; + std::map>> _allTSIs; + std::map>> _allLHIs; + std::map>> _allCHIs; + std::map>> _allKLIs; + std::map> _allTCHIs; // quality branches (inputs) - - std::vector > > _allRQCHs; // outer vector is for each track type, inner vector is all RecoQuals + std::vector > > _allRQCHs; std::vector >> _allTrkQualCHs; std::vector >> _allTrkPIDCHs; - // quality branches (outputs) std::vector _allRQIs; - std::map>> _allTrkQualResults; - std::map>> _allTrkPIDResults; - + std::map>> _allTrkQualResults; + std::map>> _allTrkPIDResults; // trigger information unsigned _trigbits; std::map _tmap; // map between path and trigger ID. ID should come from trigger itself FIXME! TrigInfo _triggerResults; - // MC truth (fcl parameters) - bool _fillmc; - // MC steps (associated with KalSeed) + // cached optional tracker MC tags std::vector _extraMCStepTags; std::vector> _extraMCStepCollections; - std::map>> _extraMCStepInfos; - std::map>> _extraMCStepSummaryInfos; - // SurfaceSteps + std::map>> _extraMCStepInfos; + std::map>> _extraMCStepSummaryInfos; art::InputTag _surfaceStepsTag; - std::map>> _surfaceStepInfos; + std::map>> _surfaceStepInfos; art::Handle _surfaceStepsHandle; - // MC steps (all) std::vector _stepPointMCTags; std::vector> _stepPointMCCollections; std::map _stepPointMCInfos; - - // + // MC truth handles art::Handle _pph; art::Handle _ksmcah; art::Handle _simParticles; art::Handle _mcTrajectories; - // MC truth branches (outputs) - std::map> _allMCTIs; - std::map>> _allMCSimTIs; - std::map>> _allMCVDInfos; - bool _fillcalomc; + // tracker MC truth branches (outputs) + std::map> _allMCTIs; + std::map>> _allMCSimTIs; + std::map>> _allMCVDInfos; art::Handle _ccmcch; art::Handle _chmcch; - std::map> _allMCTCHIs; - + std::map> _allMCTCHIs; // hit level info branches - std::map>> _allTSHIs; - std::map>> _allTSHCIs; - std::map>> _allTSMIs; - std::map>> _allTSHIMCs; - + std::map>> _allTSHIs; + std::map>> _allTSHCIs; + std::map>> _allTSMIs; + std::map>> _allTSHIMCs; // time cluster branch art::Handle _tcsHandle; std::vector _tcIs; - bool _filltcs; - // event weights std::vector > _wtHandles; EventWeightInfo _wtinfo; - // Calorimeter art::Handle _caloClusters; art::Handle _caloHits; @@ -314,18 +377,13 @@ namespace mu2e { std::vector _caloRDIs; std::vector _caloDIs; std::vector _caloDigiMCIs; - - bool _fillcaloclusters, _fillcalohits, _fillcalorecodigis, _fillcalodigis, _fillcalodigismc; - // Calorimeter MC - std::vector _caloCIMCs; //Independent from tracker - std::vector _caloHIMCs; //Independent from tracker - std::vector _caloSIMCs; //Sim particle infos associated with calo clusters - std::vector _caloDigiSIMCs; //Sim particle infos associated with calo digis - bool _fillcaloclustersmc, _fillcalohitsmc, _fillcalosiminfos, _fillcalodigisiminfos; - + std::vector _caloCIMCs; + std::vector _caloHIMCs; + std::vector _caloSIMCs; + std::vector _caloDigiSIMCs; // CRV (inputs) - std::map>> _allBestCrvAssns; + std::map>> _allBestCrvAssns; art::Handle _crvMCAssns; art::Handle _crvCoincidences; art::Handle _crvCoincidenceMCs; @@ -333,18 +391,15 @@ namespace mu2e { art::Handle _crvDigiMCs; art::Handle _crvDigis; art::Handle _crvSteps; - // CRV -- fhicl parameters - bool _fillcrvcoincs, _fillcrvpulses, _fillcrvdigis; - double _crvPlaneY; // needs to move to KinKalGeom FIXME // CRV inference bool _fillCrvInference; art::InputTag _crvInferenceTag; std::vector> _crvInference; // CRV (output) std::vector _crvcoincs; - std::map> _allBestCrvs; // there can be more than one of these per track type + std::map> _allBestCrvs; std::vector _crvcoincsmc; - std::map> _allBestCrvMCs; + std::map> _allBestCrvMCs; CrvSummaryReco _crvsummary; CrvSummaryMC _crvsummarymc; std::vector _crvcoincsmcplane; @@ -363,14 +418,41 @@ namespace mu2e { enum FType{Unknown=0,LoopHelix,CentralHelix,KinematicLine}; FType _ftype = Unknown; std::vector fitNames = {"Unknown", "LoopHelix","CentralHelix","KinematicLine"}; - // for trigger branch: bool firstEvent = true; + + // ── Gating helpers ───────────────────────────────────────────────────── + // Event-level MC gate (simParticles, mcTrajectories, primaryParticle). + // Also gates tracker MC and CRV MC. + bool fillEventMC() const { return _conf.mc().fill(); } + + // Tracker MC: both the global event MC gate and the tracker MC gate must + // be true, plus the per-branch flag. + bool fillTrkMC(const TrkFitOptConfig& opt) const { + return _conf.trk().fill() && _conf.mc().fill() && _conf.trk().mc().fill() && opt.fillmc(); + } + + // Calorimeter MC: independent of fillEventMC() — calo MC products do not + // require simParticles/primaryParticle so calo-only jobs can enable this + // while keeping mc.fill false. + bool fillCaloMC() const { return _conf.calo().fill() && _conf.calo().mc().fill(); } + bool fillCaloTrackMatchMC() const { return fillCaloMC() && _conf.calo().mc().fillTrackMatch(); } + bool fillCaloClsMC() const { return fillCaloMC() && _conf.calo().mc().fillClusters(); } + bool fillCaloHitsMC() const { return fillCaloMC() && _conf.calo().mc().fillHits(); } + bool fillCaloSimMC() const { return fillCaloMC() && _conf.calo().mc().fillSim(); } + bool fillCaloDigisMC() const { return fillCaloMC() && _conf.calo().mc().fillDigis(); } + bool fillCaloDigiSimMC() const { return fillCaloMC() && _conf.calo().mc().fillDigiSim(); } + + // CRV MC: needs global event MC gate (CRV uses mcTrajectories/primaryParticle). + bool fillCRVMC() const { + return _conf.mc().fill() && _conf.crv().fill() && _conf.crv().mc().fill(); + } + // helper functions void fillEventInfo(const art::Event& event); - void fillTriggerBranch(const art::Event& event,std::string const& process, bool firstEvent); + void fillTriggerBranch(const art::Event& event, std::string const& process, bool firstEvent); void resetTrackBranches(); - void fillTrackBranches(const art::Handle& kspch, BranchIndex i_branch, size_t i_kseedptr); + void fillTrackBranches(const art::Handle& kspch, TrkFitBranchIndex i_trk_fit_branch, size_t i_kseedptr); template std::vector > createSpecialBranch(const art::Event& event, const std::string& branchname, @@ -385,108 +467,72 @@ namespace mu2e { _PBITag(conf().PBITag()), _PBTTag(conf().PBTTag()), _EWMTag(conf().EWMTag()), - _PBTMCTag(conf().PBTMCTag()), _hastrks(conf().hastrks()), _hascrv(conf().hascrv()), - _fillmc(conf().fillmc()), - _fillcalomc(conf().fillCaloMC()), - _filltcs(conf().fillTimeClusters()), - // Calorimeter - _fillcaloclusters(conf().fillCaloClusters()), - _fillcalohits(conf().fillCaloHits()), - _fillcalorecodigis(conf().fillCaloRecoDigis()), - _fillcalodigis(conf().fillCaloDigis()), - _fillcalodigismc(conf().fillCaloDigisMC()), - // Calorimeter MC - _fillcaloclustersmc(conf().fillCaloClustersMC()), - _fillcalohitsmc(conf().fillCaloHitsMC()), - _fillcalosiminfos(conf().fillCaloSimInfos()), - _fillcalodigisiminfos(conf().fillCaloDigiSimInfos()), - // CRV - _fillcrvcoincs(conf().fillcrvcoincs()), - _fillcrvpulses(conf().fillcrvpulses()), - _fillcrvdigis(conf().fillcrvdigis()), - _crvPlaneY(conf().crvPlaneY()), - _infoMCStructHelper(conf().infoMCStructHelper()), + _infoMCStructHelper(conf().mc().infoMCStructHelper()), _buffsize(conf().buffsize()), _splitlevel(conf().splitlevel()) { - _fillCrvInference = conf().crvInferenceTag(_crvInferenceTag); + _fillCrvInference = conf().crv().inferenceTag(_crvInferenceTag); // decode fit type - for(size_t ifit=0;ifit < fitNames.size();++ifit){ - auto const& fname = fitNames[ifit]; - if(_conf.fittype() == fname){ - _ftype= (FType)ifit; + for(size_t ifit = 0; ifit < fitNames.size(); ++ifit){ + if(_conf.fittype() == fitNames[ifit]){ + _ftype = (FType)ifit; break; } } - // Put all the branch configurations together - for(const auto& branch_cfg : _conf.branches()){ - _allBranches.push_back(branch_cfg); - } - // Create all the info structs - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - auto i_branchConfig = _allBranches.at(i_branch); - _allTIs[i_branch] = std::vector(); - // fit sampling (KalIntersection) at a surface - _allTSIs[i_branch] = std::vector>(); - // fit-specific branches - _allLHIs[i_branch] = std::vector>(); - _allCHIs[i_branch] = std::vector>(); - _allKLIs[i_branch] = std::vector>(); - - // mc truth info at VDs - _allMCVDInfos[i_branch] = std::vector>(); + // resolve optional MC step tags from top-level mcsteps config + _conf.mcsteps().extraMCStepTags(_extraMCStepTags); + _conf.mcsteps().surfaceStepsTag(_surfaceStepsTag); + _conf.mcsteps().stepPointMCTags(_stepPointMCTags); - _allTCHIs[i_branch] = std::vector(); - - _allMCTIs[i_branch] = std::vector(); - _allMCSimTIs[i_branch] = std::vector>(); + // populate branch list from trk.branches + for(const auto& trk_fit_cfg : _conf.trk().fits()){ + _allTrkFitBranches.push_back(trk_fit_cfg); + } - if(_fillcalomc && _fillmc){ - _allMCTCHIs[i_branch] = std::vector(); + // create per-branch storage (skip disabled branches) + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + const auto& i_trkFitConfig = _allTrkFitBranches.at(i_trk_fit_branch); + if (!i_trkFitConfig.enabled()) continue; + + _allTIs[i_trk_fit_branch] = {}; + _allTSIs[i_trk_fit_branch] = {}; + _allLHIs[i_trk_fit_branch] = {}; + _allCHIs[i_trk_fit_branch] = {}; + _allKLIs[i_trk_fit_branch] = {}; + _allMCVDInfos[i_trk_fit_branch] = {}; + _allTCHIs[i_trk_fit_branch] = {}; + _allMCTIs[i_trk_fit_branch] = {}; + _allMCSimTIs[i_trk_fit_branch] = {}; + + if(fillCaloTrackMatchMC()){ + _allMCTCHIs[i_trk_fit_branch] = {}; } - RecoQualInfo rqi; - _allRQIs.push_back(rqi); + _allRQIs.push_back(RecoQualInfo{}); + _allTSHIs[i_trk_fit_branch] = {}; + _allTSHCIs[i_trk_fit_branch] = {}; + _allTSMIs[i_trk_fit_branch] = {}; + _allTSHIMCs[i_trk_fit_branch] = {}; - _allTSHIs[i_branch] = std::vector>(); - _allTSHCIs[i_branch] = std::vector>(); - _allTSMIs[i_branch] = std::vector>(); - _allTSHIMCs[i_branch] = std::vector>(); + _allTrkQualResults[i_trk_fit_branch].resize(i_trkFitConfig.trkQualTags().size()); + _allTrkPIDResults[i_trk_fit_branch].resize(i_trkFitConfig.trkPIDTags().size()); - std::vector> trkQualResults; - for (size_t i_trkQualTag = 0; i_trkQualTag < i_branchConfig.trkQualTags().size(); ++i_trkQualTag) { - trkQualResults.emplace_back(std::vector()); + for (StepCollIndex ixt = 0; ixt < _extraMCStepTags.size(); ++ixt) { + _extraMCStepInfos[i_trk_fit_branch][ixt] = {}; + _extraMCStepSummaryInfos[i_trk_fit_branch][ixt] = {}; } - _allTrkQualResults[i_branch] = trkQualResults; - - std::vector> trkPIDResults; - for (size_t i_trkPIDTag = 0; i_trkPIDTag < i_branchConfig.trkPIDTags().size(); ++i_trkPIDTag) { - trkPIDResults.emplace_back(std::vector()); + if (!_surfaceStepsTag.empty()) { + _surfaceStepInfos[i_trk_fit_branch] = {}; } - _allTrkPIDResults[i_branch] = trkPIDResults; + } - if(_conf.extraMCStepTags(_extraMCStepTags)){ - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - for (StepCollIndex i_extraMCStepTag = 0; i_extraMCStepTag < _extraMCStepTags.size(); ++i_extraMCStepTag) { - _extraMCStepInfos[i_branch][i_extraMCStepTag] = std::vector(); - _extraMCStepSummaryInfos[i_branch][i_extraMCStepTag] = std::vector(); - } - } - } - if(_conf.SurfaceStepsTag(_surfaceStepsTag)){ - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - _surfaceStepInfos[i_branch] = std::vector>(); - } - } - if(_conf.stepPointMCTags(_stepPointMCTags)){ - for (StepCollIndex i_stepPointMCTag = 0; i_stepPointMCTag < _stepPointMCTags.size(); ++i_stepPointMCTag) { - _stepPointMCInfos[i_stepPointMCTag] = MCStepInfos(); - } - } + // global (non-per-branch) MC step storage + for (StepCollIndex icoll = 0; icoll < _stepPointMCTags.size(); ++icoll) { + _stepPointMCInfos[icoll] = MCStepInfos{}; } } @@ -499,136 +545,125 @@ namespace mu2e { _hVersion->GetXaxis()->SetBinLabel(2, "minor"); _hVersion->SetBinContent(2, 11); _hVersion->GetXaxis()->SetBinLabel(3, "patch"); _hVersion->SetBinContent(3, 2); _hProcEvents = tfs->make("n_proc_events", "number of processed events", 1,0,1); - // add event info branch + // event info branch _ntuple->Branch("evtinfo",&_einfo,_buffsize,_splitlevel); - if (_fillmc) { + if (fillEventMC()) { _ntuple->Branch("evtinfomc",&_einfomc,_buffsize,_splitlevel); } // hit counting branch _ntuple->Branch("hitcount",&_hcnt); // track counting branches - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - BranchConfig i_branchConfig = _allBranches.at(i_branch); - std::string leafname = i_branchConfig.branch(); - _ntuple->Branch(("tcnt.n"+leafname).c_str(),&_tcnt._counts[i_branch]); + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + if (!_allTrkFitBranches.at(i_trk_fit_branch).enabled()) continue; + std::string leafname = _allTrkFitBranches.at(i_trk_fit_branch).branchname(); + _ntuple->Branch(("tcnt.n"+leafname).c_str(),&_tcnt._counts[i_trk_fit_branch]); } // create all track branches - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - BranchConfig i_branchConfig = _allBranches.at(i_branch); - std::string branch = i_branchConfig.branch(); - _ntuple->Branch((branch+".").c_str(),&_allTIs.at(i_branch),_buffsize,_splitlevel); - _ntuple->Branch((branch+"segs.").c_str(),&_allTSIs.at(i_branch),_buffsize,_splitlevel); -// add traj-specific branches - if(_ftype == LoopHelix )_ntuple->Branch((branch+"segpars_lh.").c_str(),&_allLHIs.at(i_branch),_buffsize,_splitlevel); - if(_ftype == CentralHelix )_ntuple->Branch((branch+"segpars_ch.").c_str(),&_allCHIs.at(i_branch),_buffsize,_splitlevel); - if(_ftype == KinematicLine )_ntuple->Branch((branch+"segpars_kl.").c_str(),&_allKLIs.at(i_branch),_buffsize,_splitlevel); - // TrkCaloHit: currently only 1 - _ntuple->Branch((branch+"calohit.").c_str(),&_allTCHIs.at(i_branch)); - for (size_t i_trkQualTag = 0; i_trkQualTag < i_branchConfig.trkQualTags().size(); ++i_trkQualTag) { - std::string branchname = "qual"; - if (i_trkQualTag > 0) { - branchname += std::to_string(i_trkQualTag+1); // +1 so that the second trkqual will be "trkqual2" + if (_conf.trk().fill()) { + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + const TrkFitConfig& i_trkFitConfig = _allTrkFitBranches.at(i_trk_fit_branch); + if (!i_trkFitConfig.enabled()) continue; + std::string branch = i_trkFitConfig.branchname(); + _ntuple->Branch((branch+".").c_str(),&_allTIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + _ntuple->Branch((branch+"segs.").c_str(),&_allTSIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + // add traj-specific branches + if(_ftype == LoopHelix) _ntuple->Branch((branch+"segpars_lh.").c_str(),&_allLHIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + if(_ftype == CentralHelix) _ntuple->Branch((branch+"segpars_ch.").c_str(),&_allCHIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + if(_ftype == KinematicLine) _ntuple->Branch((branch+"segpars_kl.").c_str(),&_allKLIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + // TrkCaloHit: currently only 1 + _ntuple->Branch((branch+"calohit.").c_str(),&_allTCHIs.at(i_trk_fit_branch)); + for (size_t i_trkQualTag = 0; i_trkQualTag < i_trkFitConfig.trkQualTags().size(); ++i_trkQualTag) { + std::string branchname = "qual"; + if (i_trkQualTag > 0) branchname += std::to_string(i_trkQualTag+1); + _ntuple->Branch((branch+branchname+".").c_str(),&_allTrkQualResults.at(i_trk_fit_branch).at(i_trkQualTag),_buffsize,_splitlevel); } - _ntuple->Branch((branch+branchname+".").c_str(),&_allTrkQualResults.at(i_branch).at(i_trkQualTag),_buffsize,_splitlevel); - } - for (size_t i_trkPIDTag = 0; i_trkPIDTag < i_branchConfig.trkPIDTags().size(); ++i_trkPIDTag) { - std::string branchname = "pid"; - if (i_trkPIDTag > 0) { - branchname += std::to_string(i_trkPIDTag+1); // +1 so that the second trkpid will be "trkpid2" + for (size_t i_trkPIDTag = 0; i_trkPIDTag < i_trkFitConfig.trkPIDTags().size(); ++i_trkPIDTag) { + std::string branchname = "pid"; + if (i_trkPIDTag > 0) branchname += std::to_string(i_trkPIDTag+1); + _ntuple->Branch((branch+branchname+'.').c_str(),&_allTrkPIDResults.at(i_trk_fit_branch).at(i_trkPIDTag),_buffsize,_splitlevel); } - _ntuple->Branch((branch+branchname+'.').c_str(),&_allTrkPIDResults.at(i_branch).at(i_trkPIDTag),_buffsize,_splitlevel); - } - // optionally add hit-level branches - // (for the time being diagLevel : 2 will still work, but I propose removing this at some point) - if(_conf.diag() > 1 || (_conf.fillhits() && i_branchConfig.options().fillhits())){ - _ntuple->Branch((branch+"hits.").c_str(),&_allTSHIs.at(i_branch),_buffsize,_splitlevel); - if (_conf.fillhitcalibs()) - _ntuple->Branch((branch+"hitcalibs.").c_str(),&_allTSHCIs.at(i_branch),_buffsize,_splitlevel); - _ntuple->Branch((branch+"mats.").c_str(),&_allTSMIs.at(i_branch),_buffsize,_splitlevel); - } - - // optionally add MC branches - if(_fillmc && i_branchConfig.options().fillmc()){ - _ntuple->Branch((branch+"mc.").c_str(),&_allMCTIs.at(i_branch),_buffsize,_splitlevel); - - _ntuple->Branch((branch+"mcsim.").c_str(),&_allMCSimTIs.at(i_branch),_buffsize,_splitlevel); - _ntuple->Branch((branch+"mcvd.").c_str(),&_allMCVDInfos.at(i_branch),_buffsize,_splitlevel); - if(_fillcalomc)_ntuple->Branch((branch+"calohitmc.").c_str(),&_allMCTCHIs.at(i_branch),_buffsize,_splitlevel); - // at hit-level MC information - // (for the time being diagLevel will still work, but I propose removing this at some point) - if(_conf.diag() > 1 || (_conf.fillhits() && i_branchConfig.options().fillhits())){ - _ntuple->Branch((branch+"hitsmc.").c_str(),&_allTSHIMCs.at(i_branch),_buffsize,_splitlevel); + // hit-level branches (global trk.fillHits AND per-branch options.fillHits both required) + if(_conf.diag() > 1 || (_conf.trk().fillHits() && i_trkFitConfig.options().fillhits())){ + _ntuple->Branch((branch+"hits.").c_str(),&_allTSHIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + if (_conf.trk().fillHitCalibs()) + _ntuple->Branch((branch+"hitcalibs.").c_str(),&_allTSHCIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + _ntuple->Branch((branch+"mats.").c_str(),&_allTSMIs.at(i_trk_fit_branch),_buffsize,_splitlevel); } - // configure extra MCStep branches for this track type - if(_conf.extraMCStepTags(_extraMCStepTags)){ - for(size_t ixtra=0;ixtra < _extraMCStepTags.size(); ++ixtra) { + // tracker MC branches + if(fillTrkMC(i_trkFitConfig.options())){ + _ntuple->Branch((branch+"mc.").c_str(),&_allMCTIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + _ntuple->Branch((branch+"mcsim.").c_str(),&_allMCSimTIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + _ntuple->Branch((branch+"mcvd.").c_str(),&_allMCVDInfos.at(i_trk_fit_branch),_buffsize,_splitlevel); + if(fillCaloTrackMatchMC()) + _ntuple->Branch((branch+"calohitmc.").c_str(),&_allMCTCHIs.at(i_trk_fit_branch),_buffsize,_splitlevel); + if(_conf.diag() > 1 || (_conf.trk().fillHits() && i_trkFitConfig.options().fillhits())){ + _ntuple->Branch((branch+"hitsmc.").c_str(),&_allTSHIMCs.at(i_trk_fit_branch),_buffsize,_splitlevel); + } + // extra MCStep branches per track type + for(size_t ixtra = 0; ixtra < _extraMCStepTags.size(); ++ixtra) { auto const& tag = _extraMCStepTags[ixtra]; auto inst = tag.instance(); - std::string mcsiname = branch +"mcsic_" + inst + "."; - std::string mcssiname = branch + "mcssi_" + inst + "."; - _ntuple->Branch(mcsiname.c_str(),&_extraMCStepInfos[i_branch][ixtra],_buffsize,_splitlevel); - _ntuple->Branch(mcssiname.c_str(),&_extraMCStepSummaryInfos[i_branch][ixtra],_buffsize,_splitlevel); + _ntuple->Branch((branch+"mcsic_"+inst+".").c_str(),&_extraMCStepInfos[i_trk_fit_branch][ixtra],_buffsize,_splitlevel); + _ntuple->Branch((branch+"mcssi_"+inst+".").c_str(),&_extraMCStepSummaryInfos[i_trk_fit_branch][ixtra],_buffsize,_splitlevel); + } + if(!_surfaceStepsTag.empty()){ + _ntuple->Branch((branch+"segsmc.").c_str(),&_surfaceStepInfos[i_trk_fit_branch],_buffsize,_splitlevel); } - } - if(_conf.SurfaceStepsTag(_surfaceStepsTag)){ - _ntuple->Branch((branch+"segsmc.").c_str(),&_surfaceStepInfos[i_branch],_buffsize,_splitlevel); } } } // Time clusters - if(_filltcs) { + if(_conf.timeclusters().fill()) { _ntuple->Branch("timeclusters.",&_tcIs,_buffsize,_splitlevel); } - // Calorimeter - if (_fillcaloclusters){ + // ── Calorimeter reco branches ────────────────────────────────────────── + if(_conf.calo().fill() && _conf.calo().fillClusters()){ _ntuple->Branch("caloclusters.",&_caloCIs,_buffsize,_splitlevel); } - if (_fillcalohits){ + if(_conf.calo().fill() && _conf.calo().fillHits()){ _ntuple->Branch("calohits.",&_caloHIs,_buffsize,_splitlevel); } - if (_fillcalorecodigis){ + if(_conf.calo().fill() && _conf.calo().fillRecoDigis()){ _ntuple->Branch("calorecodigis.",&_caloRDIs,_buffsize,_splitlevel); } - if (_fillcalodigis){ + if(_conf.calo().fill() && _conf.calo().fillDigis()){ _ntuple->Branch("calodigis.",&_caloDIs,_buffsize,_splitlevel); } - if (_fillcalodigismc){ + // ── Calorimeter MC branches ──────────────────────────────────────────── + if(fillCaloDigisMC()){ _ntuple->Branch("calodigismc.",&_caloDigiMCIs,_buffsize,_splitlevel); } - if (_fillcalodigisiminfos){ + if(fillCaloDigiSimMC()){ _ntuple->Branch("calodigisim.",&_caloDigiSIMCs,_buffsize,_splitlevel); } - // Calorimeter MC - if (_fillmc) { - if (_fillcaloclustersmc){ - _ntuple->Branch("caloclustersmc.",&_caloCIMCs,_buffsize,_splitlevel); - } - if (_fillcalohitsmc){ - _ntuple->Branch("calohitsmc.",&_caloHIMCs,_buffsize,_splitlevel); - } - if (_fillcalosiminfos){ - _ntuple->Branch("calomcsim.",&_caloSIMCs,_buffsize,_splitlevel); - } + if(fillCaloClsMC()){ + _ntuple->Branch("caloclustersmc.",&_caloCIMCs,_buffsize,_splitlevel); + } + if(fillCaloHitsMC()){ + _ntuple->Branch("calohitsmc.",&_caloHIMCs,_buffsize,_splitlevel); + } + if(fillCaloSimMC()){ + _ntuple->Branch("calomcsim.",&_caloSIMCs,_buffsize,_splitlevel); } - // general CRV info - if(_fillcrvcoincs) { + // ── CRV reco branches ────────────────────────────────────────────────── + if(_conf.crv().fill() && _conf.crv().fillCoincs()) { _ntuple->Branch("crvsummary",&_crvsummary,_buffsize,_splitlevel); _ntuple->Branch("crvcoincs.",&_crvcoincs,_buffsize,_splitlevel); - if(_fillmc){ + if(fillCRVMC()){ _ntuple->Branch("crvsummarymc",&_crvsummarymc,_buffsize,_splitlevel); _ntuple->Branch("crvcoincsmc.",&_crvcoincsmc,_buffsize,_splitlevel); _ntuple->Branch("crvcoincsmcplane.",&_crvcoincsmcplane,_buffsize,_splitlevel); } } - if(_fillcrvpulses) { + if(_conf.crv().fill() && _conf.crv().fillPulses()) { _ntuple->Branch("crvpulses.",&_crvpulses,_buffsize,_splitlevel); - if(_fillmc) _ntuple->Branch("crvpulsesmc.",&_crvpulsesmc,_buffsize,_splitlevel); + if(fillCRVMC()) _ntuple->Branch("crvpulsesmc.",&_crvpulsesmc,_buffsize,_splitlevel); } - if(_fillcrvdigis) { + if(_conf.crv().fill() && _conf.crv().fillDigis()) { _ntuple->Branch("crvdigis.",&_crvdigis,_buffsize,_splitlevel); } // CRV cosmic inference @@ -636,23 +671,19 @@ namespace mu2e { _ntuple->Branch("crvcosmic.",&_crvInference,_buffsize,_splitlevel); } // helix info - if(_conf.helices()) _ntuple->Branch("helices.",&_hinfos,_buffsize,_splitlevel); - - // all MC information - if (_fillmc) { - if(_conf.stepPointMCTags(_stepPointMCTags)){ - for(size_t icoll=0;icoll < _stepPointMCTags.size(); ++icoll) { - auto const& tag = _stepPointMCTags[icoll]; - auto inst = tag.instance(); - std::string branchname = "mcsteps_" + inst + "."; - _ntuple->Branch(branchname.c_str(),&_stepPointMCInfos[icoll],_buffsize,_splitlevel); - } + if(_conf.helices().fill()) _ntuple->Branch("helices.",&_hinfos,_buffsize,_splitlevel); + + // global MC step branches (all steps, not per-track) + if(fillEventMC()){ + for(size_t icoll = 0; icoll < _stepPointMCTags.size(); ++icoll) { + auto const& tag = _stepPointMCTags[icoll]; + auto inst = tag.instance(); + _ntuple->Branch(("mcsteps_"+inst+".").c_str(),&_stepPointMCInfos[icoll],_buffsize,_splitlevel); } } } void EventNtupleMaker::beginSubRun(const art::SubRun & subrun ) { - // get bfield _infoStructHelper.updateSubRun(); } @@ -668,13 +699,13 @@ namespace mu2e { _hinfos.clear(); // update timing maps for MC - if(_fillmc){ + if(fillEventMC()){ _infoMCStructHelper.updateEvent(event); } // fill event level info fillEventInfo(event); - // need to create and define the event weight branch here because we only now know the EventWeight creating modules that have been run through the Event + // create event weight branch std::vector > eventWeightHandles; _wtHandles = createSpecialBranch(event, "evtwt", eventWeightHandles, _wtinfo, _wtinfo._weights, false); @@ -686,47 +717,54 @@ namespace mu2e { _allTrkPIDCHs.clear(); art::Handle khaH; - if(_conf.helices()){ // find associated Helices - BranchConfig i_branchConfig = _allBranches.at(0); - art::InputTag kalSeedInputTag = i_branchConfig.input(); + if(_conf.helices().fill()){ + TrkFitConfig i_trkFitConfig = _allTrkFitBranches.at(0); + art::InputTag kalSeedInputTag = i_trkFitConfig.input(); event.getByLabel(kalSeedInputTag,khaH); } - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - BranchConfig i_branchConfig = _allBranches.at(i_branch); - art::Handle kalSeedPtrCollHandle; - art::InputTag kalSeedPtrInputTag = i_branchConfig.input(); - event.getByLabel(kalSeedPtrInputTag,kalSeedPtrCollHandle); - _allKSPCHs.push_back(kalSeedPtrCollHandle); + if (_conf.trk().fill()) { + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + TrkFitConfig i_trkFitConfig = _allTrkFitBranches.at(i_trk_fit_branch); + if (!i_trkFitConfig.enabled()) { + // push empty placeholders to keep index alignment + _allKSPCHs.push_back(art::Handle()); + _allTrkQualCHs.emplace_back(); + _allRQCHs.push_back({}); + _allTrkPIDCHs.emplace_back(); + continue; + } + art::Handle kalSeedPtrCollHandle; + art::InputTag kalSeedPtrInputTag = i_trkFitConfig.input(); + event.getByLabel(kalSeedPtrInputTag,kalSeedPtrCollHandle); + _allKSPCHs.push_back(kalSeedPtrCollHandle); + + std::vector> trkQualCollHandles; + for (const auto& i_trkQualTag : i_trkFitConfig.trkQualTags()) { + art::Handle trkQualCollHandle; + event.getByLabel(i_trkQualTag,trkQualCollHandle); + trkQualCollHandles.push_back(trkQualCollHandle); + } + _allTrkQualCHs.emplace_back(trkQualCollHandles); + + std::vector > recoQualCollHandles; + std::vector > selectedRQCHs; + selectedRQCHs = createSpecialBranch(event, i_trkFitConfig.branchname()+"qual", recoQualCollHandles, _allRQIs.at(i_trk_fit_branch), _allRQIs.at(i_trk_fit_branch)._qualsAndCalibs, true); + for (const auto& i_selectedRQCH : selectedRQCHs) { + if (i_selectedRQCH->size() != kalSeedPtrCollHandle->size()) { + throw cet::exception("TrkAna") << "Sizes of KalSeedPtrCollection and this RecoQualCollection are inconsistent (" << kalSeedPtrCollHandle->size() << " and " << i_selectedRQCH->size() << " respectively)"; + } + } + _allRQCHs.push_back(selectedRQCHs); - std::vector> trkQualCollHandles; - for (const auto& i_trkQualTag : i_branchConfig.trkQualTags()) { - art::Handle trkQualCollHandle; - event.getByLabel(i_trkQualTag,trkQualCollHandle); - trkQualCollHandles.push_back(trkQualCollHandle); - } - _allTrkQualCHs.emplace_back(trkQualCollHandles); - - // also create the reco qual branches - std::vector > recoQualCollHandles; - std::vector > selectedRQCHs; - selectedRQCHs = createSpecialBranch(event, i_branchConfig.branch()+"qual", recoQualCollHandles, _allRQIs.at(i_branch), _allRQIs.at(i_branch)._qualsAndCalibs, true); - for (const auto& i_selectedRQCH : selectedRQCHs) { - if (i_selectedRQCH->size() != kalSeedPtrCollHandle->size()) { - throw cet::exception("TrkAna") << "Sizes of KalSeedPtrCollection and this RecoQualCollection are inconsistent (" << kalSeedPtrCollHandle->size() << " and " << i_selectedRQCH->size() << " respectively)"; + std::vector> trkPIDCollHandles; + for (const auto& i_trkPIDTag : i_trkFitConfig.trkPIDTags()) { + art::Handle trkPIDCollHandle; + event.getByLabel(i_trkPIDTag,trkPIDCollHandle); + trkPIDCollHandles.push_back(trkPIDCollHandle); } + _allTrkPIDCHs.emplace_back(trkPIDCollHandles); } - _allRQCHs.push_back(selectedRQCHs); - - // TrkCaloHitPID - std::vector> trkPIDCollHandles; - for (const auto& i_trkPIDTag : i_branchConfig.trkPIDTags()) { - art::Handle trkPIDCollHandle; - event.getByLabel(i_trkPIDTag,trkPIDCollHandle); - trkPIDCollHandles.push_back(trkPIDCollHandle); - } - _allTrkPIDCHs.emplace_back(trkPIDCollHandles); - } // trigger information @@ -735,102 +773,105 @@ namespace mu2e { firstEvent=false; } - // MC data - if(_fillmc) { // get MC product collections - event.getByLabel(_conf.primaryParticleTag(),_pph); - event.getByLabel(_conf.kalSeedMCTag(),_ksmcah); - event.getByLabel(_conf.simParticlesTag(),_simParticles); - event.getByLabel(_conf.mcTrajectoriesTag(),_mcTrajectories); - if(_fillcalomc || _fillcaloclustersmc)event.getByLabel(_conf.caloClusterMCTag(),_ccmcch); - if(_fillcalohitsmc)event.getByLabel(_conf.caloHitMCTag(),_chmcch); + // load event-level MC products (tracker + CRV MC) + if(fillEventMC()) { + event.getByLabel(_conf.mc().primaryParticleTag(),_pph); + event.getByLabel(_conf.trk().mc().kalSeedMCAssns(),_ksmcah); + event.getByLabel(_conf.mc().simParticlesTag(),_simParticles); + event.getByLabel(_conf.mc().mcTrajectoriesTag(),_mcTrajectories); + } + // load calo MC products (independent of fillEventMC) + if(fillCaloClsMC() || fillCaloTrackMatchMC()){ + event.getByLabel(_conf.calo().mc().clusterMCTag(),_ccmcch); + } + if(fillCaloHitsMC()){ + event.getByLabel(_conf.calo().mc().hitMCTag(),_chmcch); } + // fill track counts - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - _tcnt._counts[i_branch] = (_allKSPCHs.at(i_branch))->size(); + if (_conf.trk().fill()) { + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + if (!_allTrkFitBranches.at(i_trk_fit_branch).enabled()) continue; + _tcnt._counts[i_trk_fit_branch] = (_allKSPCHs.at(i_trk_fit_branch))->size(); + } } // find extra MCStep collections _extraMCStepCollections.clear(); for(size_t ixt = 0; ixt < _extraMCStepTags.size(); ixt++){ - auto const& tag = _extraMCStepTags[ixt]; art::Handle mcstepch; - event.getByLabel(tag,mcstepch); + event.getByLabel(_extraMCStepTags[ixt],mcstepch); _extraMCStepCollections.push_back(mcstepch); } event.getByLabel(_surfaceStepsTag,_surfaceStepsHandle); - // find extra MCStep collections + // find global MCStep collections _stepPointMCCollections.clear(); for(size_t icoll = 0; icoll < _stepPointMCTags.size(); icoll++){ - auto const& tag = _stepPointMCTags[icoll]; art::Handle mcstepch; - event.getByLabel(tag,mcstepch); + event.getByLabel(_stepPointMCTags[icoll],mcstepch); _stepPointMCCollections.push_back(mcstepch); } - // loop through all track types unsigned ntrks(0); - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - BranchConfig i_branchConfig = _allBranches.at(i_branch); - - _allTIs.at(i_branch).clear(); - _allTSIs.at(i_branch).clear(); - _allLHIs.at(i_branch).clear(); - _allCHIs.at(i_branch).clear(); - _allKLIs.at(i_branch).clear(); - _allTCHIs.at(i_branch).clear(); - - _allTSHIs.at(i_branch).clear(); - _allTSHCIs.at(i_branch).clear(); - _allTSMIs.at(i_branch).clear(); - _allTSHIMCs.at(i_branch).clear(); - - _allMCTIs.at(i_branch).clear(); - _allMCVDInfos.at(i_branch).clear(); - _allMCSimTIs.at(i_branch).clear(); - - for (size_t i_trkQualTag = 0; i_trkQualTag < i_branchConfig.trkQualTags().size(); ++i_trkQualTag) { - _allTrkQualResults.at(i_branch).at(i_trkQualTag).clear(); + for (TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + TrkFitConfig i_trkFitConfig = _allTrkFitBranches.at(i_trk_fit_branch); + if (!i_trkFitConfig.enabled()) continue; + + _allTIs.at(i_trk_fit_branch).clear(); + _allTSIs.at(i_trk_fit_branch).clear(); + _allLHIs.at(i_trk_fit_branch).clear(); + _allCHIs.at(i_trk_fit_branch).clear(); + _allKLIs.at(i_trk_fit_branch).clear(); + _allTCHIs.at(i_trk_fit_branch).clear(); + + _allTSHIs.at(i_trk_fit_branch).clear(); + _allTSHCIs.at(i_trk_fit_branch).clear(); + _allTSMIs.at(i_trk_fit_branch).clear(); + _allTSHIMCs.at(i_trk_fit_branch).clear(); + + _allMCTIs.at(i_trk_fit_branch).clear(); + _allMCVDInfos.at(i_trk_fit_branch).clear(); + _allMCSimTIs.at(i_trk_fit_branch).clear(); + + for (size_t i_trkQualTag = 0; i_trkQualTag < i_trkFitConfig.trkQualTags().size(); ++i_trkQualTag) { + _allTrkQualResults.at(i_trk_fit_branch).at(i_trkQualTag).clear(); } - - for (size_t i_trkPIDTag = 0; i_trkPIDTag < i_branchConfig.trkPIDTags().size(); ++i_trkPIDTag) { - _allTrkPIDResults.at(i_branch).at(i_trkPIDTag).clear(); + for (size_t i_trkPIDTag = 0; i_trkPIDTag < i_trkFitConfig.trkPIDTags().size(); ++i_trkPIDTag) { + _allTrkPIDResults.at(i_trk_fit_branch).at(i_trkPIDTag).clear(); } - - for (StepCollIndex i_extraMCStepTag = 0; i_extraMCStepTag < _extraMCStepTags.size(); ++i_extraMCStepTag) { - _extraMCStepInfos.at(i_branch).at(i_extraMCStepTag).clear(); - _extraMCStepSummaryInfos.at(i_branch).at(i_extraMCStepTag).clear(); + for (StepCollIndex ixt = 0; ixt < _extraMCStepTags.size(); ++ixt) { + _extraMCStepInfos.at(i_trk_fit_branch).at(ixt).clear(); + _extraMCStepSummaryInfos.at(i_trk_fit_branch).at(ixt).clear(); } - _surfaceStepInfos.at(i_branch).clear(); - for (StepCollIndex i_stepPointMCTag = 0; i_stepPointMCTag < _stepPointMCTags.size(); ++i_stepPointMCTag) { - _stepPointMCInfos.at(i_stepPointMCTag).clear(); + _surfaceStepInfos.at(i_trk_fit_branch).clear(); + for (StepCollIndex icoll = 0; icoll < _stepPointMCTags.size(); ++icoll) { + _stepPointMCInfos.at(icoll).clear(); } - - if(_fillcalomc) { _allMCTCHIs.at(i_branch).clear(); } - - const auto& kseedptr_coll_h = _allKSPCHs.at(i_branch); - const auto& kseedptr_coll = *kseedptr_coll_h; - for (size_t i_kseedptr = 0; i_kseedptr < kseedptr_coll.size(); ++i_kseedptr) { - resetTrackBranches(); // reset track branches here so that we don't get information from previous tracks in the next entry - - fillTrackBranches(kseedptr_coll_h, i_branch, i_kseedptr); // fill the info structs for this track - if(_conf.helices()){ - auto const& khassns = khaH.product(); - // find the associated HelixSeed to this KalSeed using the assns. - auto hptr = (*khassns)[i_kseedptr].second; - _infoStructHelper.fillHelixInfo(hptr, _hinfos); + if(fillCaloTrackMatchMC()) { _allMCTCHIs.at(i_trk_fit_branch).clear(); } + + if (_conf.trk().fill()) { + const auto& kseedptr_coll_h = _allKSPCHs.at(i_trk_fit_branch); + const auto& kseedptr_coll = *kseedptr_coll_h; + for (size_t i_kseedptr = 0; i_kseedptr < kseedptr_coll.size(); ++i_kseedptr) { + resetTrackBranches(); + fillTrackBranches(kseedptr_coll_h, i_trk_fit_branch, i_kseedptr); + if(_conf.helices().fill()){ + auto const& khassns = khaH.product(); + auto hptr = (*khassns)[i_kseedptr].second; + _infoStructHelper.fillHelixInfo(hptr, _hinfos); + } + ntrks++; } - ntrks++; // count total # of tracks } } // Time clusters - if(_filltcs) { + if(_conf.timeclusters().fill()) { _tcIs.clear(); - //Get the clusters - event.getByLabel(_conf.timeClustersTag(),_tcsHandle); + event.getByLabel(_conf.timeclusters().tag(),_tcsHandle); if(_tcsHandle.isValid()) { for(const auto& tc : *(_tcsHandle)) { _infoStructHelper.fillTimeClusterInfo(tc, _tcIs); @@ -838,299 +879,237 @@ namespace mu2e { } } - // Calorimeter - // Clear lists for this event - if (_fillcaloclustersmc) { _caloCIMCs.clear(); } - if (_fillcalohitsmc) { _caloHIMCs.clear(); } - if (_fillcalosiminfos) { _caloSIMCs.clear(); } - if (_fillcalodigisiminfos) { _caloDigiSIMCs.clear(); } - if (_fillcalodigismc) { _caloDigiMCIs.clear(); } - if (_fillcaloclusters) { _caloCIs.clear(); } - if (_fillcalohits) { _caloHIs.clear(); } - if (_fillcalorecodigis) { _caloRDIs.clear(); } - if (_fillcalodigis) { _caloDIs.clear(); } - - // Fill Calo MC - - // Retrieve CaloShowerSim collection for both caloDigisMC and caloDigiSimInfos branches - if (_fillcalodigismc || _fillcalodigisiminfos) { - event.getByLabel(_conf.caloShowerSimTag(),_caloShowerSim); + // ── Calorimeter ──────────────────────────────────────────────────────── + if(fillCaloClsMC()) { _caloCIMCs.clear(); } + if(fillCaloHitsMC()) { _caloHIMCs.clear(); } + if(fillCaloSimMC()) { _caloSIMCs.clear(); } + if(fillCaloDigiSimMC()){ _caloDigiSIMCs.clear(); } + if(fillCaloDigisMC()) { _caloDigiMCIs.clear(); } + if(_conf.calo().fill() && _conf.calo().fillClusters()) { _caloCIs.clear(); } + if(_conf.calo().fill() && _conf.calo().fillHits()) { _caloHIs.clear(); } + if(_conf.calo().fill() && _conf.calo().fillRecoDigis()){ _caloRDIs.clear(); } + if(_conf.calo().fill() && _conf.calo().fillDigis()) { _caloDIs.clear(); } + + // Calo MC digi branches (need showerSim) + if(fillCaloDigisMC() || fillCaloDigiSimMC()) { + event.getByLabel(_conf.calo().mc().showerSimTag(),_caloShowerSim); } - - // Fill Calo MC Digis branch - if (_fillcalodigismc){ - if (_caloShowerSim.isValid()){ - for (const auto& showerSim : *_caloShowerSim.product()){ + if(fillCaloDigisMC()){ + if(_caloShowerSim.isValid()){ + for(const auto& showerSim : *_caloShowerSim.product()){ _infoMCStructHelper.fillCaloDigiMCInfo(showerSim,_caloDigiMCIs); } } } - - // Fill Calo MC Hits branch - if (_fillcalohitsmc){ - for (const auto& hitmc : *_chmcch.product()){ + if(fillCaloHitsMC()){ + for(const auto& hitmc : *_chmcch.product()){ _infoMCStructHelper.fillCaloHitInfoMC(hitmc,_caloHIMCs); } } - - // Fill Calo MC Clusters branch - if (_fillcaloclustersmc){ - for (const auto& clustermc : *_ccmcch.product()){ - + if(fillCaloClsMC()){ + for(const auto& clustermc : *_ccmcch.product()){ _infoMCStructHelper.fillCaloClusterInfoMC(clustermc,_caloCIMCs); - - //Find and link the existing hits - if (_fillcalohitsmc){ - for (const auto& hitmc : clustermc.caloHitMCs()){ - for (uint hitIdx=0; hitIdx < _caloHIMCs.size(); hitIdx++){ - - if (hitmc && _caloHIMCs[hitIdx] == *hitmc){ - //Update the hit and cluster indexes + if(fillCaloHitsMC()){ + for(const auto& hitmc : clustermc.caloHitMCs()){ + for(uint hitIdx = 0; hitIdx < _caloHIMCs.size(); hitIdx++){ + if(hitmc && _caloHIMCs[hitIdx] == *hitmc){ _caloHIMCs[hitIdx].clusterIdx_ = _caloCIMCs.size()-1; _caloCIMCs.back().hits_.push_back(hitIdx); break; } } } - if (int(_caloCIMCs.back().hits_.size()) != _caloCIMCs.back().nhits){ + if(int(_caloCIMCs.back().hits_.size()) != _caloCIMCs.back().nhits){ throw cet::exception("EventNtuple") << "Could not find one or all CaloHitMCs linked to CaloClusterMC " << _caloCIMCs.size()-1 << "\n"; } } } } - - if (_fillcalosiminfos){ - for (const auto& clustermc : *_ccmcch.product()){ + if(fillCaloSimMC()){ + for(const auto& clustermc : *_ccmcch.product()){ _infoMCStructHelper.fillCaloSimInfos(clustermc,_caloSIMCs); } } - - if (_fillcalodigisiminfos){ - if (_caloShowerSim.isValid()){ - for (const auto& showerSim : *_caloShowerSim.product()){ + if(fillCaloDigiSimMC()){ + if(_caloShowerSim.isValid()){ + for(const auto& showerSim : *_caloShowerSim.product()){ _infoMCStructHelper.fillCaloDigiSimInfos(showerSim,_caloDigiSIMCs); } } } // Calorimeter reco branches - - if (_fillcalodigis){ - //Get the digis - event.getByLabel(_conf.caloDigisTag(),_caloDigis); - for (const auto& digi : *_caloDigis.product()){ + if(_conf.calo().fill() && _conf.calo().fillDigis()){ + event.getByLabel(_conf.calo().digisTag(),_caloDigis); + for(const auto& digi : *_caloDigis.product()){ _infoStructHelper.fillCaloDigiInfo(digi,_caloDIs); } } - - if (_fillcalorecodigis){ - //Get the recodigis - event.getByLabel(_conf.caloRecoDigisTag(),_caloRecoDigis); - for (const auto& recodigi : *_caloRecoDigis.product()){ - + if(_conf.calo().fill() && _conf.calo().fillRecoDigis()){ + event.getByLabel(_conf.calo().recoDigisTag(),_caloRecoDigis); + for(const auto& recodigi : *_caloRecoDigis.product()){ _infoStructHelper.fillCaloRecoDigiInfo(recodigi,_caloRDIs); - - //Find and link the existing digis - if (_fillcalodigis){ + if(_conf.calo().fillDigis()){ const auto& digi = recodigi.caloDigiPtr(); - for (uint digiIdx=0; digiIdx < _caloDIs.size(); digiIdx++){ - - if (_caloDIs[digiIdx] == *digi){ - //Update the digi and recodigi indexes + for(uint digiIdx = 0; digiIdx < _caloDIs.size(); digiIdx++){ + if(_caloDIs[digiIdx] == *digi){ _caloDIs[digiIdx].caloRecoDigiIdx_ = _caloRDIs.size()-1; _caloRDIs.back().caloDigiIdx_ = digiIdx; break; } } - if (_caloRDIs.back().caloDigiIdx_ < 0){ + if(_caloRDIs.back().caloDigiIdx_ < 0){ throw cet::exception("EventNtuple") << "Could not find CaloDigi linked to CaloRecoDigi " << _caloRDIs.size()-1 << "\n"; } } } } - - if (_fillcalohits){ - //Get the hits - event.getByLabel(_conf.caloHitsTag(),_caloHits); - for (const auto& hit : *_caloHits.product()){ - + if(_conf.calo().fill() && _conf.calo().fillHits()){ + event.getByLabel(_conf.calo().hitsTag(),_caloHits); + for(const auto& hit : *_caloHits.product()){ _infoStructHelper.fillCaloHitInfo(hit,_caloHIs); - - //Find and link the existing recodigis - if (_fillcalorecodigis){ - for (const auto& recodigi : hit.recoCaloDigis()){ - for (uint recoDigiIdx=0; recoDigiIdx < _caloRDIs.size(); recoDigiIdx++){ - - if (_caloRDIs[recoDigiIdx] == *recodigi){ - //Update the recodigi and hit indexes + if(_conf.calo().fillRecoDigis()){ + for(const auto& recodigi : hit.recoCaloDigis()){ + for(uint recoDigiIdx = 0; recoDigiIdx < _caloRDIs.size(); recoDigiIdx++){ + if(_caloRDIs[recoDigiIdx] == *recodigi){ _caloRDIs[recoDigiIdx].caloHitIdx_ = _caloHIs.size()-1; _caloHIs.back().recoDigis_.push_back(recoDigiIdx); break; } } } - if (int(_caloHIs.back().recoDigis_.size()) != _caloHIs.back().nSiPMs_){ + if(int(_caloHIs.back().recoDigis_.size()) != _caloHIs.back().nSiPMs_){ throw cet::exception("EventNtuple") << "Could not find one or all CaloRecoDigi linked to CaloHit " << _caloHIs.size()-1 << "\n"; } } } } - - if (_fillcaloclusters){ - //Get the clusters - event.getByLabel(_conf.caloClustersTag(),_caloClusters); - for (const auto& cluster : *_caloClusters.product()){ - + if(_conf.calo().fill() && _conf.calo().fillClusters()){ + event.getByLabel(_conf.calo().clustersTag(),_caloClusters); + for(const auto& cluster : *_caloClusters.product()){ _infoStructHelper.fillCaloClusterInfo(cluster,_caloCIs); - - //Find and link the existing hits - if (_fillcalohits){ - for (const auto& hit : cluster.caloHitsPtrVector()){ - for (uint hitIdx=0; hitIdx < _caloHIs.size(); hitIdx++){ - - if (_caloHIs[hitIdx] == *hit){ - //Update the hit and cluster indexes + if(_conf.calo().fillHits()){ + for(const auto& hit : cluster.caloHitsPtrVector()){ + for(uint hitIdx = 0; hitIdx < _caloHIs.size(); hitIdx++){ + if(_caloHIs[hitIdx] == *hit){ _caloHIs[hitIdx].clusterIdx_ = _caloCIs.size()-1; _caloCIs.back().hits_.push_back(hitIdx); break; } } } - if (_caloCIs.back().hits_.size() != _caloCIs.back().size_){ + if(_caloCIs.back().hits_.size() != _caloCIs.back().size_){ throw cet::exception("EventNtuple") << "Could not find one or all CaloHits linked to CaloCluster " << _caloCIs.size()-1 << "\n"; } } } } - // fill CRV info - if(_fillcrvcoincs){ + // ── CRV ─────────────────────────────────────────────────────────────── + if(_conf.crv().fill() && _conf.crv().fillCoincs()){ _crvcoincs.clear(); _crvcoincsmc.clear(); _crvcoincsmcplane.clear(); - - event.getByLabel(_conf.crvCoincidencesTag(),_crvCoincidences); - event.getByLabel(_conf.crvRecoPulsesTag(),_crvRecoPulses); - event.getByLabel(_conf.crvStepsTag(),_crvSteps); - if(_fillmc) event.getByLabel(_conf.crvCoincidenceMCsTag(),_crvCoincidenceMCs); + event.getByLabel(_conf.crv().coincidencesTag(),_crvCoincidences); + event.getByLabel(_conf.crv().recoPulsesTag(),_crvRecoPulses); + event.getByLabel(_conf.crv().stepsTag(),_crvSteps); + if(fillCRVMC()) event.getByLabel(_conf.crv().mc().coincidenceMCsTag(),_crvCoincidenceMCs); _crvHelper.FillCrvHitInfoCollections( - _crvCoincidences, _crvCoincidenceMCs, - _crvRecoPulses, _crvSteps, _mcTrajectories,_crvcoincs, _crvcoincsmc, - _crvsummary, _crvsummarymc, _crvcoincsmcplane, _crvPlaneY, _pph); + _crvCoincidences, _crvCoincidenceMCs, + _crvRecoPulses, _crvSteps, _mcTrajectories, _crvcoincs, _crvcoincsmc, + _crvsummary, _crvsummarymc, _crvcoincsmcplane, _conf.crv().planeY(), _pph); } - if(_fillcrvpulses){ + if(_conf.crv().fill() && _conf.crv().fillPulses()){ _crvpulses.clear(); _crvpulsesmc.clear(); - event.getByLabel(_conf.crvRecoPulsesTag(),_crvRecoPulses); - if(_fillmc) event.getByLabel(_conf.crvDigiMCsTag(),_crvDigiMCs); + event.getByLabel(_conf.crv().recoPulsesTag(),_crvRecoPulses); + if(fillCRVMC()) event.getByLabel(_conf.crv().mc().digiMCsTag(),_crvDigiMCs); _crvHelper.FillCrvPulseInfoCollections(_crvRecoPulses, _crvDigiMCs, _ewmh, - _crvpulses, _crvpulsesmc); + _crvpulses, _crvpulsesmc); } - if(_fillcrvdigis){ + if(_conf.crv().fill() && _conf.crv().fillDigis()){ _crvdigis.clear(); - event.getByLabel(_conf.crvDigisTag(),_crvDigis); + event.getByLabel(_conf.crv().digisTag(),_crvDigis); _crvHelper.FillCrvDigiInfoCollections(_crvDigis,_crvdigis); } - // fill CRV cosmic inference scores (T x C structure aligned with trk branches) + // CRV cosmic inference if(_fillCrvInference) { _crvInference.clear(); - // Init associations from CrvInference module: KalSeed <-> CrvCoincidenceCluster with MVAResult data product art::Handle> crvInfHandle; - // grab the associations from the art::Event event.getByLabel(_crvInferenceTag, crvInfHandle); - // build a map from KalSeed ptr to scores std::map, std::vector> crvInfMap; - // Get the scores from the associations and fill the map if(crvInfHandle.isValid()) { - // loop through association for(const auto& assn : *crvInfHandle) { MVAResultInfo info; - info.result = assn.data->_value; // the model score - info.valid = true; // true if assns is valid + info.result = assn.data->_value; + info.valid = true; crvInfMap[assn.first].push_back(info); } } - // fill the branch - // iterate over all tracks to keep alignment with trk branches - // this means filling empty vectors for tracks with no coincidence const auto& kseedptr_coll = *_allKSPCHs.at(0); for(size_t i = 0; i < kseedptr_coll.size(); ++i) { auto it = crvInfMap.find(kseedptr_coll[i]); if(it != crvInfMap.end()) { - _crvInference.push_back(it->second); // fill + _crvInference.push_back(it->second); } else { - _crvInference.emplace_back(); // empty vector for tracks with no CRV match + _crvInference.emplace_back(); } } } - // fill MC information unrelated to any reconstructed object - if (_fillmc) { - // fill MCStepCollection branch + // global MC step branches (not per-track) + if(fillEventMC()) { for(size_t icoll = 0; icoll < _stepPointMCTags.size(); icoll++){ auto const& mcsteps = *_stepPointMCCollections[icoll]; - auto& mcstepinfos = _stepPointMCInfos[icoll]; - _infoMCStructHelper.fillStepPointMCInfo(mcsteps, mcstepinfos); + _infoMCStructHelper.fillStepPointMCInfo(mcsteps, _stepPointMCInfos[icoll]); } } // fill this row in the TTree - bool fill = true; // default to fliling event - if(_hastrks && ntrks == 0) { // if we require tracks (_hastrks) but we have none, don't write - fill = false; - } - if (_hascrv && _crvsummary.totalPEs == 0) { // if we require CRV but we have none, don't write - fill = false; - } - if (fill) { + bool fill = true; + if(_hastrks && ntrks == 0) fill = false; + if(_hascrv && _crvsummary.totalPEs == 0) fill = false; + if(fill) { _ntuple->Fill(); } - _hProcEvents->Fill(0); // this is the number of events we have processed, which may be different to the number of events stored in the ntuple + _hProcEvents->Fill(0); } void EventNtupleMaker::fillEventInfo( const art::Event& event) { - // fill basic event information - _einfo.event = event.event(); - _einfo.run = event.run(); + _einfo.event = event.event(); + _einfo.run = event.run(); _einfo.subrun = event.subRun(); - if (_recoCountTag != "") { + if(_recoCountTag != "") { art::Handle recoCountHandle; if(event.getByLabel(_recoCountTag, recoCountHandle)) { - auto recoCount = *recoCountHandle; - _infoStructHelper.fillHitCount(recoCount, _hcnt); + _infoStructHelper.fillHitCount(*recoCountHandle, _hcnt); } } - // currently no reco nproton estimate TODO - if (_PBTTag != "") { + if(_PBTTag != "") { auto PBThandle = event.getValidHandle(_PBTTag); - auto PBT = *PBThandle; - _einfo.pbtime = PBT.pbtime_; - _einfo.pbterr = PBT.pbterr_; + _einfo.pbtime = PBThandle->pbtime_; + _einfo.pbterr = PBThandle->pbterr_; } - if (_EWMTag != "") { + if(_EWMTag != "") { event.getByLabel(_EWMTag, _ewmh); } - if (_fillmc) { - auto PBTMChandle = event.getValidHandle(_PBTMCTag); - auto PBTMC = *PBTMChandle; - _einfomc.pbtime = PBTMC.pbtime_; + if(fillEventMC()) { + auto PBTMChandle = event.getValidHandle(_conf.mc().PBTMCTag()); + _einfomc.pbtime = PBTMChandle->pbtime_; auto PBIhandle = event.getValidHandle(_PBITag); - auto PBI = *PBIhandle; - _einfomc.nprotons = PBI.intensity(); + _einfomc.nprotons = PBIhandle->intensity(); } - // get event weight products std::vector weights; - for (const auto& i_weightHandle : _wtHandles) { - double weight = i_weightHandle->weight(); - weights.push_back(weight); + for(const auto& i_weightHandle : _wtHandles) { + weights.push_back(i_weightHandle->weight()); } _wtinfo.setWeights(weights); } @@ -1141,48 +1120,39 @@ namespace mu2e { const art::TriggerResults* trigResults = trigResultsH.product(); TriggerResultsNavigator tnav(trigResults); - if (firstEvent) { - if (tnav.getTrigPaths().size() > TrigInfo::ntrig_) { + if(firstEvent) { + if(tnav.getTrigPaths().size() > TrigInfo::ntrig_) { throw cet::exception("EventNtuple") << "More trigger paths in TriggerResultsNavigator than maximum allowed by TrigInfo::ntrig_. Increase TrigInfo::ntrig_ and rebuild\n"; } - for (unsigned int i = 0; i < tnav.getTrigPaths().size(); ++i) { - const std::string name = "trig_"+tnav.getTrigPathNameByIndex(i); - _ntuple->Branch(name.c_str(), &_triggerResults._triggerArray[i], _buffsize,_splitlevel); + for(unsigned int i = 0; i < tnav.getTrigPaths().size(); ++i) { + const std::string name = "trig_"+tnav.getTrigPathNameByIndex(i); + _ntuple->Branch(name.c_str(), &_triggerResults._triggerArray[i], _buffsize,_splitlevel); } } - - for (unsigned int i = 0; i < tnav.getTrigPaths().size(); ++i) { - const std::string path = tnav.getTrigPathNameByIndex(i); - bool accepted = tnav.accepted(path); - _triggerResults._triggerArray[i] = accepted; + for(unsigned int i = 0; i < tnav.getTrigPaths().size(); ++i) { + _triggerResults._triggerArray[i] = tnav.accepted(tnav.getTrigPathNameByIndex(i)); } - - } - void EventNtupleMaker::fillTrackBranches(const art::Handle& kspch, BranchIndex i_branch, size_t i_kseedptr) { + void EventNtupleMaker::fillTrackBranches(const art::Handle& kspch, TrkFitBranchIndex i_trk_fit_branch, size_t i_kseedptr) { const auto& kseedptr = (kspch->at(i_kseedptr)); const auto& kseed = *kseedptr; // general info - _infoStructHelper.fillTrkInfo(kseed,_allTIs.at(i_branch)); - - // fit information at specific points:e - _infoStructHelper.fillTrkSegInfo(kseed,_allTSIs.at(i_branch)); - if(_ftype == LoopHelix && kseed.loopHelixFit())_infoStructHelper.fillLoopHelixInfo(kseed,_allLHIs.at(i_branch)); - if(_ftype == CentralHelix && kseed.centralHelixFit())_infoStructHelper.fillCentralHelixInfo(kseed,_allCHIs.at(i_branch)); - if(_ftype == KinematicLine && kseed.kinematicLineFit())_infoStructHelper.fillKinematicLineInfo(kseed,_allKLIs.at(i_branch)); - BranchConfig branchConfig = _allBranches.at(i_branch); - if(_conf.diag() > 1 || (_conf.fillhits() && branchConfig.options().fillhits())){ // want hit level info - _infoStructHelper.fillHitInfo(kseed, _allTSHIs.at(i_branch), _allTSHCIs.at(i_branch), _conf.fillhitcalibs()); - _infoStructHelper.fillMatInfo(kseed, _allTSMIs.at(i_branch)); - } - - // calorimeter info - _infoStructHelper.fillTrkCaloHitInfo(kseed, _allTCHIs.at(i_branch)); // fillTrkCaloHitInfo handles whether there is a calo hit or not - if (kseed.hasCaloCluster()) { - _tcnt._ndec = 1; // only 1 possible calo hit at the moment FIXME: should work with the above - // test + _infoStructHelper.fillTrkInfo(kseed,_allTIs.at(i_trk_fit_branch)); + _infoStructHelper.fillTrkSegInfo(kseed,_allTSIs.at(i_trk_fit_branch)); + if(_ftype == LoopHelix && kseed.loopHelixFit()) _infoStructHelper.fillLoopHelixInfo(kseed,_allLHIs.at(i_trk_fit_branch)); + if(_ftype == CentralHelix && kseed.centralHelixFit()) _infoStructHelper.fillCentralHelixInfo(kseed,_allCHIs.at(i_trk_fit_branch)); + if(_ftype == KinematicLine && kseed.kinematicLineFit()) _infoStructHelper.fillKinematicLineInfo(kseed,_allKLIs.at(i_trk_fit_branch)); + const TrkFitConfig& trkFitConfig = _allTrkFitBranches.at(i_trk_fit_branch); + if(_conf.diag() > 1 || (_conf.trk().fillHits() && trkFitConfig.options().fillhits())){ + _infoStructHelper.fillHitInfo(kseed, _allTSHIs.at(i_trk_fit_branch), _allTSHCIs.at(i_trk_fit_branch), _conf.trk().fillHitCalibs()); + _infoStructHelper.fillMatInfo(kseed, _allTSMIs.at(i_trk_fit_branch)); + } + + _infoStructHelper.fillTrkCaloHitInfo(kseed, _allTCHIs.at(i_trk_fit_branch)); + if(kseed.hasCaloCluster()) { + _tcnt._ndec = 1; if(_conf.debug()>0){ auto const& tch = kseed.caloHit(); auto const& cc = tch.caloCluster(); @@ -1191,127 +1161,106 @@ namespace mu2e { } } - - const auto& trkQualHandles = _allTrkQualCHs.at(i_branch); - for (size_t i_trkQualHandle = 0; i_trkQualHandle < trkQualHandles.size(); ++i_trkQualHandle) { + const auto& trkQualHandles = _allTrkQualCHs.at(i_trk_fit_branch); + for(size_t i_trkQualHandle = 0; i_trkQualHandle < trkQualHandles.size(); ++i_trkQualHandle) { const auto& trkQualHandle = trkQualHandles.at(i_trkQualHandle); - if (trkQualHandle.isValid()) { // might not have a valid handle - _infoStructHelper.fillTrkQualInfo(kseed, trkQualHandle->at(i_kseedptr) , _allTrkQualResults.at(i_branch).at(i_trkQualHandle)); + if(trkQualHandle.isValid()) { + _infoStructHelper.fillTrkQualInfo(kseed, trkQualHandle->at(i_kseedptr), _allTrkQualResults.at(i_trk_fit_branch).at(i_trkQualHandle)); } } - // all RecoQuals - std::vector recoQuals; // for the output value - for (const auto& i_recoQualHandle : _allRQCHs.at(i_branch)) { - Float_t recoQual = i_recoQualHandle->at(i_kseedptr)._value; - recoQuals.push_back(recoQual); - Float_t recoQualCalib = i_recoQualHandle->at(i_kseedptr)._calib; - recoQuals.push_back(recoQualCalib); + std::vector recoQuals; + for(const auto& i_recoQualHandle : _allRQCHs.at(i_trk_fit_branch)) { + recoQuals.push_back(i_recoQualHandle->at(i_kseedptr)._value); + recoQuals.push_back(i_recoQualHandle->at(i_kseedptr)._calib); } - _allRQIs.at(i_branch).setQuals(recoQuals); - // TrkCaloHitPID + _allRQIs.at(i_trk_fit_branch).setQuals(recoQuals); - const auto& trkPIDHandles = _allTrkPIDCHs.at(i_branch); - for (size_t i_trkPIDHandle = 0; i_trkPIDHandle < trkPIDHandles.size(); ++i_trkPIDHandle) { + const auto& trkPIDHandles = _allTrkPIDCHs.at(i_trk_fit_branch); + for(size_t i_trkPIDHandle = 0; i_trkPIDHandle < trkPIDHandles.size(); ++i_trkPIDHandle) { const auto& trkPIDHandle = trkPIDHandles.at(i_trkPIDHandle); - if (trkPIDHandle.isValid()) { // might not have a valid handle - _infoStructHelper.fillTrkPIDInfo(kseed, trkPIDHandle->at(i_kseedptr) , _allTrkPIDResults.at(i_branch).at(i_trkPIDHandle)); + if(trkPIDHandle.isValid()) { + _infoStructHelper.fillTrkPIDInfo(kseed, trkPIDHandle->at(i_kseedptr), _allTrkPIDResults.at(i_trk_fit_branch).at(i_trkPIDHandle)); } } - // fill MC info associated with this track - if(_fillmc && branchConfig.options().fillmc()) { + // fill tracker MC info + if(fillTrkMC(trkFitConfig.options())) { const PrimaryParticle& primary = *_pph; - // use Assns interface to find the associated KalSeedMC; this uses ptrs - if(_conf.debug() > 1)std::cout << "KalSeedMCMatch has " << _ksmcah->size() << " entries" << std::endl; - for(auto iksmca = _ksmcah->begin(); iksmca!= _ksmcah->end(); iksmca++){ + if(_conf.debug() > 1) std::cout << "KalSeedMCMatch has " << _ksmcah->size() << " entries" << std::endl; + for(auto iksmca = _ksmcah->begin(); iksmca != _ksmcah->end(); iksmca++){ if(_conf.debug() > 2) std::cout << "KalSeed Ptr " << kseedptr << " match Ptr " << iksmca->first << "?" << std::endl; if(iksmca->first == kseedptr) { auto const& kseedmc = *(iksmca->second); - _infoMCStructHelper.fillTrkInfoMC(kseed, kseedmc, _surfaceStepsHandle, _allMCTIs.at(i_branch)); - auto& mcvdis = _allMCVDInfos.at(i_branch); - _infoMCStructHelper.fillVDInfo(kseed, kseedmc, mcvdis); - _infoMCStructHelper.fillAllSimInfos(kseedmc, primary, _allMCSimTIs.at(i_branch), branchConfig.options().genealogyDepth(), branchConfig.options().matchDepth()); - - if(_conf.diag() > 1 || (_conf.fillhits() && branchConfig.options().fillhits())){ - _infoMCStructHelper.fillHitInfoMCs(kseed,kseedmc, _allTSHIMCs.at(i_branch)); + _infoMCStructHelper.fillTrkInfoMC(kseed, kseedmc, _surfaceStepsHandle, _allMCTIs.at(i_trk_fit_branch)); + _infoMCStructHelper.fillVDInfo(kseed, kseedmc, _allMCVDInfos.at(i_trk_fit_branch)); + _infoMCStructHelper.fillAllSimInfos(kseedmc, primary, _allMCSimTIs.at(i_trk_fit_branch), trkFitConfig.options().genealogyDepth(), trkFitConfig.options().matchDepth()); + if(_conf.diag() > 1 || (_conf.trk().fillHits() && trkFitConfig.options().fillhits())){ + _infoMCStructHelper.fillHitInfoMCs(kseed,kseedmc, _allTSHIMCs.at(i_trk_fit_branch)); } - // fill extra MCStep info for this branch for(size_t ixt = 0; ixt < _extraMCStepTags.size(); ixt++){ - auto const& mcsc = *_extraMCStepCollections[ixt]; - auto& mcsic = _extraMCStepInfos[i_branch][ixt]; - auto& mcssi = _extraMCStepSummaryInfos.at(i_branch).at(ixt); - _infoMCStructHelper.fillExtraMCStepInfos(kseedmc,mcsc,mcsic,mcssi); + _infoMCStructHelper.fillExtraMCStepInfos(kseedmc,*_extraMCStepCollections[ixt], + _extraMCStepInfos[i_trk_fit_branch][ixt],_extraMCStepSummaryInfos.at(i_trk_fit_branch).at(ixt)); } - // fill SurfaceStep info if(_surfaceStepsHandle.isValid()){ - if(_conf.debug() > 2)std::cout << "SurfaceSteps from handle " << _surfaceStepsHandle << std::endl; - auto& ssi = _surfaceStepInfos.at(i_branch); + if(_conf.debug() > 2) std::cout << "SurfaceSteps from handle " << _surfaceStepsHandle << std::endl; + auto& ssi = _surfaceStepInfos.at(i_trk_fit_branch); ssi.push_back(std::vector()); _infoMCStructHelper.fillSurfaceStepInfos(kseedmc,*_surfaceStepsHandle,ssi.back()); } break; } } - if (kseed.hasCaloCluster() && _fillcalomc) { - // fill MC truth of the associated CaloCluster. Use the fact that these are correlated by index with the clusters in that collection + if(kseed.hasCaloCluster() && fillCaloTrackMatchMC()) { auto index = kseed.caloCluster().key(); - auto const& ccmcc = *_ccmcch; - auto const& ccmc = ccmcc[index]; - _infoMCStructHelper.fillCaloClusterInfoMC(ccmc,_allMCTCHIs.at(i_branch)); // currently broken due to CaloMC changes. This needs fixing in compression + auto const& ccmc = (*_ccmcch)[index]; + _infoMCStructHelper.fillCaloClusterInfoMC(ccmc,_allMCTCHIs.at(i_trk_fit_branch)); } } } // some branches can't be made until the analyze() function because we want to write out all data products of a certain type - // these all have an underlying array where we want to name the individual elements in the array with different leaf names template std::vector > EventNtupleMaker::createSpecialBranch(const art::Event& event, const std::string& branchname, - std::vector >& handles, // this parameter is only needed so that the template parameter T can be deduced. There is probably a better way to do this FIXME + std::vector >& handles, TI& infostruct, TIA& array, bool make_individual_branches, const std::string& selection) { std::vector > outputHandles; std::vector > inputHandles = event.getMany(); - if (inputHandles.size()>0) { + if(inputHandles.size()>0) { std::vector labels; - for (const auto& i_handle : inputHandles) { + for(const auto& i_handle : inputHandles) { std::string moduleLabel = i_handle.provenance()->moduleLabel(); - // event.getMany() doesn't have a way to wildcard part of the ModuleLabel, do it ourselves here size_t pos; - if (selection != "") { // if we want to add a selection + if(selection != "") { pos = moduleLabel.find(selection); - - // make sure that the selection (e.g. "DeM") appears at the end of the module label - if (pos == std::string::npos) { - if(_conf.debug() > 3)std::cout << "Selection not found" << std::endl; + if(pos == std::string::npos) { + if(_conf.debug() > 3) std::cout << "Selection not found" << std::endl; continue; } - else if (pos+selection.length() != moduleLabel.size()) { - if(_conf.debug() > 3)std::cout << "Selection wasn't at end of moduleLabel" << std::endl; + else if(pos+selection.length() != moduleLabel.size()) { + if(_conf.debug() > 3) std::cout << "Selection wasn't at end of moduleLabel" << std::endl; continue; } moduleLabel = moduleLabel.erase(pos, selection.length()); } std::string instanceName = i_handle.provenance()->productInstanceName(); - - std::string branchname = moduleLabel; - if (instanceName != "") { - branchname += "_" + instanceName; - } + std::string bname = moduleLabel; + if(instanceName != "") bname += "_" + instanceName; outputHandles.push_back(i_handle); - labels.push_back(branchname); + labels.push_back(bname); } - if (make_individual_branches) { // if we want to make individual branches per leaf (e.g. to avoid branch ambiguities in python such as detrkqual.NActiveHits vs uetrkqual.NActiveHits) + if(make_individual_branches) { const std::vector& leafnames = infostruct.leafnames(labels); int n_leaves = leafnames.size(); - for (int i_leaf = 0; i_leaf < n_leaves; ++i_leaf) { + for(int i_leaf = 0; i_leaf < n_leaves; ++i_leaf) { std::string thisbranchname = (branchname+"."+leafnames.at(i_leaf)); - if (!_ntuple->GetBranch(thisbranchname.c_str())) { // only want to create the branch once + if(!_ntuple->GetBranch(thisbranchname.c_str())) { _ntuple->Branch(thisbranchname.c_str(), &array[i_leaf]); } } } else { - if (!_ntuple->GetBranch((branchname+".").c_str())) { // only want to create the branch once + if(!_ntuple->GetBranch((branchname+".").c_str())) { _ntuple->Branch((branchname+".").c_str(), &infostruct, infostruct.leafname(labels).c_str()); } } @@ -1320,14 +1269,12 @@ namespace mu2e { } void EventNtupleMaker::resetTrackBranches() { - for (BranchIndex i_branch = 0; i_branch < _allBranches.size(); ++i_branch) { - - _allRQIs.at(i_branch).reset(); + for(TrkFitBranchIndex i_trk_fit_branch = 0; i_trk_fit_branch < _allTrkFitBranches.size(); ++i_trk_fit_branch) { + if (!_allTrkFitBranches.at(i_trk_fit_branch).enabled()) continue; + _allRQIs.at(i_trk_fit_branch).reset(); } } } // end namespace mu2e -// Part of the magic that makes this class a module. -// create an instance of the module. It also registers using mu2e::EventNtupleMaker; DEFINE_ART_MODULE(EventNtupleMaker) diff --git a/validation/README.md b/validation/README.md index 25853ae..244c93c 100644 --- a/validation/README.md +++ b/validation/README.md @@ -8,10 +8,15 @@ We want to make sure that any changes we make to the code don't: From ```EventNtuple/``` directory: ``` -. ./validation/test_fcls.sh [MDC2020 or MDC2025 or Run1B] +python validation/test_fcls.py --tests validation/commands.txt --datasets validation/datasets_XXXXX.txt ``` +where ```XXXXXX``` is any of ```MDC2020```, ```MDC2025```, or ```Run1B```. -This script will run all the supported fcl files over relevant datasets. All tests should report ```OK```. If one reports ```FAIL```, you can find the exact command that failed in the ```test_fcls.log``` file in order to debug. +This script will run all the supported fcl files over relevant datasets. The output should be self-explanatory. + +The reason for separating the commands from the datasets is so that we can easily change to a different set of datasets with new simulation campaigns, as well as update datasets in currently-supported simulation campaigns. + +You can comment out commands in the ```commands.txt``` with a ```#``` to focus on failing tests ### Creating a Validation File Before making any changes, create an EventNtuple file and run the validation script. This creates a new ROOT file with histograms created from the EventNtuple ntuple: diff --git a/validation/commands.txt b/validation/commands.txt new file mode 100644 index 0000000..1ae791c --- /dev/null +++ b/validation/commands.txt @@ -0,0 +1,41 @@ +# Test mock datasets with and without MC +mu2e -c fcl/from_mcs-mockdata.fcl -S {mock} --TFileName nts.ntuple.mock.root -n 100 +mu2e -c fcl/from_mcs-mockdata_noMC.fcl -S {mock} --TFileName nts.ntuple.mockNoMC.root -n 100 + +# Test some individual reconstructed datasets +mu2e -c fcl/from_mcs-primary.fcl -S {primary} --TFileName nts.ntuple.primary.root -n 100 +mu2e -c fcl/from_mcs-mixed.fcl -S {mixed} --TFileName nts.ntuple.mixed.root -n 100 +mu2e -c fcl/from_mcs-extracted.fcl -S {extracted} --TFileName nts.ntuple.extracted.root -n 100 + +# Test ceSimReco works +mu2e -c Production/Validation/ceSimReco.fcl -n 10 +mu2e -c fcl/from_mcs-ceSimReco.fcl -s mcs.owner.val-ceSimReco.dsconf.seq.art --TFileName nts.ntuple.ceSimReco.root + +# Create a dataset with trkhitcalib branch +mu2e -c validation/ceSimReco.fcl -n 100 +mu2e -c validation/ceTrig.fcl -s mcs.owner.val-ceSimRecoVal.dsconf.seq.art --TFileName nts.ntuple.ceTrig.root +mu2e -c fcl/from_mcs-ceSimRecoVal.fcl -s mcs.owner.val-ceTrig.dsconf.seq.art --TFileName nts.ntuple.ceSimRecoVal.root + +# Test some options +mu2e -c fcl/from_mcs-mockdata_separateTrkBranches.fcl -S {mock} --TFileName nts.ntuple.mockSepTrkBranches.root -n 100 +mu2e -c fcl/from_mcs-mockdata_selectorExample.fcl -S {mock} --TFileName nts.ntuple.mockSelector.root -n 100 +mu2e -c fcl/from_mcs-primary_addVDSteps.fcl -S {primary} --TFileName nts.ntuple.primaryVDSteps.root -n 100 + +# Test digi datasets +mu2e -c fcl/from_dig-mockdata.fcl -S {digi} --TFileName nts.ntuple.dig.root -n 100 +mu2e -c fcl/from_dig-OnSpill.fcl -S {digi} --TFileName nts.ntuple.digOnSpill.root -n 100 +mu2e -c fcl/from_dig-calo.fcl -S {digi} --TFileName nts.ntuple.digCalo.root -n 100 + +# Create validation histogras +mu2e -c fcl/from_mcs-mockdata.fcl -S {mock} --TFileName nts.ntuple.after.root -n 100 +root -l -b -q validation/create_val_file_rooutil.C\(\"nts.ntuple.after.root\",\"val.ntuple.after.root\"\) + +# CRV KPP (real data) +mu2e -c fcl/from_rec-crv-kpp.fcl -S {crv_kpp} --TFileName nts.ntuple.crv-kpp.root -n 100 +mu2e -c fcl/from_rec-crv-kpp_withCrvDigis.fcl -S {crv_kpp} --TFileName nts.ntuple.crv-kpp_withCrvDigis.root -n 100 + +# DeCalib tests +mu2e -c fcl/from_mcs-DeCalib.fcl -S {mixed} --TFileName nts.ntuple.deCalib.root -n 100 + +# Campaign-specific +mu2e -c fcl/from_mcs-Run1B.fcl -S {Run1B} --TFileName nts.ntuple.Run1B.root -n 100 \ No newline at end of file diff --git a/validation/datasets_MDC2020.txt b/validation/datasets_MDC2020.txt new file mode 100644 index 0000000..0965d95 --- /dev/null +++ b/validation/datasets_MDC2020.txt @@ -0,0 +1,7 @@ +# Test Datasets for Validating EventNtuple with MDC2020 +{mock} mcs.mu2e.ensembleMDS2cMix1BBTriggered.MDC2020ba_best_v1_3.art +{primary} mcs.mu2e.CeEndpointOnSpillTriggered.MDC2020aw_best_v1_3.art +{mixed} mcs.mu2e.CeEndpointMix1BBTriggered.MDC2020aw_best_v1_3.art +{extracted} mcs.mu2e.CosmicCRYExtractedTriggered.MDC2020aw.art +{digi} dig.mu2e.DIOtail95OnSpillTriggered.MDC2020au_perfect_v1_3.art +{crv_kpp} N/A \ No newline at end of file diff --git a/validation/datasets_MDC2025.txt b/validation/datasets_MDC2025.txt new file mode 100644 index 0000000..4858ad5 --- /dev/null +++ b/validation/datasets_MDC2025.txt @@ -0,0 +1,7 @@ +# Test Datasets for Validating EventNtuple with MDC2025 +{mock} mcs.mu2e.ensembleMDS3aOnSpillTriggered.MDC2025af_best_v1_3.art +{primary} mcs.mu2e.CeEndpointOnSpillTriggered.MDC2025ae_best_v1_3.art +{mixed} mcs.mu2e.CeMLeadingLogMix1BBTriggered.MDC2025af_best_v1_1.art +{extracted} mcs.mu2e.CosmicCRYExtractedTriggered.MDC2025ae_best_v1_3.art +{digi} dig.mu2e.FlatGammaMix1BBTriggered.MDC2025af_best_v1_1.art +{crv_kpp} rec.mu2e.cosmics_crv.crvreco-004-000.art \ No newline at end of file diff --git a/validation/datasets_Run1B.txt b/validation/datasets_Run1B.txt new file mode 100644 index 0000000..db5da2b --- /dev/null +++ b/validation/datasets_Run1B.txt @@ -0,0 +1,8 @@ +# Test Datasets for Validating EventNtuple with MDC2025 +{mock} N/A +{primary} mcs.mu2e.CeEndpointOnSpillTriggerable-KL.Run1Baf_best_v1_4-000.art +{mixed} mcs.mu2e.CeEndpointMix1BB-KL.Run1Bah_best_v1_4-001.art +{extracted} N/A +{digi} N/A +{crv_kpp} N/A +{Run1B} mcs.mu2e.CeEndpointOnSpillTriggerable-KL.Run1Baf_best_v1_4-000.art \ No newline at end of file diff --git a/validation/test_fcls.py b/validation/test_fcls.py new file mode 100644 index 0000000..e285242 --- /dev/null +++ b/validation/test_fcls.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +""" +Test script to run EventNtuple fcl files on datasets. +Similar to test_fcls.sh but in Python for better control and error handling. +""" + +import os +import subprocess +import sys +import argparse +from pathlib import Path + +from enum import Enum + +# Define the enum +class Result(Enum): + FAILED = 0 + PASSED = 1 + SKIPPED = 2 + +def run(cmd, datasets_dict): + """ + Run a test + + Args: + test (str): Command to run + + Returns: + bool: True if successful, False otherwise + """ + """ if not os.path.exists(fcl_file): + print(f"Error: FCL file not found: {fcl_file}") + return False + + filelist_path = Path("../filelists/") / f"{input_dataset}.list" + if not os.path.exists(filelist_path): + # Get the filelist using metacat + cmd = [f"muse setup ops && metacat query files from mu2e:{input_dataset} | mdh print-url -l tape - > {filelist_path}"] + print(f"Creating filelist for dataset: {input_dataset}") + try: + subprocess.run(cmd, shell=True, check=True) + except subprocess.CalledProcessError as e: + print(f"Error: Failed to create filelist for dataset: {input_dataset}") + return False + + # Build mu2e command + cmd = ["mu2e", "-c", fcl_file] + + # Add input dataset + cmd.extend(["-S", filelist_path.as_posix()]) + + # Add output file + cmd.extend(["-o", output_file]) + + # Add max events if specified + if max_events: + cmd.extend(["-n", str(max_events)]) """ + + # Check for a filelists/ directory + filelist_dir = Path("../filelists/") + if not os.path.exists(filelist_dir): + print(f"Target directory does not exist: {filelist_dir}") + reply = input("Would you like to create this new directory? (y/n): ").strip().lower() + + if reply in ('y', 'yes'): + try: + # parents=True creates intermediate folders if missing (like mkdir -p) + filelist_dir.mkdir(parents=True, exist_ok=True) + print(f"Successfully created directory: {filelist_dir}") + except PermissionError: + print(f"Permission denied: Cannot create directory at {filelist_dir}") + except Exception as e: + print(f"Failed to create directory. Error: {e}") + else: + print("Directory creation canceled by user.") + return Result.FAILED + + # Search for dataset name and turn into filelist + input_dataset = cmd.split('-S ') + if len(input_dataset)>1: + input_dataset = input_dataset[1].split()[0] + if input_dataset in datasets_dict: + old_input_dataset = input_dataset + input_dataset = datasets_dict[input_dataset] + if (input_dataset == "N/A"): + print(f"Warning: Dataset name for '{old_input_dataset}' is marked as N/A in datasets file. Skipping test...") + return Result.SKIPPED # returning SKIPPED to avoid counting as a failure, but skipping the test since dataset is not available + cmd = cmd.replace(old_input_dataset, input_dataset) + else: + if input_dataset.startswith("{"): + print(f"Warning: Dataset name '{input_dataset}' looks like it should be in the datasets file but it can't be found. Skipping test...") + return Result.SKIPPED + else: + print(f"Warning: Dataset name '{input_dataset}' not found in datasets file. Using as-is.") + + # Check for filelist + filelist_path = Path("../filelists/") / f"{input_dataset}.list" + if not os.path.exists(filelist_path): + # Get the filelist using metacat + subcmd = [f"muse setup ops && metacat query files from mu2e:{input_dataset} | mdh print-url -l tape - > {filelist_path}"] + print(f"Creating filelist for dataset: {input_dataset}") + try: + subprocess.run(subcmd, shell=True, check=True) + except subprocess.CalledProcessError as e: + print(f"Error: Failed to create filelist for dataset: {input_dataset}") + return FAILED + cmd = cmd.replace(input_dataset, str(filelist_path)) + + print(f"Running: {cmd}") + + try: + result = subprocess.run(cmd.split(), check=True, capture_output=True, text=True) + print(f"✓ Successfully processed: {cmd}") + #print(f" Output: {output_file}") + return Result.PASSED + except subprocess.CalledProcessError as e: + print(f"✗ Error processing {cmd}") + print(f" Stdout: {e.stdout}") + print(f" Stderr: {e.stderr}") + return Result.FAILED + + except FileNotFoundError as e: + print(f"{e}") + return Result.FAILED + + +def main(): + parser = argparse.ArgumentParser( + description="Run EventNtuple fcl files on datasets" + ) + parser.add_argument( + "--tests", + help="Path to file containing list of tests to run (one command per line)", + required=True + ) + parser.add_argument( + "--datasets", + help="Path to file containing list of dataset names", + required=True + ) + + args = parser.parse_args() + + # Check if batch processing requested + datasets = { } + if not os.path.exists(args.datasets): + print(f"Error: Datasets file not found: {args.datasets}") + sys.exit(1) + with open(args.datasets, 'r') as f: + for line in f: + line = line.strip() + if line and not line.startswith('#'): + parts = line.split() + if len(parts) == 2: + datasets[parts[0]] = parts[1] + else: + print(f"Warning: Invalid line in datasets file (skipping): {line}") + + if not os.path.exists(args.tests): + print(f"Error: Tests file not found: {args.tests}") + sys.exit(1) + + results = [] + with open(args.tests, 'r') as f: + tests = [line.strip() for line in f if line.strip() and not line.strip().startswith('#')] + + print(f"Processing {len(tests)} tests...") + + for i, (test) in enumerate(tests, 1): + print(f"\n[{i}/{len(tests)}] Processing: {test}") + result = run(test, datasets) + results.append((test, result)) + + # Print summary + print("\n" + "="*60) + print("TEST PROCESSING SUMMARY") + print("="*60) + passed = sum(1 for _, result in results if result == Result.PASSED) + failed = sum(1 for _, result in results if result == Result.FAILED) + skipped = sum(1 for _, result in results if result == Result.SKIPPED) + print(f"Total: {len(results)}, Passed: {passed}, Failed: {failed}, Skipped: {skipped}") + + if passed < len(results): + print("\nFailed commands:") + for command, result in results: + if result == Result.FAILED: + print(f" - {command}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/validation/test_fcls.sh b/validation/test_fcls.sh deleted file mode 100644 index 3fb89d2..0000000 --- a/validation/test_fcls.sh +++ /dev/null @@ -1,216 +0,0 @@ -# -# test_fcls.sh - runs fcl files to make sure that they complete successfully -# Note: requires relevant filelists in a directory above this one -# -if [ $# -ne 1 ]; then - echo "Usage: . ./validation/test_fcls.sh [MDC2020 / MDC2025 / Run1B]" - return -fi -vMDC=$1 - -log_file="test_fcls.log" -rm ${log_file} - -mock_dataset="" -primary_dataset="" -mixed_dataset="" -extracted_dataset="" -digi_dataset="" -crv_kpp_dataset="temp_crv_kpp" - -if [[ "$vMDC" == "MDC2020" ]]; then - echo "Testing MDC2020 datasets" - mock_dataset="mcs.mu2e.ensembleMDS2cMix1BBTriggered.MDC2020ba_best_v1_3.art" - primary_dataset="mcs.mu2e.CeEndpointOnSpillTriggered.MDC2020aw_best_v1_3.art" - mixed_dataset="mcs.mu2e.CeEndpointMix1BBTriggered.MDC2020aw_best_v1_3.art" - extracted_dataset="mcs.mu2e.CosmicCRYExtractedTriggered.MDC2020aw.art" - digi_dataset="dig.mu2e.DIOtail95OnSpillTriggered.MDC2020au_perfect_v1_3.art" -elif [[ "$vMDC" == "MDC2025" ]]; then - echo "Testing MDC2025 datasets" - mock_dataset="mcs.mu2e.ensembleMDS3aOnSpillTriggered.MDC2025af_best_v1_3.art" - primary_dataset="mcs.mu2e.CeEndpointOnSpillTriggered.MDC2025ae_best_v1_3.art" - mixed_dataset="mcs.mu2e.CeMLeadingLogMix1BBTriggered.MDC2025af_best_v1_1.art" - extracted_dataset="mcs.mu2e.CosmicCRYExtractedTriggered.MDC2025ae_best_v1_3.art" - digi_dataset="dig.mu2e.FlatGammaMix1BBTriggered.MDC2025af_best_v1_1.art" -elif [[ "$vMDC" == "Run1B" ]]; then - echo "Testing Run1B datasets" - echo "No mock datasets for Run1B - some tests will fail..." - mock_dataset="" # "mcs.mu2e.ensembleMDS3aOnSpillTriggered.MDC2025af_best_v1_3.art" - primary_dataset="mcs.mu2e.CeEndpointOnSpillTriggerable-KL.Run1Baf_best_v1_4-000.art" - mixed_dataset="mcs.mu2e.CeEndpointMix1BB-KL.Run1Bah_best_v1_4-001.art" - echo "No extracted datasets for Run1B - some tests will fail..." - extracted_dataset="" # "mcs.mu2e.CosmicCRYExtractedTriggered.MDC2025ae_best_v1_3.art" - echo "No reco+ntuple fcl for Run1B - some tests will fail..." - digi_dataset="" # "dig.mu2e.FlatGammaMix1BBTriggered.MDC2025af_best_v1_1.art" -else - echo "Unsupported option $vMDC" - return -fi - -all_datasets=( $mock_dataset $primary_dataset $mixed_dataset $extracted_dataset $digi_dataset $crv_kpp_dataset ) - -if [ ! -d ../filelists ]; then - echo "Making directory ../filelists/" - mkdir ../filelists/ -fi - -for dataset in "${all_datasets[@]}" -do - if [ ! -f ../filelists/$dataset.list ]; then - echo "File list for $dataset doesn't exist. Creating..." - setup mu2efiletools - mu2eDatasetFileList $dataset > ../filelists/$dataset.list - fi -done - - -echo -n "from_mcs-mockdata.fcl... " -echo "mu2e -c fcl/from_mcs-mockdata.fcl -S ../filelists/$mock_dataset.list --TFileName nts.ntuple.mock.root -n 100" > ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mockdata.fcl -S ../filelists/$mock_dataset.list --TFileName nts.ntuple.mock.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-mockdata_noMC.fcl... " -echo "mu2e -c fcl/from_mcs-mockdata_noMC.fcl -S ../filelists/$mock_dataset.list --TFileName nts.ntuple.mockNoMC.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mockdata_noMC.fcl -S ../filelists/$mock_dataset.list --TFileName nts.ntuple.mockNoMC.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-primary.fcl... " -echo "mu2e -c fcl/from_mcs-primary.fcl -S ../filelists/$primary_dataset.list --TFileName nts.ntuple.primary.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-primary.fcl -S ../filelists/$primary_dataset.list --TFileName nts.ntuple.primary.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-mixed.fcl... " -echo "mu2e -c fcl/from_mcs-mixed.fcl -S ../filelists/$mixed_dataset.list --TFileName nts.ntuple.mixed.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mixed.fcl -S ../filelists/$mixed_dataset.list --TFileName nts.ntuple.mixed.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-extracted.fcl... " -echo "mu2e -c fcl/from_mcs-extracted.fcl -S ../filelists/$extracted_dataset.list --TFileName nts.ntuple.extracted.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-extracted.fcl -S ../filelists/$extracted_dataset.list --TFileName nts.ntuple.extracted.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-ceSimReco.fcl... " -echo "mu2e -c Production/Validation/ceSimReco.fcl -n 10" >> ${log_file} 2>&1 -mu2e -c Production/Validation/ceSimReco.fcl -n 10 >> ${log_file} 2>&1 -echo "mu2e -c fcl/from_mcs-ceSimReco.fcl -s mcs.owner.val-ceSimReco.dsconf.seq.art --TFileName nts.ntuple.ceSimReco.root" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-ceSimReco.fcl -s mcs.owner.val-ceSimReco.dsconf.seq.art --TFileName nts.ntuple.ceSimReco.root >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-ceSimRecoVal.fcl... " -echo "mu2e -c validation/ceSimReco.fcl -n 100" >> ${log_file} 2>&1 -mu2e -c validation/ceSimReco.fcl -n 100 >> ${log_file} 2>&1 -echo "mu2e -c validation/ceTrig.fcl -s mcs.owner.val-ceSimRecoVal.dsconf.seq.art --TFileName nts.ntuple.ceTrig.root" >> ${log_file} 2>&1 -mu2e -c validation/ceTrig.fcl -s mcs.owner.val-ceSimRecoVal.dsconf.seq.art --TFileName nts.ntuple.ceSimRecoVal.root >> ${log_file} 2>&1 -echo "mu2e -c fcl/from_mcs-ceSimRecoVal.fcl -s mcs.owner.val-ceTrig.dsconf.seq.art --TFileName nts.ntuple.ceSimRecoVal.root" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-ceSimRecoVal.fcl -s mcs.owner.val-ceTrig.dsconf.seq.art --TFileName nts.ntuple.ceTrig.root >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-mockdata_separateTrkBranches.fcl... " -echo "mu2e -c fcl/from_mcs-mockdata_separateTrkBranches.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.mockSepTrkBranches.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mockdata_separateTrkBranches.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.mockSepTrkBranches.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-mockdata_selectorExample.fcl... " -echo"mu2e -c fcl/from_mcs-mockdata_selectorExample.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.mockSelector.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mockdata_selectorExample.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.mockSelector.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-primary_addVDSteps.fcl... " -echo "mu2e -c fcl/from_mcs-primary_addVDSteps.fcl -S ../filelists/$primary_dataset.list --TFileName nts.ntuple.primaryVDSteps.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-primary_addVDSteps.fcl -S ../filelists/$primary_dataset.list --TFileName nts.ntuple.primaryVDSteps.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - - -echo -n "from_dig-mockdata.fcl... " -echo "mu2e -c fcl/from_dig-mockdata.fcl -S ../filelists/${digi_dataset}.list --TFileName nts.ntuple.dig.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_dig-mockdata.fcl -S ../filelists/${digi_dataset}.list --TFileName nts.ntuple.dig.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "creating file for validation script... " -echo "mu2e -c fcl/from_mcs-mockdata.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.after.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-mockdata.fcl -S ../filelists/${mock_dataset}.list --TFileName nts.ntuple.after.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "creating validation file... " -echo "root -l -b -q validation/create_val_file_rooutil.C\(\"nts.ntuple.after.root\",\"val.ntuple.after.root\"\)" >> ${log_file} 2>&1 -root -l -b -q validation/create_val_file_rooutil.C\(\"nts.ntuple.after.root\",\"val.ntuple.after.root\"\) >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_rec-crv-kpp.fcl... " -echo "mu2e -c fcl/from_rec-crv-kpp.fcl -S ../filelists/${crv_kpp_dataset}.list --TFileName nts.ntuple.crv-kpp.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_rec-crv-kpp.fcl -S ../filelists/${crv_kpp_dataset}.list --TFileName nts.ntuple.crv-kpp.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_rec-crv-kpp_withCrvDigis.fcl... " -echo "mu2e -c fcl/from_rec-crv-kpp_withCrvDigis.fcl -S ../filelists/${crv_kpp_dataset}.list --TFileName nts.ntuple.crv-kpp_withCrvDigis.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_rec-crv-kpp_withCrvDigis.fcl -S ../filelists/${crv_kpp_dataset}.list --TFileName nts.ntuple.crv-kpp_withCrvDigis.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi - -echo -n "from_mcs-DeCalib.fcl... " -echo "mu2e -c fcl/from_mcs-DeCalib.fcl -S ../filelists/${mixed_dataset}.list --TFileName nts.ntuple.deCalib.root -n 100" >> ${log_file} 2>&1 -mu2e -c fcl/from_mcs-DeCalib.fcl -S ../filelists/${mixed_dataset}.list --TFileName nts.ntuple.deCalib.root -n 100 >> ${log_file} 2>&1 -if [ $? == 0 ]; then - echo "OK" | tee -a ${log_file} -else - echo "FAIL" | tee -a ${log_file} -fi