Skip to content

Fix CVT projectors for aligned geometry and harden failure reporting - #1460

Open
yugotra wants to merge 7 commits into
developmentfrom
cvt-full3d-projectors
Open

yugotra wants to merge 7 commits into
developmentfrom
cvt-full3d-projectors

Conversation

@yugotra

@yugotra yugotra commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Fix CVT Kalman projectors for aligned geometry and harden failure reporting

With alignment constants enabled, CVT transport used the aligned detector surfaces while the
analytic Kalman Jacobian projected perturbed states to idealized SVT planes and beam-axis BMT
cylinders. The filter therefore differentiated a different measurement function from the one used
for transport and residuals. In 5 T run-19060 data this produced broad, sector-dependent SVT
residuals and many failed filtered fits; ideal-geometry MC and 0 T data did not expose the defect.

This branch makes the analytic projector use the complete SVT plane and BMT cylinder geometry. It
also makes the resulting failure paths and output reporting explicit, fixes BMT status precedence,
and adds two independent ClusterFinder safety corrections.

Commits

commit change
c2d49aed8 project the analytic helix to the full aligned SVT plane
6bcc0a905 project to the BMT cylinder's actual displaced and tilted axis
d1f41beea exclude a null smoothed state instead of failing the complete track
eec27e687 report unavailable fail-safe covariance with the existing -999 sentinel
a0081aaf7 preserve BMT timing/channel rejection when applying non-rejecting HV annotations
37140ae54 fix the ClusterFinder final-strip bound and pool-exhaustion null dereference
5b1f63ac5 make the covariance-output test independent of an installed CLAS12DIR

The commits are separated by mechanism and can be reviewed independently.

Projector defect and fix

Normal state transport swims through the field map to an aligned measurement surface. The numerical
Jacobian in MeasVecs.H() perturbs the state and uses the analytic helix projector because swimming
ten perturbed states for every measurement and iteration is both expensive and numerically noisy.

The former analytic projector simplified the detector geometry:

  • SVT used only the plane's trace in x-y and discarded the plane normal's z component.
  • BMT intersected a cylinder centered on the beam line and ignored cylinder.getAxis().

The new SVT solver finds the helix path length satisfying the complete plane equation
n dot (r(l)-p) = 0. The BMT solver finds the crossing of the actual cylinder defined by its axis
and radius. It uses a Newton solve with a bounded bracket/bisection fallback and preserves the exact
legacy closed form when the cylinder axis is the beam line.

If a perturbed state cannot reach the requested surface, H() returns failure and the measurement
is skipped. It no longer substitutes a result on a different nominal surface.

The Jacobian remains approximate: it is an analytic uniform-field helix derivative while the
nominal state is swum through the measured field map. A swimmer finite-difference variant was
tested and performed worse because finite differences amplify swimmer noise. This change removes
the detector-geometry inconsistency; it does not claim to make the Jacobian field-map exact.

Null smoothing and covariance reporting

A single null smoothed state formerly propagated NaN into the track chi2 and discarded the entire
filtered fit. Null states are now treated like other unused measurements: they contribute neither
chi2 nor NDF, and their trajectory residual is reported as unavailable.

When a filtered fit still fails, reconstruction publishes the existing unfiltered seed as its
fail-safe track. That object formerly retained the seed-prior covariance, making it indistinguishable
from a fitted covariance. The bank writer now publishes -999 for unavailable covariance elements.
It does not discard the fail-safe track or alter its physics parameters.

BMT status precedence

HitReader first applied BMT timing/channel rejection statuses 1–4 and then overwrote them with HV
annotations 5 or 6. Because 5 and 6 are non-rejecting, enabling hvCuts could resurrect a hit that
had already failed timing. Rejection status now takes precedence over an HV annotation.

This makes masking the persistently unavailable BMT sector 1 layer 5 module safe. It does not
recover that missing measurement; it only prevents sparse off-module noise from being admitted.

ClusterFinder safety

The fixed-size hit map rejected strip 1200 through w < NSTRIP, while the scan would access beyond
the array if that guard were changed in isolation. The fill and scan bounds are now consistent, and
invalid strip/layer/sector coordinates fail explicitly. If more than 64 concurrent calls exhaust
the map pool, poll() allocates a fallback map instead of dereferencing null.

