Repository navigation
Mu2eKinKal clone copy constructors drop state; KKMaterial eloss-mode cast and a dangling HeCF4 material #2007
Description
These five defects came out of a static sweep of KFTrack/KinKal (KFTrack/KinKal#225). They are
in Offline's code and data, not in KinKal, so they're filed here. I rechecked all five at Offline
main 7bcb06ff67a4b97b2fd20418e89c1449136229e7.
None of them affects a production job today. Items 1 and 2 matter once anything calls
KinKal::CloneContext, which is the track re-instantiation path that KFTrack/KinKal#224 is staging.
Nothing in Offline calls it yet. Every mention of CloneContext is inside Mu2eKinKal/inc/KKTrack.hh (the copy constructor at :77 and two helpers at :103 and :116), and no .cc file constructs one.
1. 🟡 [S2] KKStrawHit and KKCaloHit copy constructors skip their ResidualHit base, so clone() drops the hit weight
Mu2eKinKal/inc/KKStrawHit.hh:55-67 and Mu2eKinKal/inc/KKCaloHit.hh:31-37 both list their own
members and never name KinKal::ResidualHit<KTRAJ>(rhs). The base is therefore
default-constructed through its protected ResidualHit() {}, and the cached weight_ comes out
empty. clone() in both classes is std::make_shared<T>(*this), so every clone goes through this
constructor.
KinKal makes delegating to the base copy constructor opt-in (Detector/ResidualHit.hh:14). I
reproduced the effect with a minimal subclass that skips it. The clone reports the same
active(), the same nDOF() and the same residual as the original, but with zero weight:
good clone active=1 nDOF=1 resid.value=0.7 |weightVec|=82.0359 trace(weightMat)=3.64
bad clone active=1 nDOF=1 resid.value=0.7 |weightVec|=0 trace(weightMat)=0
Nothing downstream can tell the bad clone from a good one. That demo used a stand-in class, not
the two headers above. The headers were read, not run.
Suggested fix: add KinKal::ResidualHit<KTRAJ>(rhs), at the head of both initializer lists.
KKStrawHit.hh:47 already declares using RESIDHIT = KinKal::ResidualHit<KTRAJ>; and never uses
it, so that alias is available.
2. 🟡 [S2] KKStrawXing copy constructor loses the updater configuration, the material xings and the active flag
Mu2eKinKal/inc/KKStrawXing.hh:39-50 delegates to the four-argument constructor. That
constructor's fifth parameter, active, defaults to false (:34), and none of sxconfig_,
mxings_, fparams_ or active_ is copied. clone() at :51-67 patches only shptr_ and the
trajectory in ca_.
I ran this by compiling the real KKStrawXing.hh and KKStrawMaterial.cc against KinKal
134126b5, with a Mu2e straw and a 100 MeV/c electron:
src active()=true config().nsig_=3 config().maxdoca_=5 matXings().size()=2 |params()|=3.540e-02
XING copy(src) active()=false config().nsig_=-1 config().maxdoca_=0 matXings().size()=0 |params()|=0.000e+00
src.clone(ctx) active()=false config().nsig_=-1 config().maxdoca_=0 matXings().size()=0 |params()|=0.000e+00
nsig_=-1 and maxdoca_=0 are the StrawXingUpdater defaults (StrawXingUpdater.hh:10-11). The
nsig_ < 0 guard at KKStrawXing.hh:158 sits inside if(first). The normal meta-iteration
update passes first == false, so the clone doesn't throw. It carries on with an empty
configuration.
This breaks KinKal's own contract for the method it overrides. Detector/ElementXing.hh:48-54
says an overriding clone() must reassign the pointer members explicitly.
Suggested fix: copy the four members in the copy constructor, and pass rhs.active() through.
3. 🟡 [S2] KKMaterial.cc:20 casts an unbounded fhicl int straight into a three-valued enum
dmconf.elossmode_ = (DetMaterial::energylossmode)matconfig.elossMode();elossMode is fhicl::Atom<int> (KinKalGeom/inc/KKMaterial.hh:39), with no bound in its type
or its Comment. DetMaterial::energylossmode is {mpv=0, moyalmean, bethemean}. KinKal's
consumer (MatEnv/DetMaterial.cc:134-140) is an if/else-if chain with no invalid case, so any
other value falls through to MPV. Running it through KinKal:
setElossMode(2) -> ionizationEnergyLoss = -43.06791841
setElossMode(3) -> ionizationEnergyLoss = -38.06825137 <== out of range, silently == mpv
setElossMode(7) -> ionizationEnergyLoss = -38.06825137 <== out of range, silently == mpv
KinKalGeom/fcl/prolog.fcl:15 sets 1, so nothing is wrong today. But
IonizationEnergyLossMode : 3 in any job would quietly change the energy-loss model with no
diagnostic.
Suggested fix: reject values outside {0,1,2} in the KKMaterial constructor, or better, make
it a named enum parameter so fhicl validation rejects it.
4. 🟡 [S2] TrackerConditions/data/MaterialsList.data:19 defines HeCF4 from a CF4 that exists nowhere
HeCF4 0.0005331 0. 0. 2 0.334 Helium 0 0.666 CF4 1 -10 -20 -30 20.0 1.0 gas
CF4 is not defined in this file or in ElementsList.data. The file that
KinKalGeom/fcl/prolog.fcl:4-6 points MatEnv at is this one. KinKal's own copy of the list still
defines CF4 (in its ITracker block), and Offline's copy dropped it. KinKal resolves an unknown
component to a null pointer without throwing. Asking for HeCF4 against the production data
segfaults inside MtrPropObj. The KinKal side of that is item 1 on KFTrack/KinKal#225.
Nothing asks for it: git grep HeCF4 returns only this line, and every material name in the
prolog and in KinKalGeomMaker.cc resolves. Remove the row, or restore a CF4 definition.
5. ⚪ [S3] Analyses/test/materailsPropStudy.fcl:47 names a module that does not exist
module_type : materailsPropStudy, and no source file by that name exists anywhere in the tree.
The job cannot run. The file supplies a free-form material name from fhicl (:49), which makes
it the one place item 4 could have been reached, but it is dead.
Not checked: no art job was run. Items 1 and 2 matter only once CloneContext has a caller.
The executed output for item 1 comes from a stand-in class, and for item 3 from running KinKal
directly rather than through KKMaterial.
Activity
oksuzian commentedon Sep 24, 2026
One more item for this issue, in the same Mu2eKinKal code. I found it while measuring KFTrack/KinKal#225 item 2 (see KFTrack/KinKal#225, comment 5817222595). I established it by reading the code at Offline
mainacc62589e19d122ba5880bc49a9f425f1e4f1887; I did not run it.6. 🟡 [S2]
KKStrawHit::updateReferencechecks the branch of the old closest approach, not the new oneMu2eKinKal/inc/KKStrawHit.hh:164-172:The comment says the test exists to catch the new solution moving onto the wrong helix loop. But
:166measuresdzfromca_, the previous iteration's closest approach, which:174only replaces afterwards. The retry path at:171does usepca, which shows the intent. As a result:pcathat has jumped to a neighbouring loop is accepted in this iteration. It is caught only in the next call, if there is one.pcais already correct. That costs time but gives no wrong answer.Suggested fix:
double dz = straw().origin().z() - pca.particlePoca().Z();at:166.Not checked: how often a closest approach actually jumps loops between iterations in production fits. That decides whether this ever fires. This finding comes from reading the code, not from running it.
edcallaghan commentedon Sep 25, 2026
@brownd1978 do you have an opinion on item-2 here? IIRC we decided a while back that the missing state-copies were ok because they would be reassigned by any non-zero schedule ran after the clone.