Port CVH refit + WMass NanoAOD customizations to CMSSW_15_0_19_patch2#46
Draft
davidwalter2 wants to merge 178 commits into
Draft
Port CVH refit + WMass NanoAOD customizations to CMSSW_15_0_19_patch2#46davidwalter2 wants to merge 178 commits into
davidwalter2 wants to merge 178 commits into
Conversation
This reverts commit 2db7789.
… info for wedge modules, restore ability to apply linearized corrections to two track output, and additional disabled debugging
… hits), exclude loopers from two track fit, add muon matching and id info to two track fit
…tion
The job-scoped G4 world captures the geometry and magnetic-field ES
products at first start and cannot be rebuilt. Record the ES cache
identifiers and throw cms::Exception('Conditions') if either record's
IOV changed at a later run boundary (e.g. AutoMagneticFieldESProducer
picking a different payload after a magnet-current change), instead of
silently propagating with stale conditions.
Co-Authored-By: Claude Fable 5 <[email protected]>
…kout Dead code / leftovers: - Remove G4WentzelVIModelForCVH (compiled but never instantiated; 10_6 used it only inside if(false) blocks that the port already dropped) - Remove unreferenced SimG4Core/MagneticField/FieldParameters.xml and FWCore/PluginManager dep; drop -g CXXFLAGS from Geant4e BuildFile - Remove vestigial nValid(Pixel)HitsFinal counters (incremented but never read; were uninitialized in two-track jobs) - runCvhBplusJpsiK.py: delete dead _resolve_default_corfile(), correct the corFiles-default comment, update stale midPropagated help text, reject mode=both at config time instead of documenting the G4-singleton crash - Fix swapped FWD/BCKD verbose labels in G4ErrorEnergyLossForCVH; update stale species-disambiguation comment in JpsiKCandidateSplitter cfi Small fixes: - Kaon q/p diagnostic now gated on the B->J/psiK path (bCandIdxSrc set) instead of particle name, so plain muon single-track jobs stay quiet; atomic gate can no longer overshoot under concurrency - JpsiK channel cfis: useIdealGeometry defaults False (True + empty corFiles is the broken Stage-1-only combination the maker warns about) - FlattenedValueMapVectorTableProducer: always put all declared tables (empty when a type has no configured variables) instead of null-deref - ScalarPot3DEval: throw on missing r_scale header (was silently 1 cm) and on negative l_max - ScalarPot3D cfi no longer ships an EmptyESSource (conflicts with the standard field's record provider when process.load'ed); the standalone test cfg declares its own Consolidation (add the owning package instead of duplicating): - RecoTracker/TrackProducer added to the checkout; its BuildFiles reset to stock + deps actually needed by the WMass additions (PatCandidates for the pat::Muon dictionaries, MuonReco, VolumeBasedEngine); removes the 13 orphaned deps of the deleted residual-maker copies, -g, and the illegal plugin-library export - edm::ValueMap<vector<int>> dictionary now declared only in RecoTracker/TrackProducer; HitAnalyzer's duplicate (and the vector<float> one already provided by DataFormats/Common) removed - Delete HitAnalyzer's unused copies of MagneticFieldOffset / OffsetMagneticField and the dead includes referencing them Validated: scram b clean; 50-evt 2-stream J/psi CVH smoke, ~6000 propagations, 0 failures, mean m(mumu)=3.090 GeV. Co-Authored-By: Claude Fable 5 <[email protected]>
…ug trace
Traced the rare CVH refit failures (0.58% of J/psi ditrack candidates,
0.025% of single-track fits, 10k-event Run2016F ALCARECO) to runaway
propagation legs: with PropagationDirection=alongMomentum every hit-to-hit
leg is forced forward onto an INFINITE target plane, so a plane marginally
behind/tangent to the state (kinfit seed displaced from the Kalman
trajectory; worst at stereo-partner surfaces) sends the track out of the
ScalarPot3D 320 cm validity sphere where B=0 -> straight line -> it either
grinds to the G4 extrapolator table floor (Ekin=1 MeV, p=14.6 MeV) or gets
pinned at the G4 world boundary (r~17.5 m) -- and both were returned as
propagation *successes*, poisoning the sparse solve with NaN.
Geant4ePropagator (propagateGenericWithJacobianAltD):
- in-flight momentum floor (exit4[pdrain]): fail a leg once |p| < plimit
instead of returning a drained garbage state
- on-surface closure check (exit5[offsurface]): final position must lie
within 1 mm of the destination plane (kills world-edge zombie legs and
their zero-step "successes")
- backward-mode momentum flip-back: the AltD variant was missing the
flip-back the TSOS variant has, so anyDirection returned reversed
momentum. With this fixed, propagationDirection=anyDirection recovers
11/11 previously-failing candidates (9/11 genuine J/psi on the peak,
normal 3-iteration convergence); kept opt-in pending large-scale
validation of backward-leg (flipped-frame) transport Jacobians.
ResidualGlobalCorrectionMaker{G4e,TwoTrackG4e}:
- NaN fail-fast right after the sparse solve; previously NaN leaked into
the charge-sum check (copysign(1., nan)) and was miscounted as a charge
flip
- skip same-sign pairs before the kinematic fit (all two-track channels
fit a neutral parent; 0.41% of ALCARECO pairs are same-sign and aborted
deterministically after wasting a kinfit + one GN iteration)
- per-job fit summary counters (attempted/succeeded/fail-by-category)
printed from the destructor; abort prints carry seed kinematics and
iteration context
- dbgSeed/dbgIter/dbgHit stdout trace behind debugPerIterDump (survives
candidates whose fit aborts, unlike the tree branches)
Drivers:
- runCvhSingleTrack.py: catch up with the CvhMaster GlobalCache port
(CvhMaster PSet + per-stream RNG service, geopro dropped from the path);
the driver could not construct the producer at all since that refactor
- runCvhJpsi.py: new knobs debugPerIterDump, eventsToProcess,
propagationDirection
Validated on 10k events: succeeded fits and physics bit-consistent with
baseline (9784 entries, m=3.08933+-0.05307 in both); the 57 baseline
losses become 40 same-sign skips + 17 fast, correctly-labelled
propagation failures.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…gle-track GN step floor; multi-run field IOV fix Follow-up to ba2b3cf, validated on 100k Run2016F J/psi events. Geant4ePropagator: - propagateGenericWithJacobianAltD: exact frame conversion of all derivative/noise outputs for backward legs. They are accumulated on the momentum-flipped trajectory; under momentum reversal the CMSSW curvilinear basis transforms with P = diag(1,-1,1,-1,1) (q/p even, lambda odd, dphi even, xT odd, yT even), so return P*J*P for the state block, P*cols for the dB/dxi columns, P*Q*P for the noise matrices, and -dEdx (the retraced path gains energy where the physical track loses it). Per-leg gradients and Hessians are now exact for backward legs of any length. `backwardLegs` counter added to the job summary. - Geant4ePropagator_cfi: PropagationDirection default alongMomentum -> anyDirection. A/B on 100k events: every fit that succeeds with forward-only propagation is bit-identical under anyDirection (97,773 dimuon + 196,957 single-track candidates, zero exceptions); strict superset with +218 dimuon / +28 single-track recovered candidates and none lost. Failure rates: dimuon 0.263% -> 0.041%, single-track 0.033% -> 0.019%. With the frame conversion the recovered candidates' masses move by <= 0.44 MeV (median 0.05 MeV); a 2k slice against the pre-conversion run is bit-identical except exactly the recovered candidates. ResidualGlobalCorrectionMakerG4e (single-track): - momentum-floor safeguard on the Gauss-Newton step: scale the step only when the q/p update would push the reference momentum below 2 GeV or flip its charge. This eliminates the divergence where one unphysical update dragged p from 3.6 GeV to 0.54 GeV and the next propagation was refused: 100k validation shows 0 such failures in 197,022 tracks (was 1/4k), clamp active in 261 fits (0.13%), first-2k bit-compare against the pre-clamp reference 3923/3927 identical -- the 4 changed fits were silent divergences (edm 1e6..4e8) that now end finite, and the originally-failing track is recovered. A generic relative-step cap was deliberately NOT used: forward tracks legitimately repull q/p by O(100%) in early iterations. `clamped[step]` counter in the fit summary. Multi-run jobs (needed to run past ~30k events of a 2016F file): - ParametrizedMagneticFieldProducer: cache the field and return the same shared object for every IOV. The parametrized field is a pure function of the configuration; IdealMagneticFieldRecord rolls every run through its RunInfoRcd dependency and previously rebuilt an identical product, which the CVH G4 world (holding the raw pointer job-long) could not tolerate. The volume-based (useOpera3D) path still rebuilds per run. - CvhMasterThread: on a run-boundary cacheIdentifier change, re-fetch the field product and accept the boundary iff it is the same object the G4 world was built with; only a genuinely rebuilt product throws. Validated across runs 278769 -> 278808. Drivers: propagationDirection option defaults to anyDirection (both drivers); runCvhSingleTrack.py gains numberOfThreads. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
The per-candidate global-parameter Hessian H = 2 J^T R J has rank rank(Vinv) - nstatefree ~ ndof (+1 mass row), i.e. 16-45 for J/psi candidates, while hesspackedv stores the dense nParms(nParms+1)/2 triangle (~570 kB/candidate at 360 scalar-potential modes, ~97% of the grads output; ~50 TB extrapolated to 2016 F+G+H). New untracked options fillGradsFactored (default False) and hessFactorTol (default 1e-8, the fidelity floor of the float32 packed storage): eigendecompose hess in double precision at the fill site, keep modes above tol*lambda_max, and store B (nRank x nParms row-major, rows sqrt(lambda) v^T by decreasing lambda, convention H = B^T B) in branches nRank/nFactor/hessfactorv plus the truncation monitor hessdroppedmass. gradv is unchanged and now also written when only the factored storage is enabled; hesspackedv stays available via fillGrads for A/B validation. Validated on the 50-evt ALCARECO smoke (both storages on, mass constraint, lmax18 coefficients): nRank = ndof..ndof+3, dropped eigenvalue mass ~1e-10, per-candidate and accumulated-global-Hessian reconstruction at the float32 floor (~1e-7), 566 -> 60 kB/candidate (9.4x). Downstream accumulation becomes a syrk: hess[gi,gj] += sum_k B[k,i] B[k,j]. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UvHBa3aKnPnkKZ7GHRDQLv
beginRun declared a local const double dtheta, shadowing the member the runtree branch points to, so the branch held stack garbage (~1e-310 denormals) for all module rows -- and made runtree differ spuriously between output files. Assign the member instead, clamping the dot product to [-1, 1] so acos cannot return NaN for numerically parallel axes. Verified on the ALCARECO smoke: no NaNs/denormals, field-mode rows stay 0, median aligned-vs-ideal local-x rotation 1.9 mrad (99% < 15 mrad, plus a genuine large-rotation tail of 132 pixel modules), and runtree is now bit-identical across stream output files. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UvHBa3aKnPnkKZ7GHRDQLv
…egion All tracker modules lie strictly inside the ScalarPot3D validity sphere (outermost module corners at R ~ 295 cm vs. R = 320 cm), so a state outside it is unambiguous proof of a runaway/wrong-way leg. Check MagneticField::isDefined() after every G4 step in propagateGenericWithJacobianAltD and fail the leg within one step (<= 10 mm) of leaving -- metres earlier and thousands of steps cheaper than the momentum-drain / off-surface backstops, and long before the B=0 fallback region can silently straighten the trajectory. For field models defined everywhere (volume-based map) the guard never fires. New exit6[fieldbound] counter + per-failure kinematics print. Validated: with forward-only propagation forced, all 11 known runaway candidates are caught exactly at the sphere crossing (p still 3.2-17 GeV, pathLen 2.4-3.1 m, vs. draining to the 1 MeV table floor after 3.5-7 m or 18+ m world-edge flights before); 50-event smoke with the anyDirection default shows zero guard activations and unchanged fits. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…, in-loop chi2 fix - nIters / edmConvergence configurables (existsAs-guarded, defaults 10 / 1e-5 reproduce the baseline exactly), exposed in both test drivers. - Optional Gauss-Newton step damping (gnDampAfter/gnDampFactor, default off): remedy for limit cycles between chi2-degenerate states observed in ~8% of single-track fits (at-cap fraction 16% -> 4.4% with gnDampAfter=6); a half-step from a 2-cycle endpoint lands on the midpoint. - debugPerIterDump: per-iteration chisq/EDM trajectory vector branches, mirroring the two-track maker; edmvalref_iter (the actual convergence criterion) added to the two-track dump as well. - Fix: chisqval/deltachisqval were never filled inside the iteration loop (dead members) -- now track the realized chi2 at the current linearization point plus the predicted step change, as in the two-track maker. The final stored chisqval is still overwritten with the residual-projector value after the loop, as before. Validated bit-identical (7856/7856 fits) against the pre-change reference at default settings. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
… modules Modules that were off during data-taking collect no hits, so their alignment is unconstrained and the persisted constants can be arbitrary. Observed in the 2016 alignment: TIB glued module 369141860 has its rphi face rotated ~49 deg against its partner (ideal: exactly anti-parallel), which contaminates the composite frame used to anchor the glued-surface reconstruction and turns the module into a propagation booby trap (the dominant source of the runaway-leg fit failures). If the aligned composite orientation deviates from the ideal one by more than 50 mrad -- far beyond any genuine alignment correction -- rebuild the composite frame anchored on the component face with the smaller aligned-vs-ideal tilt, composed with the ideal component->composite transform, and emit a LogWarning. In the 2016 UL alignment this fires for exactly one module (369141860: mono tilt 0.752, stereo 0.0016); with the repair, 8 of the 11 hotspot candidate fits succeed even with legacy forward-only propagation. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
Diagnostic EDAnalyzer used for the dead-module forensics behind the CVH fit-failure studies. Three modes: - detids: dump position/normal/glued-partner/isStereo for a given list of DetIds, plus SiStripQuality bad-module status. - scanAllGlued: check parallelism of the two faces of every glued composite in the tracker. - compareIdeal: sweep all modules comparing aligned vs ideal geometry (via the "idealForDigi" label), flagging orientation (sin > 0.02) or position (> 0.5 cm) outliers. The 2016 UL census over 16588 sensors found 34 outliers: the one active booby trap (369141860, TIB, 49 deg), one harmless live tilted module, and ~30 latent dead FPIX modules outside the acceptance. BuildFile: add CalibFormats/SiStripObjects + CalibTracker/Records for the SiStripQuality lookup. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
The pixel hit-quality criterion was hard-coded as !isOnEdge && sizeX > 1, demoting failing hits to inactive (module still traversed for material, no measurement). Make both parts configurable (existsAs-guarded in the base class, defaults preserve the legacy behaviour bit-identically): - keepPixelEdgeHits (default False): lift the boundary veto for clusters touching the sensor edge. - pixelMinSizeX (default 2): minimum cluster size in x; 1 admits all clusters including single-pixel-in-x. Both makers print a per-job pixel hit-quality summary (seen/onEdge/sizeX1/demoted) alongside the fit summary. Exposed in runCvhJpsi.py and runCvhSingleTrack.py. Findings on 100k J/psi ALCARECO events (Run2016H): - 3.7% of valid pixel hits are on-edge, 31.4% fail sizeX>1; about half of the edge clusters are also single-pixel in x. - Edge hits only (keepPixelEdgeHits=True): recovers 1.9% of pixel hits; failure rates, at-cap fraction and Jpsi mass unchanged; sigma_m -0.3% on affected candidates. - All pixel hits (pixelMinSizeX=1 + keepPixelEdgeHits=True): hard failures down 3x (single-track 36->12 of 197k) with pathological fits rescued; core mass resolution -1.4%, sigma(q/p) -1.1%; robust mass-scale shift +0.05 +- 0.03 MeV. But limit cycles worsen (single-track at-cap 15.8%->17.7%, forward 10.7%->15.5%) and q/p on converged gainers moves by median 2 sigma of the baseline error with only ~1% error-bar reduction -- the CPE position errors of unshared/edge clusters are underestimated. Defaults therefore keep both cuts for production. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
Design plan in doc/global-material-model-plan.md: replace ~25k
per-module energy-loss parameters with O(50-100) parameters attached to
physical material volumes (active and passive), evaluated along each
track's actual path -- origin- and direction-independent by
construction. Material analogue of the parmtype-14 scalar-potential
field reduction.
Phase 0 deliverables:
- MaterialAuditAnalyzer: geometry-only DDDWorld + G4Navigator ray
tally over the tracker (path, x/X0, mass path, Bethe-Bloch dE for a
10 GeV muon) keyed by (logical volume, z-side, 2 cm r-bin, 10 cm
z-bin). The eta profile of total x/X0 reproduces the published
tracker material budget (0.46 X0 at eta 0, peak 2.14 at |eta| 1.5,
z-symmetric).
- runMaterialAudit.py: EmptySource driver on the same DB XML geometry
as the CVH refit drivers.
- makeMaterialGroups.py + data/materialGroups{50,100}.txt: the two
grouping tiers (41 z-symmetric groups / 82 z-split groups + catch-all)
as ordered classification rules (name regex + r/z windows + z-side).
Per-step classification is required because logical volumes are
shared across layers (PixelBarrelActive* serves all three BPIX
layers; TOBActive name digits are module types spanning layer pairs).
Passive material dominates as expected (TEC structure 14%, TIB supports
13% of total x/X0; each active layer 0.1-1%), which is the point of
the model: the legacy per-module parameters cannot describe it for
displaced or cosmic tracks.
Base.cc: comment note documenting why no separate dead-face repair is
needed in the glued-module guard (plane already rebuilt from the
repaired composite; verified bit-identical on 10k events).
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…es in the propagator - MaterialGroupModel (new): runtime classifier mapping a Geant4 step (logical volume, midpoint r, z) to a material group via the ordered rules of a materialGroups tier file; per-LV regex shortlists are memoized, per step only r/z/zside windows are evaluated. Carries the per-group scaling values k_g and an FD-injection knob. - sim::Field: abstract MaterialOffsetProvider next to the existing scalar dxi channel (SimG4Core stays independent of TrackPropagation). - G4ErrorEnergyLossForCVH: when a provider is attached, each step's energy loss is scaled by exp(dxi + k_g) with the group resolved from the step's own volume -- this makes the material correction volume-resolved and hence independent of track origin and direction. - propagateGenericWithJacobianAltD: two new defaulted arguments (matGroups, groupJacOut). Per-group d(state)/dk_g columns are accumulated with the same transport recursion as the integrated dxi column, so the sum over groups equals jac.col(8) exactly; P-conjugated on backward legs; provider set/cleared symmetrically on all exits. Null arguments leave the legacy path untouched. Validation (J/psi ALCARECO): V0 bit-identity with the model off (19678/19678 tracks on 10k events); V1 FD closure fd/analytic = 1.0005 across 8 streams (forward-difference truncation at eps = 1e-3) with analytic d(q/p)/dk spanning 5e-8..8e-5; V2 sum identity to 2.6e-15 relative over ~450k propagation legs. See Analysis/HitAnalyzer/doc/global-material-model-plan.md. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…n both makers globalMaterialModel flag (exclusive switch, requires materialGroupsFile): replaces the per-module material parameters (parmtype 7, one per glued module) with parmtype-15 sentinel entries, one per material group of the loaded tier file -- mirroring the parmtype-14 scalar-potential field modes (sentinel runtree handling, global-index resolution next to resolveGlobalIndices, corFiles update path). Gradient assembly (single-track + two-track makers): the per-hit eloss block becomes nGroups columns -- the propagator's per-group d(state)/dk_g columns for crossed groups, zeros elsewhere -- with shared global indices collapsed by the existing index map, the same mechanics as the field-mode block. doRes resolution-slot offsets generalized. With the model on, the leg-constant dxi is zero and the per-step provider applies the current k_g, synced from corparms_ per event. The MaterialGroupModel is owned by the base class (both makers share it); the single-track maker keeps the Phase A validation knobs (materialFDGroup/materialFDEps + inline V2 check). Validation (J/psi ALCARECO, all k_g = 0): single-track q/p bit-identical 3928/3928 vs legacy registration; dimuon mass+pt bit-identical 1958/1958 vs the legacy 100k baseline; per-track Jacobian grows by exactly the expected 42-groups-minus-per-module-slots; stored gradient magnitudes physically ordered (tib_support largest, matching the Phase 0 material weights); V2 sum identity 2.4e-15 with the model active. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
- materialGroupsFile / globalMaterialModel (+ single-track materialFDGroup/materialFDEps validation knobs) for the global material model, both drivers. - useOpera3D now also routes the labelled 160812 field into the CPE ESProducers (stripCPEESProducer, StripCPEfromTrackAngleESProducer, siPixelTemplateDBObjectESProducer, templates), matching the production nano_cff wiring and the 10_6 cross-release drivers -- previously the hit re-evaluation used the default field while the propagator used the grid. Used for the 10_6-vs-15_0 cross-release validation (agreement at few-1e-6 per track on identical input). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
- Seed the parmtype-15 corparms entries from the k_init column of the groups file (genuine multiplicative deltas, unlike the parmtype-14 absolute coefficients whose seeding stays disabled). corFiles deltas accumulate on top; also the injection route for the V3 closure. - Plan doc: application contract resolved with the measured V3 numbers. Re-seed refits once per calibration iteration; the linearized jacref application between iterations is good to ~1% of the correction for |k| <~ 1% (error growing linearly, ~3% at k = 0.05). Measured caveat: the stored Hessians are Gauss-Newton, and with dE ~ e^k the own-parameter gradient response is (H + g) k -- verified exactly (predicted ratio -0.591 vs measured -0.592 on the raw-data tib_support pull), so iteration-1 deltas far from the minimum are not final; the corFiles iteration converges regardless. V3 closure summary (2k J/psi data events, tib_support injections): single-track fit response k_hat = 0.01013 for injected 0.01; dimuon unconstrained one-parameter recovery -0.0089; difference-fit identity dg = H k to 0.1-1% in all off-diagonal components. Fit-side aggregation + solve tool: calibration_studies/global_corrections/fit_global_grads.py. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…-hit propagation With both global correction blocks (parmtype-14 field modes, parmtype-15 material groups) the propagation-leg boundaries no longer carry any parameter attribution. Two new options (default off, bit-identical when unset) complete the decoupling: perStepFieldModes: the scalar-potential correction is applied per Geant4 step and the per-mode d(state)/dc_i columns are accumulated in the propagator (sim::FieldModeProvider abstract base next to the material provider; ScalarPotFieldModeProvider adapter over the basis evaluator, with corparms passthrough and an FD-injection knob). ONE basis sample per step, taken at the predicted step midpoint (5 mm lookahead at the 10 mm step cap), feeds BOTH the applied offset and the derivative columns -- consistent by construction. Removes the per-leg piecewise-constant sampling error of the legacy chain rule (~0.04 mT at 2 cm legs, the order of the NMR accuracy target). Per-mode columns are P-conjugated on backward legs like the dB columns. skipHitlessSurfaces (requires globalMaterialModel): dead-module placeholders and quality-demoted hits are dropped from the fit hit list, so propagation goes hit to hit. Statistically exact (process noise composes across the merged leg); the material of skipped modules is still crossed by the G4 steps. Validation (2016 J/psi data): defaults off and perStepFieldModes with zero coefficients both 100% bit-identical (3928/3928 tracks); per-step FD closure (provider injection, qop/lambda/phi comparison, per-mode eps scaled by basis amplitude) closes phi at the forward-FD limit for all modes and lambda at 1e-6 where non-vanishing; full package leaves tracks without hitless surfaces bit-identical and shifts the rest by median 4e-7 relative q/p (<nHits> 18.3 -> 16.5, at-cap unchanged); the 11 dead-module hotspot candidates all succeed with production defaults. Also: the single-track per-leg FD-closure block compared cartesian endpoint differences against curvilinear columns (latent bug, never exercised -- the validated B.5 closure ran in the two-track maker); the per-step block uses the proper qop/lambda/phi conversion, and runFDClosure/epsilonFDClosure knobs are now exposed in runCvhSingleTrack.py as well. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…tion variances The same per-step k_g that scales the mean energy loss now coherently scales the step's multiple-scattering variance, ionization-fluctuation variance, and radiation-length tally (e^k more material = e^k more scattering and fluctuation power). Only the CURRENT k values enter -- the two-step scheme decided in the plan: fitted coefficients reweight the next refit iteration, the fit never differentiates through the weights. The step-group classification is hoisted ahead of the MS block and reused by the per-group column accumulation (one classify per step, new MaterialGroupModel::offsetOf); the legacy dms/dioni knobs compose on top unchanged. This restores, group-resolved and origin-independent, the coherence the legacy per-module scheme had (with doRes off, the module xi scaled MS/ioni via dms = dioni = dxival) and the M0-only global model had dropped. Supersedes the per-module resolution parmtypes 10/11 for the material-driven part; decoupled per-group variance parameters remain a documented fallback if the M1 resolution closure fails. Gates (2016 J/psi data, 2k events): k = 0 bit-identical including covariances (3928/3928); k = +5% injected on TIB supports grows sigma(q/p) by +0.69% in the barrel, +0.52% in the transition and +0.00% forward -- the exact eta footprint and magnitude of the TIB-support MS share; refit central values shift by median 0.12 sigma from the reweighting. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
- perStepFieldModes now defaults to True (maker + drivers): per-step application/attribution is strictly more accurate than the per-leg chain rule and bit-identical for the fitted states at zero coefficients; the stored parmtype-14 Jacobians change at the per-leg sampling-error level. - skipHitlessSurfaces now defaults to the value of globalMaterialModel: hit-to-hit propagation is on whenever the global material model is on (the only configuration where it is well-defined), off otherwise; explicitly forcing it without the model still throws, and the drivers auto-disable their default-True when the model is off. Direction-mode study under the full package (10k J/psi data, globalMaterialModel + hit-to-hit + per-step field): single-track fits are IDENTICAL between alongMomentum and anyDirection (same 4 failures of 19682, zero backward legs in 2M propagations -- direction choice is now irrelevant there); the dimuon fit still needs anyDirection for 4/9801 candidates (0.04%, valid-plane order flips after the vertex-constrained update; 8 backward legs in 1M propagations recover all of them). Forward-only dimuon failures drop 0.26% -> 0.04% relative to the pre-package code: the dead-module phantom-plane class is eliminated at the source, anyDirection remains as a free safety net. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
globalMaterialModel now defaults to True in both CVH drivers, with materialGroupsFile defaulting to the tier-50 grouping file resolved from the release (src/Analysis/HitAnalyzer/data/materialGroups50.txt). Together with the existing defaults this brings up the complete leg-structure-free configuration with no extra arguments: parmtype-15 material groups (replacing the per-module block), per-step field attribution, hit-to-hit propagation, and M1 coherent variance scaling. Set globalMaterialModel=False for the legacy per-module parameterisation; materialGroups100.txt selects the relaxed tier. Deliberately driver-level only: the maker-side default stays off, so non-driver configurations (e.g. the NanoAOD production customisation) keep the legacy parameter model until the coordinated migration. Smoke (500 events, no material arguments): 42 parmtype-15 groups registered, zero parmtype-7 entries remaining, 124/124 candidates fit. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…nels Candidate-driven driver over the stage-1 V0 ALCARECO (persisted *Resonances collections), KS -> pi pi and Lambda -> p pi, using the existing PiPi / ProtonPi cfis. Full global-model defaults (parmtype-15 material groups, per-step field modes, hit-to-hit propagation) -- the first consumer of the displaced-track channels the origin-independent corrections were built for. Geant4ePropagator momentum floor exposed as ptotLimit, default 0.05 GeV (the 10_6-era V0 lesson: soft daughters dominate the failure accounting at the 0.5 GeV default). First results (2016G Charmonium stage-1 test sample, repacked split=1): - KS: 11 037 candidates, 3.65% failures (perigee starting state); core mass 497.57 MeV (PDG 497.611), 79% core fraction. - Lambda: 4 230 candidates, 13.2% failures; core mass 1119.2 MeV (PDG 1115.7), 73% core fraction. - Failure mix (KS): prop 2.0% (fieldbound runaway legs dominant), NaN updates 1.0%, charge flips 0.65% -- candidates for porting the single-track momentum-floor clamp to the two-track maker. - useStartingState default 'perigee': measured BETTER than 'midPropagated' on V0s (3.65% vs 7.20%) despite the latter being built for displaced starting states -- to be understood. CRITICAL for any 10_6-produced V0 ALCARECO (including the current stage-1 workflow, conditions 106X): the SiStripCluster branches are written at splitLevel=99 and deserialize EMPTY in 15_X (ROOT cms-sw#19773) -- the refit then fails at 98.7% with iteration-0 divergence. Repack in 10_6 with PoolOutputModule splitLevel=0 + overrideInputFileSplitLevels=True before running the 15_0 CVH, or produce the stage-1 with 15_X. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
Port of the single-track safeguard: when an update would flip a daughter's q/p sign (diverging step) or push its momentum below the configurable floor, the WHOLE joint step vector (both tracks + vertex, one coupled system) is rescaled by a common factor -- direction preserved, halfway to q/p = 0 on a flip, exactly onto the floor otherwise. Applied before the deltachi2/EDM bookkeeping; the charge-sum abort remains as backstop. clampMomentumFloor default 2 GeV (the single-track value, J/psi semantics unchanged); the V0 driver passes 0.1 GeV (above the propagation floor, below the soft-daughter spectrum). clamped[step] counter added to the fit summary. Validated on the 2016G SingleMuon V0 ALCARECO (pmlugato production): charge-flip aborts eliminated completely (KS 225 -> 0 of 24855 candidates, Lambda 81 -> 0 of 10444); total failures KS 4.57% -> 4.11%, Lambda 9.90% -> 9.44%. About half the clamped fits converge to candidates of usable quality (~50% in the signal mass core, median on the peak); the rest continue and fail later in propagation, now correctly classified under fail[prop] instead of aborting at the update. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…e NaN class) Root cause of the fail[nan] class, localized by instrumenting the solve abort on the V0 samples: in all 161 KS cases the residuals and Jacobians were finite and only the weight matrix was not, for candidates with 1-3-valid-hit daughters at decay radii ~5 cm -- a displaced V0 vertex sitting on the first-hit layer gives a (near-)zero-length first leg, Q ~ 0, and the plain Q.inverse() poisons the normal matrix with inf at the very first iteration (101/161 at icons=0, iiter=0). Fix: invert the per-leg process noise through its eigendecomposition with the eigenvalues floored at 1e-10 of the largest (absolute floor 1e-16). The zero-noise leg becomes an extremely stiff constraint instead of an exact one -- the mathematically correct limit (two states joined by a zero-length leg are rigidly coupled) with a finite weight. Normal legs sit far above the floor and are untouched at double precision. Validation (pmlugato 2016G V0 files): NaN failures 161 -> 0 (KS, 24855 candidates) and 163 -> 0 (Lambda, 10444); the prop class also improves (sane weights help marginal fits); total failures KS 4.11% -> 3.33%, Lambda 9.44% -> 7.67%. Recovered candidates are genuine on-layer decays: 53% (KS) / 65% (Lambda) in the signal mass core, medians on the peak. Combined with the momentum-floor clamp the V0 failure budget is now purely propagation physics (KS 4.57 -> 3.33%, Lambda 9.90 -> 7.67% overall; chargeflip and nan both exactly zero). Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…tion) The remaining V0 propagation failures split into two mechanisms with two remedies, both budgeted per candidate and honestly accounted: - Backtracking (failures at iiter > 0; dominant for KS): a runaway or drained leg after an update means the too-large Gauss-Newton step left the propagatable manifold -- the previous iterate propagated fine by construction. Restore the iteration-entry linearization state (reference states + per-hit layer states, snapshotted before the reference update) and redo the iteration with the step halved (up to 4 halvings). Trust-region behaviour: the fit only takes steps whose trajectories propagate. - Seed inflation (failures at iiter == 0; dominant for Lambda): the drained daughter is overwhelmingly the PROTON (median pT 0.29 GeV, beta ~ 0.35, ~60 MeV kinetic energy against 1/beta^2-boosted dE/dx) -- an end-of-range particle whose modeled energy loss consumes the seed trajectory anywhere along the path (flat ihit distribution). Inflate the failing daughter's seed momentum by 1.25x (up to twice) and let the hits pull it back down. Validation (pmlugato 2016G V0 files): KS failures 3.33% -> 2.48% (2814 halvings, 92 inflations), Lambda 7.67% -> 4.11% (629 inflations recovering the slow-proton class, 2016 halvings). Recovered candidates are genuine: 51%/62% in the signal mass core, medians on the peak. Cumulative V0 campaign: KS 4.57% -> 2.48%, Lambda 9.90% -> 4.11%, with chargeflip and nan classes at exactly zero and every remaining failure a budget-exhausted propagation case. backtracked[step] and inflated[seed] counters added to the fit summary. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
…wiring Replace the per-producer edm::GlobalCache<CvhMasterThread> with a single shared Geant4 master exposed as an EventSetup product, and wire the CVH muon refit into the custom NanoAOD (Category A of the 10_6 -> 15_0 port). Shared master (the enabler): - New CvhMasterRecord (DependentRecord over IdealGeometry + IdealMagneticField) + CvhMasterESProducer producing shared_ptr<CvhMasterThread> once per job, consumed by every maker via esConsumes. TYPELOOKUP_DATA_REG(CvhMasterThread). - CvhMasterThread: drop self-esConsumes/callConsumes; ensureG4Started() now takes the fetched geometry/field products (pointer-compare rebuild guard). - ResidualGlobalCorrectionMakerBase is now edm::stream::EDProducer<> (dropped GlobalCache + RunCache + the four global* methods + callConsumes); both makers' produce() use iSetup.getData(cvhMasterToken_).cvhMaster(). This lets heterogeneous makers coexist in one job (single-track nominal/ideal/ bs, two-track, V0, B->J/psi K) -- a second GlobalCache master would have hit the G4MTRunManagerKernel singleton and aborted. NanoAOD wiring (muons_cff.py / nano_cff.py): - tracksfrommuons + trackrefit (nominal, data+MC); MC-only trackrefitideal, trackrefitbs and mergedGlobalIdxs. Muon table gains cvh*/cvhideal*/cvhbs* scalar + vector branches (nominal+ideal reproduce the 10_6-tip contract; bs is a self-contained parallel set). - nano_cff.setup3DFieldForRefit brings up the ScalarPot3D field + Geant4e propagator + the shared cvhMasterESProducer and routes the field label / init file / per-stream RNG to all refit instances; entry points nanoAOD_addCvhMuon (data) and nanoAOD_addCvhMuonMC (MC). - Drivers runCvhJpsi/V0/SingleTrack/BplusJpsiK instantiate cvhMasterESProducer instead of the per-producer CvhMaster PSet. New runCvhCoexist.py test. Validated: scram b green (el9); runCvhJpsi regression identical (J/psi fits 100% success); runCvhCoexist runs two-track + nominal/ideal/bs single-track in ONE job on one master (0 aborts, all fit summaries 100%); edmConfigDump of data + MC NANO configs expands with all producers/branches and one master. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01TwFg9M7j9LonzrmRemvsrL
Expose the per-candidate backtrack / seed-inflation budgets of the leg-failure recovery block as maxBacktracks (default 4) and maxSeedInflations (default 2) parameters instead of the hardcoded literals, and surface them as VarParsing options in runCvhV0.py so V0 channels can tune the retry depth. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01TwFg9M7j9LonzrmRemvsrL
The MC CVH refit must use the same default CMSSW field as the simulation that generated the sample, not the accurate ScalarPot3D map (which is a data-only correction) -- see CLAUDE.md "custom field map for data only". setup3DFieldForRefit gains useScalarPot3D (default True): when False (MC) it no longer installs ScalarPot3DMagneticFieldProducer and does not route a field label onto the propagator / geopro / CPEs / makers / shared G4 master, so they all keep the default IdealMagneticFieldRecord field. The parmtype-14 correction-mode basis (scalarPotentialInitFile on the makers) is still set in both cases -- it defines the Jacobian modes stored in NanoAOD and is independent of the baseline field. nanoAOD_addCvhMuonBranches defaults useScalarPot3D to (not isMC), so nanoAOD_addCvhMuon (data) uses ScalarPot3D and nanoAOD_addCvhMuonMC (MC) uses the default field; overridable. Verified via edmConfigDump: data installs ScalarPot3D and labels every CVH consumer ScalarPot3DMf; MC installs no ScalarPot3D producer, leaves the propagator/master field label empty (default), and still wires the basis + nominal/ideal/bs refits. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01TwFg9M7j9LonzrmRemvsrL
Add the two-track (dimuon) CVH refit to the MINIAOD custom NanoAOD, as a
generic resonance-agnostic "Dimuon" candidate table (works for Z / J/psi /
Upsilon -- the resonance is selected only by the candidate producer's mass
window; the refit itself is a plain common-vertex fit, no mass constraint).
- New DiMuonTrackVertexCandidateProducer: opposite-sign muon-track pairs
(from tracksfrommuons) in a configurable mass window -> a
reco::VertexCompositeCandidateCollection with RecoChargedCandidate daughters,
the form the two-track refit consumes on its srcCandidates path.
- ResidualGlobalCorrectionMakerTwoTrackG4e: new produceValueMaps flag (default
False, so the ALCARECO TTree drivers are unchanged) makes it emit per-candidate
EDM ValueMaps keyed to srcCandidates -- kinematics always (corMass/corMassErr/
corPt/eta/phi + per-muon refit pt/eta/phi + edmval), and the global-fit payload
(globalIdxs/jacRefMu{Plus,Minus}/jacMass/factored Hessian) additionally when
fillGradsFactored is on. Sentinel-filled, one entry per input candidate.
- Generic ResidualGlobalCorrectionMakerDiMuonG4e cfi + a Dimuon
SimpleCandidateFlatTableProducer (CandVars + cvh* externalVariables) and a
FlattenedCandValueMapVectorTableProducer for the payload; wired into
nanoAOD_addCvhMuonBranches (data + MC) and setup3DFieldForRefit (field label /
init file / RNG). Shares the one Geant4 master with the single-track refit
(Stage A2). Data -> ScalarPot3D, MC -> default field.
Validated: build green; data + MC edmConfigDump wire the Dimuon table + candidate
producer + refit (field correct per data/MC; payload table scheduled only when
the flag is on); runtime on J/psi ALCARECO (runCvhDimuon.py) yields cvhMass
peaking at the J/psi (mean 3.07 GeV) with filled per-muon momenta and a
consistent 3 x nGlobalIdx Jacobian payload, 0 fit failures.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TwFg9M7j9LonzrmRemvsrL
The CVH fit state is (q/p, lambda, phi, d0, z0) at the PCA to the beamspot, but the maker only ever filled the momentum part -- refParms[3]/[4] (d0, z0) were left at 0 behind a longstanding "fix filling of position parameters" TODO, so the refit impact parameters were discarded. Fill refParms[3]/[4] from the converged state (cart2pca(refFts)) plus the reference-block update dxref[3]/[4], mirroring exactly how the momentum parameters are filled. Emit them as new corDxy / corDz ValueMaps and add Muon_cvhDxy / Muon_cvhDz (and cvhideal*/cvhbs* via the shared scalar-var helper) to the muon table. Impact parameters are w.r.t. the beamspot PCA (the fit reference); meaningful on the unconstrained trackrefit (the trackrefitbs variant constrains them). Validated on the hit-retained DY MINIAODSIM: cvhDxy tracks the muon's own dB(BS2D) to the micron level (median |cvhDxy - dB(BS2D)| = 2.7 um over 213 refit muons), cvhDz physical. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01TwFg9M7j9LonzrmRemvsrL
…handling fixes Single-track maker (ResidualGlobalCorrectionMakerG4e + Base): - One-pass two-hypothesis charge resolution: when the nominal fit's clamp catches a q/p sign crossing (nChargeFlipProtect>0 -- the fit "wanted" the opposite charge, the high-p mis-ID signature), re-fit the opposite charge and keep the lower-chi2 result. The per-track fit body is wrapped in a runHypothesis lambda so it can be run per charge in a single pass; only refFts is snapshot/restored. Gated by twoHypothesisCharge (default on); inert for well-measured (low/mod p) tracks that never trip the clamp. New branches nChargeFlipProtect, chargeHypFlipped; counter chargehyp[flipped]. - allowChargeFlipAboveP knob (default 1e9 = legacy/off): separates the always-on p>=2 GeV momentum floor from the sign-flip clamp, permitting genuine charge flips above a momentum. Kept as a study facility -- naive step-crossing diverges, so the two-hypothesis refit is the production path. - forceCosmic knob (default off): apply the outside-in cosmic handling regardless of track algo. Needed for split-track legs (cosmicTrackSplitter output, algo 'cosmic') which were otherwise fit with the beamline-PCA parameterisation; improves split-leg fit success and removes spurious backward legs from the mid-trajectory PCA reference. - cosmicSeedFromEntry knob (default off): seed cosmic legs from the muon-entry (higher-y) end and iterate hits downward -> fully forward propagation. Drivers (Analysis/HitAnalyzer/test): - runCvhCosmics.py: single-track CVH refit of cosmic ALCARECO (TkAlCosmicsInCollisions), field-status good-run filter, full global model. - runCvhCosmicsSplit.py: split upper/lower-leg test -- refit -> CosmicTrackSplitter -> refit each leg -> two independent single-track CVH fits for resolution / coherent-bias checks. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01LTMiAXeSNPnpgFd5Juk8jE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port of the CVH (Continuous Variable Helix) track-refit infrastructure and the WMass custom-NanoAOD producers from
WmassNanoProd_10_6_26to CMSSW_15_0_19_patch2 (el9_amd64_gcc12, Geant4 11.x), plus the development layered on top since the initial port: multi-threaded CVH, the ScalarPot3D spherical-harmonic field model with 5×9 transport Jacobians, the sparse-GBL two-track fit, and the new B±→J/ψ(→μμ)K± calibration chain.146 commits — 104 Josh Bendavid (original CVH development, cherry-picked chronologically with authorship preserved), 31 David Walter (port glue + field-model/threading/validation work), 6 Pietro Lugato (B→J/ψK chain), 2 Davide Bruschini (ForCVH Geant4-11.x physics classes), 2 Elisabetta Manca, 1 Kenneth Long.
131 files, +23.8k/−0.1k lines. Full tree builds clean with
scram b -j8.1. Base port
WmassNanoProd_10_6_26: the CVH residual-maker plugins (ResidualGlobalCorrectionMakerBase/G4e/TwoTrackG4einAnalysis/HitAnalyzer), the customizedGeant4ePropagator(double-precisionpropagateGenericWithJacobianAltDwith per-step MS/ionization error decomposition, Urban-model variance with α=0.999 truncation), value-map producers, and per-channel configs.*ForCVHphysics stack (fromWMassRefits_CMSSW_15_1_X): physics list (no MSC, no secondaries), energy-loss tables, Universal Fluctuation model, ErrorEnergyLoss, ErrorMessenger — adapted to G4 11.x thread-localG4ErrorPropagatorManager/G4ErrorPropagatorData(all G4e init deferred to first propagate per thread, mutex + thread-local guarded).FlatTableaddColumn type inference, ESHandle → ESGetToken,SiStripClusterInfoper-producer construction,Eigen::placeholders::all, dd4hep-default geometry kept.2. Post-port development
CvhMasterThread/CvhMaster/CvhWorker, SimG4CoreOscarMTMasterThread-style master/worker): makers are nowedm::stream::EDProducer<edm::GlobalCache<CvhMasterThread>>with idempotent per-TBB-thread worker setup. One G4 master per module instance → one maker per cmsRun job (drives the per-leg job design below). Replaces the-j1GeometryProducer path (kept for legacy tests).MagneticField/ParametrizedEngine): C++ port of themfsspherical-harmonic scalar-potential basis (Φ = Σ c_{l,m}(R/s)^l P_l^m(cosθ){cos,sin}(mφ), B = ∇Φ), loaded from a coefficient dump, with per-mode basis getters for calibration-fit Jacobians. Thread-safe by construction (immutable state, stack-local per-call cache — 7.5× faster than the naive evaluation) — this replaces the thread-unsafe PolyFit3D, which stays hard-disabled. Element-wise closure vsmfsat 1e-12; validity sphere R ≤ 320 cm. Published underIdealMagneticFieldRecordlabelScalarPot3DMf.sim::Fieldgains per-propagation additive field/material offsets so the G4 stepper and the analytic Jacobian see the same B+dB (no double counting; default path behavior-neutral).runFDClosure).MagneticFieldLabelrouting (opt-in, default""= standard behavior): SiPixel/SiStrip CPE ESProducers,TransientTrackBuilder,TrackRefitter,GeometryProducer,GeantPropagatorESProducercan all consume a labelled field, so the CVH chain runs on ScalarPot3D (or Opera3D grid viauseOpera3D) without touching default reco.SimplicialLDLTsolve replaces dense Hessian assembly — ~3.3× less memory, validated bit-identical.srcCandidates(VertexCompositeCandidates) input — one output row per persisted Stage-1 candidate; per-particle Geant4e propagation for V0 daughters (π/K/p mass hypotheses via a single PDG-table source of truth);V0CandidateProducerremoved.fillGradsSIGSEGV fixed (TTree::SetAutoSave(0)), TTree memory bounded, per-stream output files.FlattenedCandValueMapVectorTableProducerflattens per-muonValueMap<vector<int/float>>(CVH global indices + Jacobians) into FlatTables; eight scalar CVH ExtVars wired intomuonTable.3. B±→J/ψ(→μμ)K± chain (new)
JpsiKCandidateSplitter: splits Stage-1 B⁺ candidates into a dimuon collection + bachelor-kaon TrackCollection, with parallel B-candidate-index vectors and per-leg pdgId vectors for multi-channel mass-hypothesis handling;bCandIdxis written to both makers' trees for offline (run, lumi, event, bCandIdx) joining of the two legs.TwoTrackJpsiKMuMuG4e(J/ψ leg, mass constraint on) andJpsiKSingleTrackKaonG4e(bachelor leg, kaon hypothesis).runCvhBplusJpsiK.py: one maker per job (mode=dimuon|kaon), ScalarPot3D as base field (with fallback A/B switch),kaonAsMuonmass-hypothesis knob, and convergence-study instrumentation (configurable GN iterations/EDM threshold,midPropagatedstarting state, per-iteration debug branches).corparms_with absolute scalar-potential coefficients while the same field was already the process base field madegetCorrectionAt()re-add the full base field as dB (doubling B, halving refit q/p). Fixed by starting corrections at zero — corrections are now deltas on the base field. Open FIXME: parmtype-14 entries incorFilesare currently skipped on load, so fitted B-field corrections cannot yet be re-applied in a refit iteration.RecoTracker/TrackProducer/plugins/.Intentionally excluded
Validation status
scram b -j8clean (all packages, plugins refreshed)mfsreference at 1e-12 (element-wise, standalone test)SiStripClusterdeserialization bug, ROOT Replace boost with standard library equivalents cms-sw/cmssw#19773 — use 15_0-native inputs.)runCvhJpsi.py, ScalarPot3D field) on 50-event repacked ALCARECO with 2 threads/2 streams: ~6000 propagations, 0 failures (0ierr, 0 path-length aborts), mean m(μμ) = 3.090 GeV. Exercises the reworked G4 lifecycle, per-stream propagators, and the particle-set registry. The single-track kaon leg and the hadron fluctuation-table rebind are not yet exercised at runtime (needs a B→J/ψK input).Review findings and their resolution
A full-diff review (July 2026) found the issues below. All high/medium items are fixed on the branch (commits
cc97b5107c1,631b221040e) unless noted:Fixed (was: must fix):
— per-instance allocation hoisted out of the per-thread init guard; validated with a 2-stream smoke run.Geant4ePropagatorfluctsegfault with nStreams > nThreadsDuplicate— theDEFINE_FWK_MODULEnamesAnalysis/HitAnalyzercopies ofGlobalIdxProducer/TrackProducerFromPatMuons/TrackExtraRekeyerwere removed; the canonical (global,fillDescriptions, extended) versions live inRecoTracker/TrackProducer.— CVH muon branches moved behind an explicitmuons_cff.pydanglingtrackrefit/trackrefitbsreferences breaking standard NanoAODnanoAOD_addCvhMuonBranches(process)customization that validates the refit modules exist.—SiPixelTemplateDBObjectESProducerbreaking bare HLT-menu instantiationsfillDescriptionswithMagneticFieldLabeldefault""added;GeometryProducertolerates an absent parameter.Fixed (was: should fix):
5.
Multi-run G4 teardown— the G4 world is now an explicitly job-scoped resource:CvhMasterThread::ensureG4Started()starts it exactly once at the firstglobalBeginRun, it survives run boundaries, and teardown happens instopThread()at end of job. Since the world captures the run-1 geometry/field ES products, a mid-job IOV change (e.g. a magnet-current change viaAutoMagneticFieldESProducer) is detected via the ES cache identifiers and refused with a clearcms::Exceptioninstead of silently refitting with stale conditions (b5940c1ee84).6.
Hadron ionization variance using the muon dE/dx table—SampleFluctuations/SampleFluctuations2now rebind particle + table viaSetParticleAndCharge(proton-table scaling for hadrons) before the dE/dx lookup, consistent with the mean-loss model.lastMaterialinitialized.7.
Physics-list particle narrowing vs full-list re-instantiation— job-wide particle-set registry inG4ErrorPhysicsListForCVH: the propagator's no-arg instances reproduce the set configured viaCvhMaster.Particles, so no process-less particle definitions can appear on worker threads.8. Max path length 200 cm → 100 m: intentional, kept, now documented in-code — low-pT curling tracks (B→J/ψK bachelor kaons, helix radius ~50 cm) legitimately accumulate >2 m of path before intersecting the target surface. Note this is a deliberate loosening relative to both stock CMSSW and the 10_6 fork (both 200 cm).
9.
Debug— removed.std::coutinsim::Fieldconstructor10.
— added (nominal 3.8 T now cached); out-of-sphere queries emit aScalarPot3DMagneticFieldmissingsetNominalValue()LogWarninginstead of silently returning B=0. The C¹ blend to the volume-based field outside the sphere remains future work.Low-severity items — swept in
2a32331fe10: deadG4WentzelVIModelForCVHremoved;-gCXXFLAGS, unreferenced files/deps, vestigial counters, and stale comments/help text cleaned up; kaon q/p diagnostic gated on the B→J/ψK path; JpsiK cfiuseIdealGeometrydefaults fixed;FlattenedValueMapVectorTableProducersingle-type null-deref guarded;ScalarPot3DEvalheader validation hardened;EmptyESSourcemoved from the ScalarPot3D cfi to the standalone test cfg. Duplications consolidated by checking out the owning package (RecoTracker/TrackProducer): single home for theValueMap<vector<int>>dictionary and theMagneticFieldOffset/OffsetMagneticFieldhelpers, and its BuildFiles reset to stock + the deps the WMass additions actually need (fixes the missingDataFormats/PatCandidatesfor thepat::Muondictionaries and removes 13 orphaned deps of the deleted residual-maker copies).Deliberately kept: dead
computeLandau/SampleFluctuations2+ always-zerolandauDelta/landauWtuple slots (10_6 parity; removal is API churn across both makers — separate commit if wanted); HitAnalyzer'sMatRepStdSMatrix dictionary (unused by current code but possibly needed to read older trees); channel cfis remain driver-injected forscalarPotentialInitFile/CvhMaster(by design).Remaining work
🤖 Generated with Claude Code