These changes are behavior-neutral for the production sample: real SVT strips stop at 256, the
largest BMT module stops at 1152, and the serial replay does not exhaust the pool.

Order-dependent energy smoothing and duplicate SVT-cell policy are deliberately not changed here.
Controlled tests found no residual-width benefit from snapshot smoothing and a small loss of
filtered tracks. Reversing all duplicate row pairs changed seven tracks but no track outcome or
good-track count.

Controlled validation

Run 19060, first 5,001 events, full CVT, rgd_fall2023, explicit beamSpotConst=2:

metric development fixed branch
published tracks 3,900 3,897
successful filtered fits 3,412 (87.49%) 3,770 (96.74%)
median chi2/NDF 6.27 1.80
SVT robust residual width 110.44 um 54.25 um
BMT robust residual width 227.29 um 159.60 um

The layerwise SVT cos(2phi) width amplitudes fall from 26–64 um to 0.7–4.7 um. The few tracks
lost by the geometry change were overwhelmingly pathological legacy fits; the gain is hundreds of
previously failed seeds becoming valid filtered fits.

On the complete 556,309-event run-19060 comparison, the isolated SVT plane correction reduces the
robust SVT width from 118.8 to 57.6 um and raises valid covariance from 87.39% to 94.93%. Track yield
is unchanged within 0.02%; the improvement is fit quality and track usability.

The same comparison on the first 5,001 events of run 19040 provides an independent 5 T
physics-target test under higher occupancy:

metric development fixed branch
published tracks 9,703 9,696
successful filtered fits 7,779 (80.17%) 8,732 (90.06%)
median chi2/NDF 18.78 5.13
standard good tracks 1,767 3,486
SVT robust residual width 142.89 um 77.33 um
BMT robust residual width 248.64 um 184.51 um

The SVT width cos(2phi) amplitude falls from 30–71 um to 1.3–4.3 um in all six layers. Matched by
event and seed, 1,134 tracks change from fail-safe to filtered while 181 change in the opposite
direction. This confirms that the correction generalizes beyond the low-occupancy empty-target
run 19060. Layer 6 retains a small median modulation without a corresponding width modulation;
that is a separate alignment or calibration observable.

Additional checks:

  • Eight analytic-geometry unit tests cover aligned and ideal SVT planes, displaced/tilted and
    beam-axis BMT cylinders, fallback behavior, and degenerate input.
  • Focused tests cover null smoothing, fail-safe covariance, BMT status precedence, the highest map
    strip, and pool exhaustion.
  • The CVT unit-test suite passes.
  • The 49-module build passes with Java 21.
  • Run-18316 0 T output is byte-identical before and after the projector changes.
  • Ideal-geometry MC is unchanged by the projector geometry and reaches 99.854% filtered fits in the
    20,000-event reference sample.
  • The final ClusterFinder bounds/pool commit produces a byte-identical 5,001-event run-19060 output
    relative to the preceding branch head.

Remaining detector limitation

BMT sector 1 layer 5 was effectively off throughout the studied running. Its raw occupancy is below
0.5% of neighboring modules, and CCDB reports 0.5 V with zero strip voltage. Masking that module in
low-pT MC reproduces the localized 180–240 degree fit-validity loss at the observed scale while the
SVT residual width remains stable. That acceptance hole is separate from the projector defect and
cannot be repaired in reconstruction.

@raffaelladevita

Copy link
Copy Markdown
Collaborator

Currently, the track propagation in the magnetic field is done with the swimmer. Therefore the changes to the helix-based propagation would have no effect. The change that may be critical is the check on the matrix H.

What problem was found with the swimmer?

yugotra and others added 6 commits October 20, 2026 22:18
helical/KFitter.smooth() returns null when setStateVecPosAtMeasSite fails,
i.e. when the swimmer cannot place the smoothed helix on that measurement
surface. Three places then consume smoothed states behind only a `skip`
check, and AMeasVecs.dh returns Double.NaN by contract for a null state:

    if (stateVec == null || ...) return value;   // Double.NaN

1. AKFitter.calc_chi2 null-checks only the first and last smoothed states,
   then evaluates every one of them. An intermediate null makes chisq NaN,
   runFitter sets setFitFailed, and the filtered fit is abandoned for the
   whole track. One unreachable intermediate surface discards a track whose
   other six measurements are good. A measurement can be excluded from the
   chi2 sum via `skip`; a null smoothed state could not be.

2. helical/KFitter.getChi2(1), the secondary (no-beamspot) chi2 published
   through Track.setSecondaryChi2, has the same loop and would return NaN.

3. AKFitter.setTrajectory computes a smoothed residual per measurement and
   would store the NaN in HitOnTrack, reachable from Track's KF trajectories.

Treat a null smoothed state as a measurement that contributed nothing:
excluded from chi2 and NDF in (1) and (2), and reported in (3) as -999 with
isUsed=false, which is the convention already used for a passive surface.

(1) is the behavioural fix. (2) and (3) are latent today - they are only
reached by tracks that (1) previously discarded - and are included so the
three sites agree. Verified behaviour-neutral: with (2) and (3) added, all
15,258 published tracks are identical to (1) alone, in both arms.

Measured on this branch. CLAS12 RG-D run 19060, 20,000 events, full CVT
chain (CVTFP/CTOF/EBHB/CVTSP), beamSpotConst=2, 15,258 tracks:

                                        before      after
  failed filtered fits (nKFIters==1)       745        587
  as a fraction                          4.881%     3.847%
  rescued (nKFIters 1 -> >1)                         162
  broken  (nKFIters >1 -> 1)                           0
  median chi2/NDF, tracks already fine   1.7428     1.7428

The gain is one-directional: no track's fit is broken by this. The rescued
tracks are ordinary rather than marginal - median NDF 5, median chi2/NDF
3.02, median pT 0.438 GeV. Only 1.33% of tracks change at all, and 162 of
those 203 are the rescues. No NaN appears in any FLOAT or DOUBLE field of
CVTRec::Tracks, CVTRec::Trajectory or CVTRec::UTracks, before or after
(4.16 M values scanned per arm).

nKFIters is the observable here rather than the published covariance. A
failed filtered fit falls back to runFitterNoFiltFailSafe, which publishes
the unfiltered seed prior, so cov_d02 is positive for every track on this
branch and cannot distinguish a real fit from a fail-safe one. runFitter's
iteration loop always runs a second pass after a successful first, so
nKFIters == 1 is exactly equivalent to a failed filtered fit.

The former behaviour is available as -Dkf.legacyNullSmoothFail=true for A/B
comparison; it should be removed once this has been exercised on more data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A2ymgxW5EqAmsepqFxb3XG
@yugotra
yugotra force-pushed the cvt-full3d-projectors branch from 3a83bde to 37140ae Compare October 21, 2026 17:51
@yugotra yugotra changed the title Fix CVT projectors for aligned SVT and BMT geometry Fix CVT projectors for aligned geometry and harden failure reporting Oct 21, 2026
@yugotra
yugotra force-pushed the cvt-full3d-projectors branch from 3a574e1 to 5b1f63a Compare October 22, 2026 00:29
@yugotra

yugotra commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

Dear Raffaella,

Thanks, and sorry for the slow reply. I did not see your comment and did not get email notification about it.

No problem was found with the swimmer used for normal state propagation. You are right that
helical/KFitter propagates the nominal state between measurement surfaces with the swimmer. Those
paths are unchanged by this PR.

The analytic helix is nevertheless used in production when the projector matrix H is evaluated.
helical/MeasVecs.H() computes each derivative numerically by perturbing a helix parameter and
placing the two perturbed states on the measurement surface. Although H() receives the swimmer,
the two placement calls explicitly pass null:

sv.setStateVecPosAtMeasSite(SVplus,  mv, null);
sv.setStateVecPosAtMeasSite(SVminus, mv, null);

That selects the analytic branch in helical/StateVecs.getStateVecPosAtMeasSite(). Before this PR,
the analytic SVT projection used only the plane's trace in x-y and discarded the plane normal's z
component. The BMT projection likewise assumed a cylinder centered on the beam axis. Those
assumptions are valid for the ideal geometry, but not after the alignment transformations are
applied. This is how I found the issue. The nominal state was therefore swum to the aligned surface while H differentiated a different, idealized surface projection.

