Update TrkPID model and producer - #8
Conversation
oksuzian
left a comment
There was a problem hiding this comment.
PR Review Summary — "Update TrkPID model and producer" (#8)
Reviewed at head 43f4d1f8 (2026-08-04). First review of this PR. Scope: deploy the MLTrain#9 retraining — new TrackPID v1 (E/P, Δt, p(χ²), dt/dt slope) and new calo-free TrackOnlyPID producer, both multi-collection; the old model renamed to v0; regenerated SOFIE headers and weights. Companion to the just-approved Mu2e/MLTrain#9, and the SOFIE headers here were regenerated today after the MLTrain float32/batch-1 fixes (v1 and TrkOnlyPID headers are batch-1, generated 2026-08-04) — the right sequencing.
Decision
- 🔴 Request changes. One S0 (merging this breaks the standard EventNtuple ntupling path until a coordinated EventNtuple PR lands) and three S1s (the old model's weights were overwritten in place, so v0 silently loads the wrong weights; the new module is absent from the CMake build; the deployed v1 slope feature is computed with a different estimator than the one the model was trained on, with no validation evidence in the PR).
Scope understood
TrackPID_modulereworked: validated multi-collection config (KalSeedssequence + optional parallelKalSeedDtDts),MVAVersionselects v0 (calo-geometry features) or v1 (track-based features from MLTrain#9), oneMVAResultCollectionoutput instance per input collection.- New
TrackOnlyPID_module: same shape, tracker-only v0 model. - Generated artifacts:
TrackPID_v0.hxx(rename of old header, namespace/guard only),TrackPID_v1.hxx+TrkOnlyPID_v0.hxx(fresh batch-1 exports),TrackPID_v1.dat(overwritten),TrkOnlyPID_v0.dat(new).
Findings
-
🔴 [S0] Merging this breaks EventNtuple's default ntupling path — a coordinated EventNtuple PR is required and none is referenced.
- Evidence:
EventNtuple/fcl/prolog.fclrunsTrkPIDAll(module_type: TrackPID) in its defaultPathandPathNoMC(lines 432/436), configured withDeltaTOffset,datFilename, andKalSeedPtrCollection(lines 54-68). The new validatedConfigremovesDeltaTOffset, renamesdatFilename→DatFilename, replacesKalSeedPtrCollectionwith the requiredKalSeedssequence, and adds requiredMVAVersion— so fhicl validation throws at module construction in every standard EventNtuple job at the next Analysis-musing build. - Three distinct breaks to coordinate: (a) the config schema above; (b) product identity — the module now
produceswith an instance name per input collection (e.g.TrkPIDAll:MergeKKAll), so EventNtuple'strkPIDTags: ["TrkPIDDeM"](empty instance) no longer resolves even after the schema fix; (c) MVAVersion 1 requiresKalSeedDtDtcollections, and EventNtuple's path configures noTrackDtDtproducers (the Offline producer exists —ParticleID/src/TrackDtDt_module.cc, merged in Offline#1873 — it just isn't wired anywhere). - Suggested fix: a companion EventNtuple PR (new schema,
label:instancetrkPIDTags,TrackDtDtproducers in the path), merged in lockstep; name it in this PR's body. Until then this PR is not mergeable without breaking downstream.
- Evidence:
-
🟠 [S1] The v0 model's weights were overwritten in place — "the older model can still be accessed" is not actually true, and the failure is silent.
- Evidence: on main,
data/TrackPID_v1.datheld the weights of the then-only (now-v0) graph. This PR overwrites its values with the new v1 training's weights and adds noTrackPID_v0.dat. Because both graphs are the same 4→5→10→5→1 architecture and the keras export produced identical tensor names and sizes (verified: the.datdiff changes only values, andTrackPID_v0.hxx/TrackPID_v1.hxxsessions read the same tensor names), a v0Sessionpointed at the new file loads without any error and evaluates v1 weights on v0 features (E−P, R(cluster), p·x, Δt) — silently wrong scores. - Suggested fix: restore the pre-PR file content as
data/TrackPID_v0.datsoMVAVersion: 0+DatFilename: .../TrackPID_v0.datreproduces the old model, and note the pairing rule (version ↔ .dat) in the Config comment.
- Evidence: on main,
-
🟠 [S1]
TrackOnlyPIDis missing fromTrkDiag/CMakeLists.txt— the new module does not exist in CMake/spack builds.- Evidence: the scons
SConscriptbuilds plugins by globbing*_module.cc, so muse builds pick it up automatically; the CMakeLists builds plugins via explicitcet_build_pluginentries and has none forTrackOnlyPID. No configure or link error results — the module is just silently absent, failing only at runtime ("module type TrackOnlyPID not found"). Same scons↔CMake mirroring class as recent Offline findings; ArtAnalysis has no CI on either build system, so nothing catches it. - Suggested fix: add
cet_build_plugin(TrackOnlyPID art::module REG_SOURCE src/TrackOnlyPID_module.cc LIBRARIES REG ArtAnalysis::TrkDiag Offline::ConfigTools Offline::RecoDataProducts), modeled on theTrackPIDentry (whose SOFIE usage compiles with the same dependency set —TrackQualityis the in-repo precedent).
- Evidence: the scons
-
🟠 [S1] The deployed v1 slope feature is computed with a different estimator than the training feature, and the PR carries no validation evidence.
- Evidence: the model was trained on the slope from MLTrain's
make_inputs.pyfit — unweighted (flat 5 ns,absolute_sigma) least squares ofptoca + udtvsptocaover all track hits. The deployed feature isKalSeedDtDt::slope()from Offline'sTrackDtDt, which fits the same axes over the same hits but with inverse-variance weights (1/fitTocaVarper hit,TrackDtDt_module.cc:127-129). A differently-weighted estimator has a systematically different distribution; the MVA then runs on a feature it was not trained on, and any performance loss is silent. Secondary, smaller: training E/P used the momentum at thesid == 1(tracker-middle) segment, while inference usesmomentumAtCalo— a sub-percent shift, likely negligible against calo energy resolution, but worth stating. - Suggested fix: overlay
TrackDtDtslope vs the training branch for the same tracks and show the model's performance is unchanged (or retrain on the producer's output — the more robust long-term choice). Also: ArtAnalysis has no CI, so please include build evidence (muse/scons at minimum, CMake if possible) and a smoke job in the PR body per the standard validation packet.
- Evidence: the model was trained on the slope from MLTrain's
-
🟡 [S2] The repo's own
prolog.fclTrkPIDblock is stale — pre-existing, but this PR widens the gap and the config surface is its subject.- Evidence:
TrkDiag/fcl/prolog.fclstill configuresMVAConfig: { MVAWeights: ...TrkCaloHitPID.weights.xml }— invalid against the module Config since before this PR, and further from valid now (TrkPIDDeM/TrkPIDDePaliases equally broken). The only working example of the new schema is nowhere in the repo. - Suggested fix: update the block to a working v1 example (
KalSeeds,KalSeedDtDts,DatFilename,MVAVersion) — it doubles as the migration reference for the EventNtuple companion PR.
- Evidence:
-
🟡 [S2] Label-only input resolution and the output-instance convention are undocumented contract.
- Evidence:
event.getByLabel(name, handle)treats eachKalSeedsentry as a bare module label (nolabel:instanceparsing), and the same string becomes the output instance name. Works for the intendedMergeKK*labels; a user passing"label:instance"gets a silent lookup failure phrased as "no collection with label". - Suggested fix: say so in the
KalSeedsConfig comment ("bare module labels; also used as the output instance name"), or parse viaart::InputTagand reject tags with instances explicitly.
- Evidence:
-
⚪ [S3] Housekeeping batch (none gate):
- The renamed
TrackPID_v0.hxxkeeps the old batch-32 session whileevaluateMVA_v0passes a 4-float array toinfer— a 128-float read from a 4-element array.mvaout[0]is still computed from the real features (row 0 of the GEMM), so results are correct in practice, but it is UB on paper. Pre-existing, preserved by the rename; regenerate v0 at batch 1 when restoring the v0 weights (finding 2). - Include hygiene (both modules):
MVATools.hh,ProditionsHandle.hh,TFileService.h,make_tool.h,<float.h>,<functional>are unused;std::arrayis used without<array>(arrives transitively). - Dead code (both modules):
initializeMVAis declared but never defined or called;printMVA_is write-only since the SOFIE migration. MVAVersionis range-checked only from above (> 1/> 0); a negative value passes the constructor and throws per-track in theswitch— validate!= {0,1}(or!= 0) at construction.nActivevianHits(true)countsStrawHitFlag::activewhile training'snactivecountedstrawHitState() > inactive— near-equivalent, but counting state-based inside the existingnNullloop would make training parity exact and save the second pass. In that loop, the first clause ofstate > inactive && state == nullis redundant (null = 0 > inactive = -2).nActive == 0gives NaN/inf features (no crash); a guard returning the invalid score would be tidier.TrackOnlyPID'skalSeedDtDtsisOptionalSequencebut de-facto required (the only version throws without it).
- The renamed
Verified 🟢 (checked, no action needed)
- 🟢 Cross-repo dependency satisfied:
KalSeedDtDtproduct,TrackDtDtproducer, and dictionary entries (classes_def.xml) are all in Offline main (merged via #1873);slope()accessor matches the module's use. - 🟢 The v1/TrkOnlyPID SOFIE headers are batch-1, float32 exports generated 2026-08-04 — after the MLTrain#9
mixed_float16/batch fixes, so the committed artifacts embody the corrected training. - 🟢 Feature parity verified where checkable:
dtis exact (EventNtuple fillstrkcalohit.dt = tch._udt; the module usestchs._udt);fitcon = fitConsistency();nhits = nHits(false) = hits().size()and the null-ambiguity count match EventNtuple'sTrkInfodefinitions; theMaxDEgate sign is consistent between v0 and v1 paths. - 🟢 Offline API usage checked against main:
TrkCaloHitSeed::_udt/_rptoca,KalSeed::nHits(bool),nearestSegment(double),TrkStrawHitSeed::strawHitState(),WireHitStateordering — all exist with the assumed semantics. - 🟢 The
kalmanOKfit gate and the calo-cluster/momentum guards are preserved from the old module; v1 adds amom == 0guard the v0 path lacked. The per-collectionKalSeeds↔KalSeedDtDtssize cross-check throws with a clear message. - 🟢 Dual
KalSeed/KalSeedPtrinput handling is correct, and output alignment by index matches the "aligned by index" contract in the header comment. - 🟢 scons build:
make_pluginsglobs the new module and the mainlib already linksopenblas/ROOTTMVASofiefor the SOFIE calls. - 🟢 Production and mu2e-trig-config: no references to this module's configuration — the only downstream consumer is EventNtuple (finding 1).
Validation check
- Build/tests run: none — ArtAnalysis has no CI on either build system, and this review did not compile the code. Static verification against Offline/EventNtuple main via the GitHub API.
- Config contract check: fail — findings 1 and 5.
- Cross-repo consistency: fail pending the EventNtuple companion PR (finding 1); Offline side is satisfied.
Residual risk
- The v1 model's real-world performance depends on finding 4; everything else is deterministic breakage with deterministic fixes.
Author follow-ups
- Prepare and reference the EventNtuple companion PR (schema,
label:instancetags,TrackDtDtwiring); merge in lockstep (finding 1). - Restore the old weights as
data/TrackPID_v0.dat(finding 2). - Add the
TrackOnlyPIDcet_build_pluginentry (finding 3). - Show the slope-feature parity check (or retrain on
TrackDtDtoutput) and include build evidence in the PR body (finding 4). - Update the repo's own
prolog.fclto a working v1 example (finding 5).
This follows the updates made in Mu2e/MLTrain#9. I renamed the previous TrkPID model to v0 so the older model can still be accessed. I also updated the module to handle multiple parallel track collections to reduce the need for multiple module instances in the art jobs.