Fix pathological alignment of dead modules for CVH - #47
Open
davidwalter2 wants to merge 3 commits into
Open
Conversation
… 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXjsuBb3HHCTK8L1jBx75j
Translation analogue of the glued-tilt (eb96cae) guard. Modules that were off during data-taking have unconstrained alignment DOF; besides garbage rotations, the stored constants can carry cm-scale translations (observed: TID r-phi face 402666798 z-shifted 1.3 cm, 5.3 mm out of its own disk plane, rank cms-sw#5 outlier of 16588 modules; genuine module-level misalignment relative to neighbors is sub-mm, p99.9 = 2.0 mm). The mis-placed surface makes the Geant4e propagation abort for every track carrying the module's dead-channel placeholder, producing a 3.2% data-only refit-failure hole at eta -1.6, phi -1.05 (hotspot 2 of the data/MC efficiency comparison). Detection at geometry load: per-module aligned-vs-ideal displacement compared with the local consensus (median displacement of same-layer neighbors within |dz| < 15 cm, |dphi| < 0.6); flag if the deviation exceeds moduleGarbageShiftThreshold (untracked, default 0.4 cm; 1e9 = off). Repair: surface rebuilt at ideal position + consensus displacement with ideal orientation (glued composite as well -- it is half-contaminated by a garbage face), and hits on flagged modules either re-inserted at the path position implied by the repaired surface (garbageShiftReorderHits, default true) or dropped. Re-ordering is required when keeping the hit: the stored hit order follows the garbage constants and the repaired surface can sit behind its neighbor, which the forward-only propagation cannot traverse. Inert on MC (ideal geometry gives zero displacements). Also adds CVHDIAG failure diagnostics at the three data failure points (propagation abort, hit-update failure, GN divergence) printing the failing surface and track kinematics. Validated on Run2016G SingleMuon data (6 files, 40k events): hotspot refit failures 17 -> 7 (= mirror-cell level), all 10 failures on 402666798 gone, +10 recovered healthy tracks, 99.78% of the remaining tracks bit-identical; drop and reorder policies give identical momenta (the re-included hit is the dead-channel placeholder -- no measurement existed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01992a1iMkSNB6fRiAA3BbAj
Review follow-up (Josh): the translation guard does fire for a glued module -- 402666798 is the r-phi face of glued pair 402666796 (the other five outliers are pixel endcap, which have no glued partner at all) -- so rebuilding the composite at ideal position + consensus displacement with ideal orientation was the wrong thing to do there. The two faces of a glued module are one rigid body, so a sane face fixes both the bad face and the composite exactly. Both guards now share a single repair: when exactly one face is bad -- by tilt vs ideal or by deviation from the layer consensus -- the bad face and the composite plane are rebuilt as anchor_aligned o anchor_ideal^-1 applied to their ideal surfaces. Composite<->face position, orientation and Rglued then take their ideal values by construction, so the plane reconstruction and the alignment parameter mapping see a consistent composite. The old composite rebuild left 402666796 0.57 mm and 2.7 mrad away from that relation (uncorrected: 2.5 mm out-of-plane). The tilt guard previously repaired only the composite, leaving the bad face's in-plane DOF taken from the garbage constants (369141862: 2.1 mm displaced plus the 0.75 rad rotation of its local axes); those are now anchored too. Ideal surface + consensus displacement survives only as the fallback for modules with no local reference: non-glued modules, and the so far unobserved case of both faces bad. Detection is unchanged -- the consensus is still needed, since 5 of 6 translation outliers are pixel endcap, and a glued-pair-local criterion does not separate (2016 geometry: |disp_mono - disp_stereo| p99.9 = 1.6 mm, largest genuine 2.1 mm, against 5.5 mm for the garbage face). Also fixes the root cause of the hit re-ordering. The mono/stereo inner/outer decision for a split matched invalid hit read the raw alignment constants; since 402666798 sits 5.4 mm out of its module plane, more than the face separation, the dot product came out with the wrong sign and the two placeholder hits were emitted back-to-front w.r.t. the surfaces the fit uses. Both producers now take those positions from surfacemapD_. With that, reorderGarbageShiftHits is the identity: over 20k Run2016G events it fired on 9 tracks, all on 402666798, changed=0 every time. Removed it, along with garbageShiftModules_, the garbageShiftReorderHits policy and the drop branch -- the per-track hit loop no longer touches a hash set. Validation (Run2016G Charmonium ALCARECO, 20k events, aligned geometry): propagation aborts 41 (guards off) -> 1 (guards on), the remaining one on FPix 352467464, which neither guard flags. Identical failure count before and after removing the re-ordering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Motivation
The data/MC comparison of the CVH refit efficiency (Z→μμ, pT 25–65 GeV) shows two localized regions where data is less efficient than MC, both absent in MC because MC uses the ideal geometry. Both trace back to the same origin: modules that were off during data-taking collect no hits, so their alignment DOF are unconstrained and the persisted constants can be arbitrary. The garbage constants then break the Geant4e propagation of the CVH refit for tracks crossing those modules — the muons themselves are perfectly reconstructed by standard tracking (full hit complement, high purity); only the refit aborts.
Both pathological modules turn out to be faces of glued pairs whose partner is sane, which is what the final design exploits.
What this PR does
Detection
Two independent flags per module, both untracked parameters with a default,
1e9= off:gluedGarbageTiltThreshold(default 50 mrad; genuine corrections are < 1 mrad, the pathological one is 0.75 rad).moduleGarbageShiftThreshold(default 4 mm).A plain aligned-vs-ideal position cut cannot separate garbage translations from legitimate coherent large-structure movements (up to ~1 cm), hence the consensus. Relative to it, genuine module-level misalignment is sub-mm over the whole tracker (p50 = 0.29 mm, p99 = 1.3 mm, p99.9 = 2.0 mm of 16588 det units), and exactly six modules exceed 4 mm in the 2016 UL alignment: 402666798 (the only one inside the muon acceptance) plus five dead FPix edge modules at |η| ≈ 2.7–2.8. Exactly one composite exceeds the tilt threshold: 369141860.
A glued-pair-local criterion was considered as a simpler discriminator and does not work: over the 3288 glued pairs, |disp_mono − disp_stereo| has p99.9 = 1.6 mm and a largest genuine value of 2.1 mm, against 5.5 mm for the garbage face — no clean separation. The consensus scan is needed anyway, since 5 of the 6 translation outliers are pixel endcap modules with no glued partner at all.
Repair
The two faces of a glued module are a single rigid body, so a sane face determines both the bad face and the composite plane exactly. Both guards therefore share one repair path: when exactly one face of a glued module is flagged (by either criterion), the bad face and the composite are rebuilt as
with the sane face as anchor. Composite↔face relative position, orientation and the
RgluedJacobian then take their ideal values by construction, which is what the downstream plane reconstruction and the alignment parameter mapping assume.Ideal surface + consensus displacement (with ideal orientation) survives only as the fallback for modules with no local reference: non-glued modules, and the so-far-unobserved case of both faces bad.
Hit ordering
The mono/stereo inner/outer decision for a split matched invalid hit read the raw alignment constants. Since 402666798 sits 5.4 mm out of its module plane — more than the physical face separation — that dot product came out with the wrong sign, and the two dead-channel placeholder hits were emitted back-to-front relative to the surfaces the fit then uses, which the forward-only propagation cannot traverse. Both producers now take those positions from
surfacemapD_, the surfaces the fit actually uses.Applied in both the single-track and two-track producers. Both guards are automatically inert on MC (
useIdealGeometry→ zero displacements).Validation
Run2016G Charmonium ALCARECO, 20k events, aligned geometry, single-track producer:
The single remaining abort is on FPix 352467464, which neither guard flags — a separate, pre-existing case.
Geometry-level closure, composite vs its sane face, in the sane face's frame:
Run2016G SingleMuon, 6 files, 40k events (earlier iteration of the guards, hotspot-2 focused):
Notes
skipHitlessSurfacesremoves the hotspot-2 class by construction (dead-module placeholders are never propagation targets); the guards remain complementary there for garbage constants on modules with valid hits.Review history
Josh asked whether the translation guard fires for glued modules at all, noted that if it does then rebuilding the composite at ideal position + consensus displacement is not right, and asked that the composite be tied to the good side instead — since the composite must keep defined position/orientation relations to the two sides or other alignment logic breaks. The third commit answers that: it does fire for one glued module, the composite is now anchored on the sane face by the same rule as the tilt guard, and the two separate composite rebuilds collapse into one. Fixing the split-hit ordering at its source then made
reorderGarbageShiftHitsprovably the identity (9 calls over 20k events, all on 402666798, no permutation in any of them), so it was removed together withgarbageShiftModules_, thegarbageShiftReorderHitspolicy and the drop branch — the per-track hit loop no longer touches a hash set. Net for that commit: 182 insertions, 218 deletions.🤖 Generated with Claude Code