The geometry fix keeps swimmer-based nominal propagation and changes only that analytic projector: it now
intersects the helix with the complete SVT plane and with the BMT cylinder's actual axis. The
Jacobian remains approximate because it uses a uniform-field analytic helix while the nominal
state is swum through the field map. The change removes the detector-geometry inconsistency; it
does not make the Jacobian field-map exact.

Is the H null check responsible?

I tested your suggestion directly on RG-D run 18528, first 20,001 events, decoded with 13.0.0,
variation: rgd_fall2023, and beamSpotConst=2. The middle arm keeps the 3-D projector but reverts
the H == null guard in KFitter and the corresponding failure return from MeasVecs. The last
arm also reverts both geometry-projector commits.

metric full PR 3-D geometry, no H guard old projectors
median chi2/NDF 4.145 4.144 16.657
median NDF 4.0 4.0 4.0
CVT tracks 32,410 32,410 32,421
BMT hits on track 473,692 473,692 473,692
SVT robust residual width 71.55 um 71.56 um 136.99 um
BMT robust residual width 172.03 um 172.02 um 240.85 um
matched SVT clusters 137,041 137,049 131,707
SVT residual median -1.68 um -1.67 um +4.14 um

The geometry-only arm is indistinguishable from the full PR. The null check therefore accounts for
none of the observed improvement in this sample. It is only failure-path hardening: without it, a
failed perturbed-state projection can silently produce a derivative from states that were not
placed on the requested surface. I can drop that guard or move it to a separate PR if you prefer a
strictly minimal geometry change.

The old-projector arm has nearly identical track yield, but its median chi2/NDF is four times worse
and its SVT and BMT residual widths are 1.91 and 1.40 times larger. That is the expected behavior
for a projector-matrix error: it changes the quality of the filtered fit much more than seeding or
the number of published tracks.

The SVT effect is present in all six layers:

layer full PR (um) 3-D geometry, no H guard (um) old projectors (um)
1 68.09 68.06 165.67
2 72.71 72.65 168.04
3 69.96 69.96 128.31
4 70.66 70.67 129.50
5 66.89 66.90 109.28
6 70.62 70.64 123.59

Residual widths are 1.4826*MAD of the per-cluster centroidResidual matched to a
CVTRec::Trajectory crossing, with |residual| < 500 um.
MAD is the median absolute deviation: take the median of the sample, then take the median of the absolute deviations from it.

MAD = median( |x_i − median(x)| )

The factor converts that into something on the same scale as a Gaussian σ. For a normal distribution, the absolute deviation from the mean is exceeded by exactly half the sample at the 75th percentile, so

MAD = Φ⁻¹(0.75) · σ = 0.674490 · σ → σ = MAD / 0.674490 = 1.4826 · MAD

So 1.4826 × MAD is a robust estimator of σ: for a truly Gaussian sample it returns the same number as the standard deviation, but it is computed only from medians, so it is almost completely insensitive to outliers.
The factor normalises the median absolute deviation to a Gaussian sigma, giving an outlier-insensitive width with no fit range to choose.

Run 18528 is a production-target run at about three times the occupancy of empty-target run 19060,
which explains why its absolute widths are larger than the 54.25 um quoted earlier.

Why not use the swimmer inside H as well?

I tested that too, as the diagnostic that first established the mismatch. That variant is slightly
broader than swimmer-in-H() alone: it used the field swimmer for both the perturbed-state
projections and the state update, so its numbers bound the swimmer-based approach rather than
isolate it exactly.

On run 19060, 5 T, delivered alignment, SVT-only, 5,000 events, across the same 2,187 matched seed
signatures: the old projector produced 678 valid fits (31.00%), the swimmer-based variant 2,130
(97.39%), and the full-3-D analytic projector 2,181 (99.73%). Robust SVT widths were 52.83, 16.92
and 14.89 um respectively.

Those absolute widths are smaller than the 18528 numbers above because this is an SVT-only fit on
the lower-occupancy empty-target run; only the comparison within this three-arm sample is meaningful.

This confirms the geometry mismatch independently, but finite differences amplify the swimmer's
numerical precision noise: H requires two propagated states for each of five parameters at
every measurement and iteration. The swimmer remains the appropriate choice for the nominal state;
the full-3-D analytic intersection gives a more stable numerical Jacobian while using the same
aligned measurement surface.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants