Skip to content

Vertex extrapolation and track fitting share mutable TGeo navigator state that could lead to unexected failures #1371

Description

@kholoimov

Summary

FairShip track fitting and shipVertex extrapolation both use GenFit's
TGeoMaterialInterface. That interface operates on gGeoManager's current
TGeoNavigator, so fitting and vertexing share mutable boundary-navigation
caches and point-stack state.

With a tracking configuration that produces difficult low-momentum fits, an
exception or nonconvergent vertex extrapolation can be followed by a chain of
otherwise unrelated GenFit failures in later events. Starting a fresh process
at those later events succeeds, which shows that the event records themselves
are not sufficient to trigger the failure.

Full log: https://cernbox.cern.ch/files/spaces/eos/user/v/vkholoim/SHIP_smallest_example/shipreco_udate.log

Environment

  • GenFit: 2.3.0, build ha28ff26_10
  • ROOT: 6.40.02
  • Input: samples/sim_signal_Scalar_mixing_2.5_GeV_10_m_2mu.root
  • Geometry: samples/geo_signal_Scalar_mixing_2.5_GeV_10_m_2mu.root
  • Pattern recognition: AR

The geometry file predates the target.length_fixed metadata field. The
provided reproduction harness adds the legacy value, 158.64 cm, to a temporary
copy so current main can load it. It does not alter the detector geometry or
events.

Reproduction

VERY STRANGE BEHAVIOR. I did not manage to produce a fully reproducible result.

From the FairShip repository root:

Samples: https://cernbox.cern.ch/files/spaces/eos/user/v/vkholoim/SHIP_smallest_example
The deterministic early reproduction used no straw smearing:

pixi run python -u macro/ShipReco.py \
  -f samples/sim_signal_Scalar_mixing_2.5_GeV_10_m_2mu.root \
  -g samples/geo_signal_Scalar_mixing_2.5_GeV_10_m_2mu.root \
  -n 5000 --noStrawSmearing 2>&1 | tee shipreco.log

Typical diagnostics are:

RKTrackRep::Extrap ==> maximum number of iterations exceeded
RKTrackRep::RKutta ==> momentum too low
MaterialEffects::getMomGammaBeta - Energy <= mass

The GenFit messages say fatal flag 0, but this text is stale: the installed
binary executes setFatal(), while Exception::what() retains text formatted
before the flag was changed. KalmanFitterRefTrack catches the exception
internally, marks the fit unsuccessful, and returns, so Python does not receive
an exception from processTrack().

Control tests

The following controls isolate the cross-event state carrier:

Configuration Result
Start a fresh process at original event 2424 Events reconstruct normally
Cut the problematic entries from the original ROOT file and reconstruct only that subset The failure chain does not appear
Continuous fitting, veto and vertexing Failure chain begins
Continuous fitting only 2500 events complete
Continuous fitting plus veto, no vertexing 2500 events complete
Continuous fitting plus vertexing, no veto Failure chain reproduces at 2425--2427
Separate TGeo navigator for vertexing 2700 events complete without issues

This rules out intrinsically corrupt input events, the DAF instance, and veto
linking as the cross-event carrier. Random straw smearing changes the onset but
is not required.

The cut-file result is particularly suspicious. The subset contains the same
MC event records and detector hits as the entries which fail during the long
continuous run, but reconstruction succeeds when those entries are placed in a
new ROOT file and processed by a fresh ShipReco process. Individual low-momentum
or nonconvergent hypotheses may still be reproducible, but they do not trigger
the subsequent chain. Thus the failure depends on reconstruction history before
the problematic range, rather than only on the contents of those events.

For example, original entries 6070--6210 were copied without modifying their
event data and reconstructed separately. The 141-event job completed normally.
Original event 6088 still produced its two invalid low-momentum hypotheses,
showing that those individual bad fits belong to the event, but another
hypothesis fitted successfully and later events did not collapse. Events which
had veto or extrapolation failures in the long run also reconstructed normally
in the cut sample.

Expected behavior

A failed or nonconvergent vertex extrapolation should affect only that vertex
candidate. Track fitting in later events should be independent.

Actual behavior

Vertexing and fitting use the same process-global current TGeo navigator. Under
the triggering tracking configuration, failures become correlated across later
events, while restarting reconstruction at those events—or reconstructing a
ROOT file cut to contain only that range—succeeds. This dependence on preceding
events is unexpected because each event's tracks and fit objects are intended
to be independent.

Proposed fix

Create a dedicated navigator for vertexing and always restore the fitting
navigator:

navigators = ROOT.gGeoManager.GetListOfNavigators()
fit_navigator = ROOT.gGeoManager.GetCurrentNavigator()
fit_navigator_index = navigators.IndexOf(fit_navigator)
vertex_navigator = ROOT.gGeoManager.AddNavigator()
vertex_navigator_index = navigators.IndexOf(vertex_navigator)

try:
    ROOT.gGeoManager.SetCurrentNavigator(vertex_navigator_index)
    Vertexing.execute()
finally:
    ROOT.gGeoManager.SetCurrentNavigator(fit_navigator_index)

This keeps the geometry and field shared while isolating mutable navigation
state. Calling TGeoNavigator::ResetAll() was tested and is not a safe
substitute; it caused a ROOT segmentation fault.

Important

I'm not sure that my solution is the best one, but it fixes the problem which was encountered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions