Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
PackedTrack.cpp 26.27 KiB
/*****************************************************************************\
* (c) Copyright 2000-2022 CERN for the benefit of the LHCb Collaboration      *
*                                                                             *
* This software is distributed under the terms of the GNU General Public      *
* Licence version 3 (GPL Version 3), copied verbatim in the file "COPYING".   *
*                                                                             *
* In applying this licence, CERN does not waive the privileges and immunities *
* granted to it by virtue of its status as an Intergovernmental Organization  *
* or submit itself to any jurisdiction.                                       *
\*****************************************************************************/

#include "Event/PackedTrack.h"
#include "Event/PackedEventChecks.h"

using namespace LHCb;

namespace {
  auto getUnPackingVersion( const TrackPacker::PackedDataVector& ptracks ) {
    // Packing version
    auto pver = ptracks.packingVersion();
    // Work around for old data that (ab)used the DataObject version for the packing version and
    // does not have an explicit packingVersion saved in the data. In this case the ROOT schema
    // evolution simply fills this member with the default value, which is defaultPackingVersion().
    // We attempt to detect this here and correct the packing version to the older value.
    // This is only needed as long as we need support for older DataObject based persistency.
    const auto ver = ptracks.version();
    if ( pver == PackedTracks::defaultPackingVersion() && pver > ver ) { pver = ver; }
    return pver;
  }
} // namespace

void TrackPacker::pack( const Data& track, PackedData& ptrack, PackedDataVector& ptracks ) const {

  // check versions
  const auto pver = ptracks.packingVersion();
  if ( !isSupportedVer( pver ) ) { return; }

  // work around for the fact in old data there was no explicit packing version and
  // the DataObject version of the packed container was (ab)used for this.
  // To handle this for the old DataObject based event model and packing ensure
  // the two values are synchronised...
  // This should not be done when we migrate to the packing for the new event model where
  // DataObject should not be used anywhere...
  ptracks.setVersion( pver );

  if ( parent().msgLevel( MSG::DEBUG ) ) {
    parent().debug() << "pack : PackingVer=" << (int)pver << " DataVer=" << (int)ptracks.version() << endmsg;
  }

  ptrack.chi2PerDoF = StandardPacker::fltPacked( track.chi2PerDoF() );
  ptrack.nDoF       = track.nDoF();
  ptrack.flags      = track.flags();
  ptrack.likelihood = StandardPacker::fltPacked( track.likelihood() );
  ptrack.ghostProba = StandardPacker::fltPacked( track.ghostProbability() );

  //== Store the LHCbID as int
  ptrack.firstId = ptracks.ids().size();
  for ( const auto& id : track.lhcbIDs() ) { ptracks.ids().push_back( id.lhcbID() ); }
  ptrack.lastId = ptracks.ids().size();
  if ( parent().msgLevel( MSG::DEBUG ) ) {
    parent().debug() << "Stored LHCbIDs from " << ptrack.firstId << " to " << ptrack.lastId << endmsg;
  }

  //== Handle the states in the track
  ptrack.firstState = ptracks.states().size();
  for ( const auto* S : track.states() ) { convertState( *S, ptracks ); }
  ptrack.lastState = ptracks.states().size();
  if ( parent().msgLevel( MSG::DEBUG ) ) {
    parent().debug() << "Stored states from " << ptrack.firstState << " to " << ptrack.lastState << endmsg;
  }

  //== Handles the ExtraInfo
  ptrack.firstExtra = ptracks.extras().size();
  for ( const auto& [k, v] : track.extraInfo() ) { ptracks.extras().emplace_back( k, StandardPacker::fltPacked( v ) ); }
  ptrack.lastExtra = ptracks.extras().size();
}

void TrackPacker::pack( const DataVector& tracks, PackedDataVector& ptracks ) const {
  // check version
  const auto pver = ptracks.packingVersion();
  if ( !isSupportedVer( pver ) ) { return; }
  // pack
  ptracks.data().reserve( tracks.size() );
  for ( const auto* track : tracks ) {
    if ( !track ) continue;
    auto& ptrack = ptracks.data().emplace_back();
    ptrack.key   = track->key();
    pack( *track, ptrack, ptracks );
  }
}

void TrackPacker::convertState( const LHCb::State& state, PackedDataVector& ptracks ) const {
  auto& newState = ptracks.states().emplace_back();

  newState.flags = state.flags();

  newState.x  = StandardPacker::position( state.x() );
  newState.y  = StandardPacker::position( state.y() );
  newState.z  = StandardPacker::position( state.z() );
  newState.tx = StandardPacker::slope( state.tx() );
  newState.ty = StandardPacker::slope( state.ty() );
  double p    = 0.;
  if ( 0 != state.qOverP() ) p = 1. / state.qOverP();
  newState.p = StandardPacker::energy( p );
  //== Get the coded value in case of saturation, to code properly the error later
  p = StandardPacker::energy( newState.p );

  // convariance Matrix
  std::vector<double> err;
  err.push_back( safe_sqrt( state.errX2() ) );
  err.push_back( safe_sqrt( state.errY2() ) );
  err.push_back( safe_sqrt( state.errTx2() ) );
  err.push_back( safe_sqrt( state.errTy2() ) );
  err.push_back( safe_sqrt( state.errQOverP2() ) );

  newState.cov_00 = StandardPacker::position( err[0] );
  newState.cov_11 = StandardPacker::position( err[1] );
  newState.cov_22 = StandardPacker::slope( err[2] );
  newState.cov_33 = StandardPacker::slope( err[3] );
  newState.cov_44 = StandardPacker::energy( 1.e5 * std::abs( p ) * err[4] ); //== 1.e5 * dp/p (*1.e2)

  newState.cov_10 = StandardPacker::fraction( state.covariance()( 1, 0 ), err[1] * err[0] );
  newState.cov_20 = StandardPacker::fraction( state.covariance()( 2, 0 ), err[2] * err[0] );
  newState.cov_21 = StandardPacker::fraction( state.covariance()( 2, 1 ), err[2] * err[1] );
  newState.cov_30 = StandardPacker::fraction( state.covariance()( 3, 0 ), err[3] * err[0] );
  newState.cov_31 = StandardPacker::fraction( state.covariance()( 3, 1 ), err[3] * err[1] );
  newState.cov_32 = StandardPacker::fraction( state.covariance()( 3, 2 ), err[3] * err[2] );
  newState.cov_40 = StandardPacker::fraction( state.covariance()( 4, 0 ), err[4] * err[0] );
  newState.cov_41 = StandardPacker::fraction( state.covariance()( 4, 1 ), err[4] * err[1] );
  newState.cov_42 = StandardPacker::fraction( state.covariance()( 4, 2 ), err[4] * err[2] );
  newState.cov_43 = StandardPacker::fraction( state.covariance()( 4, 3 ), err[4] * err[3] );
}

void TrackPacker::unpack( const PackedData& ptrack, Data& track, const PackedDataVector& ptracks,
                          DataVector& /* tracks */ ) const {

  // check packing version
  const auto pver = getUnPackingVersion( ptracks );
  if ( !isSupportedVer( pver ) ) { return; }
  if ( parent().msgLevel( MSG::DEBUG ) ) {
    parent().debug() << "unpack : PackingVer=" << (int)pver << " DataVer=" << (int)ptracks.version() << endmsg;
  }

  // Try and detect changes that require the cached wrap IDs to be reset.
  // trigger a reset if a new packed container is detected, or if the
  // packed track object is the first in the packed container.
  // Not garanteed to be 100% perfect...
  if ( &ptracks != m_lastPackedDataV || ( !ptracks.data().empty() && &ptracks.data().front() == &ptrack ) ) {
    m_lastPackedDataV = &ptracks;
    resetWrappingCounts();
  }

  track.setChi2PerDoF( StandardPacker::fltPacked( ptrack.chi2PerDoF ) );
  track.setNDoF( ptrack.nDoF );
  track.setFlags( ptrack.flags );
  if ( pver > 2 ) {
    track.setLikelihood( StandardPacker::fltPacked( ptrack.likelihood ) );
    track.setGhostProbability( StandardPacker::fltPacked( ptrack.ghostProba ) );
  }

  // extract the first and last ID for the LHCbIDs
  auto firstId = ptrack.firstId;
  auto lastId  = ptrack.lastId;

  // Apply protection for short int wrapping
  if ( pver < 5 && ptracks.ids().size() > std::numeric_limits<uint8_t>::max() ) {
    firstId = m_firstIdHigh + ptrack.firstId;
    lastId  = m_lastIdHigh + ptrack.lastId;
    if ( lastId < firstId ) { // we wrapped in the track
      parent().warning() << "** ID index wrapped, first/last Id = " << firstId << " " << lastId << endmsg;
      m_lastIdHigh += 0x10000;
      m_firstIdHigh += 0x10000;
      lastId = m_lastIdHigh + ptrack.lastId;
    }
  }

  // Check firstId and lastID are sane
  if ( lastId > static_cast<int>( ptracks.ids().size() ) || firstId > lastId ) {
    parent().warning() << "Attempted out-of-range access to packed LHCbIDs. This is bad, do not ignore !" << endmsg;
    lastId = firstId = 0;
  }

  // Fill the LHCbIDs for this track
  std::vector<LHCb::LHCbID> lhcbids;
  lhcbids.reserve( lastId - firstId );
  std::transform( std::next( ptracks.ids().begin(), firstId ), std::next( ptracks.ids().begin(), lastId ),
                  std::back_inserter( lhcbids ), [&pver]( const auto i ) {
                    LHCb::LHCbID id( i );
                    if ( pver < 6 ) {
                      // Fixup channelIDtype for old (<6) data
                      // Adapts to https://gitlab.cern.ch/lhcb/LHCb/-/merge_requests/3226
                      enum class OLD_channelIDtype { Velo = 1, TT, IT, OT, Rich, Calo, Muon, VP, FT = 10, UT, HC };
                      const auto dtype = (int)id.detectorType();
                      if ( (int)OLD_channelIDtype::VP == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::VP );
                      } else if ( (int)OLD_channelIDtype::UT == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::UT );
                      } else if ( (int)OLD_channelIDtype::FT == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::FT );
                      } else if ( (int)OLD_channelIDtype::Rich == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::Rich );
                      } else if ( (int)OLD_channelIDtype::Calo == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::Calo );
                      } else if ( (int)OLD_channelIDtype::Muon == dtype ) {
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::Muon );
                      } else {
                        // For sub-systems no longer defined ..
                        id.setDetectorType( LHCb::LHCbID::channelIDtype::UNDEFINED );
                      }
                    }
                    return id;
                  } );
  // schema change: sorting no longer needed when we write DSTs with sorted lhcbids
  // Update: Always (re)sort after modifications above (i.e. pver < 6).
  if ( pver < 6 ) { std::sort( lhcbids.begin(), lhcbids.end() ); }
  track.addSortedToLhcbIDs( lhcbids );

  // extract the first and last indices for the states for this track
  auto firstState = ptrack.firstState;
  auto lastState  = ptrack.lastState;

  // protection for short int wrapping
  if ( pver < 5 && ptracks.states().size() > std::numeric_limits<uint8_t>::max() ) {
    firstState = m_firstStateHigh + ptrack.firstState;
    lastState  = m_lastStateHigh + ptrack.lastState;
    if ( lastState < firstState ) { // we wrapped in the track
      parent().warning() << "** State index wrapped, first/last = " << firstState << " " << lastState << endmsg;
      m_lastStateHigh += 0x10000;
      m_firstStateHigh += 0x10000;
      lastState = m_lastStateHigh + ptrack.lastState;
    }
  }

  // check the indices are sane
  if ( lastState > static_cast<int>( ptracks.states().size() ) || firstState > lastState ) {
    parent().warning() << "Attempted out-of-range access to packed States. This is bad, do not ignore !" << endmsg;
    lastState = firstState = 0;
  }

  // convert the states
  std::for_each( std::next( ptracks.states().begin(), firstState ), std::next( ptracks.states().begin(), lastState ),
                 [&]( const LHCb::PackedState& s ) // could be auto& with C++14
                 { this->convertState( s, track ); } );

  // extract the first and last extra info indices
  auto firstExtra = ptrack.firstExtra;
  auto lastExtra  = ptrack.lastExtra;

  // protect against short int wrapping
  if ( pver < 5 && ptracks.extras().size() > std::numeric_limits<uint8_t>::max() ) {
    firstExtra = m_firstExtraHigh + ptrack.firstExtra;
    lastExtra  = m_lastExtraHigh + ptrack.lastExtra;
    if ( lastExtra < firstExtra ) { // we wrapped in the track
      parent().warning() << "** Extra index wrapped, first/last = " << firstExtra << " " << lastExtra << endmsg;
      m_lastExtraHigh += 0x10000;
      m_firstExtraHigh += 0x10000;
      lastExtra = m_lastExtraHigh + ptrack.lastExtra;
    }
  }

  // sanity checks on the indices
  if ( lastExtra > static_cast<int>( ptracks.extras().size() ) || firstExtra > lastExtra ) {
    parent().warning() << "Attempted out-of-range access to packed ExtraInfo. This is bad, do not ignore !" << endmsg;
    lastExtra = firstExtra = 0;
  }

  // fill the extras
  std::for_each( std::next( ptracks.extras().begin(), firstExtra ), std::next( ptracks.extras().begin(), lastExtra ),
                 [&]( auto info ) {
                   track.addInfo( static_cast<LHCb::Track::AdditionalInfo>( info.first ),
                                  StandardPacker::fltPacked( info.second ) );
                 } );

  //== Cleanup extraInfo and set likelihood/ghostProbability for old data
  if ( pver <= 2 ) {
    track.eraseInfo( LHCb::Track::AdditionalInfo::PatQuality );
    track.eraseInfo( LHCb::Track::AdditionalInfo::Cand1stQPat );
    track.eraseInfo( LHCb::Track::AdditionalInfo::Cand2ndQPat );
    track.eraseInfo( LHCb::Track::AdditionalInfo::NCandCommonHits );
    track.eraseInfo( LHCb::Track::AdditionalInfo::Cand1stChi2Mat );
    track.eraseInfo( LHCb::Track::AdditionalInfo::Cand2ndChi2Mat );
    track.eraseInfo( LHCb::Track::AdditionalInfo::MatchChi2 );
    track.eraseInfo( LHCb::Track::AdditionalInfo::TsaLikelihood );
    track.eraseInfo( LHCb::Track::AdditionalInfo::nPRVeloRZExpect );
    track.eraseInfo( LHCb::Track::AdditionalInfo::nPRVelo3DExpect );
  }
}

void TrackPacker::unpack( const PackedDataVector& ptracks, DataVector& tracks ) const {

  // Check version
  const auto pver = getUnPackingVersion( ptracks );
  if ( !isSupportedVer( pver ) ) { return; }

  // reserve the required size
  tracks.reserve( ptracks.data().size() );

  // reset the cached variables to handle the wrapping bug ...
  resetWrappingCounts();

  // unpack
  for ( const auto& ptrack : ptracks.data() ) {
    auto* track = new LHCb::Track();
    // parent().debug() << "Unpacked Track key=" << ptrack.key << endmsg;
    tracks.insert( track, ptrack.key );
    unpack( ptrack, *track, ptracks, tracks );
  }
}

void TrackPacker::convertState( const LHCb::PackedState& pSta, LHCb::Track& tra ) const {
  LHCb::StateVector stateVector;
  stateVector.setX( StandardPacker::position( pSta.x ) );
  stateVector.setY( StandardPacker::position( pSta.y ) );
  stateVector.setZ( StandardPacker::position( pSta.z ) );
  stateVector.setTx( StandardPacker::slope( pSta.tx ) );
  stateVector.setTy( StandardPacker::slope( pSta.ty ) );
  const double pInv = ( 0 != pSta.p ? 1.0 / StandardPacker::energy( pSta.p ) : 0.0 );
  stateVector.setQOverP( pInv );

  LHCb::State state( stateVector );

  //== Fill covariance matrix
  const double err0     = StandardPacker::position( pSta.cov_00 );
  const double err1     = StandardPacker::position( pSta.cov_11 );
  const double err2     = StandardPacker::slope( pSta.cov_22 );
  const double err3     = StandardPacker::slope( pSta.cov_33 );
  const double err4     = StandardPacker::energy( pSta.cov_44 ) * std::abs( pInv ) * 1.e-5;
  auto&        stateCov = *( const_cast<Gaudi::TrackSymMatrix*>( &state.covariance() ) );
  stateCov( 0, 0 )      = err0 * err0;
  stateCov( 1, 1 )      = err1 * err1;
  stateCov( 2, 2 )      = err2 * err2;
  stateCov( 3, 3 )      = err3 * err3;
  stateCov( 4, 4 )      = err4 * err4;
  stateCov( 1, 0 )      = err1 * err0 * StandardPacker::fraction( pSta.cov_10 );
  stateCov( 2, 0 )      = err2 * err0 * StandardPacker::fraction( pSta.cov_20 );
  stateCov( 2, 1 )      = err2 * err1 * StandardPacker::fraction( pSta.cov_21 );
  stateCov( 3, 0 )      = err3 * err0 * StandardPacker::fraction( pSta.cov_30 );
  stateCov( 3, 1 )      = err3 * err1 * StandardPacker::fraction( pSta.cov_31 );
  stateCov( 3, 2 )      = err3 * err2 * StandardPacker::fraction( pSta.cov_32 );
  stateCov( 4, 0 )      = err4 * err0 * StandardPacker::fraction( pSta.cov_40 );
  stateCov( 4, 1 )      = err4 * err1 * StandardPacker::fraction( pSta.cov_41 );
  stateCov( 4, 2 )      = err4 * err2 * StandardPacker::fraction( pSta.cov_42 );
  stateCov( 4, 3 )      = err4 * err3 * StandardPacker::fraction( pSta.cov_43 );

  state.setFlags( pSta.flags );

  tra.addToStates( state );
}
StatusCode TrackPacker::check( const Data& dataA, const Data& dataB ) const {

  if ( dataA.key() != dataB.key() ) {
    parent().warning() << "Wrong key : old " << dataA.key() << " test " << dataB.key() << endmsg;
  }
  bool isOK = true;
  if ( 0. == dataB.chi2PerDoF() ) {
    if ( 0. != dataA.chi2PerDoF() ) isOK = false;
  } else if ( 1.e-7 < std::abs( ( dataA.chi2PerDoF() - dataB.chi2PerDoF() ) / dataB.chi2PerDoF() ) )
    isOK = false;
  if ( 0 < abs( dataA.nDoF() - dataB.nDoF() ) ) isOK = false;
  if ( dataA.flags() != dataB.flags() ) isOK = false;
  if ( dataA.lhcbIDs().size() != dataB.lhcbIDs().size() ) isOK = false;
  if ( 0. == dataB.likelihood() ) {
    if ( 0. != dataA.likelihood() ) isOK = false;
  } else if ( 1.e-7 < std::abs( ( dataA.likelihood() - dataB.likelihood() ) / dataB.likelihood() ) )
    isOK = false;
  if ( 0. == dataB.ghostProbability() ) {
    if ( 0. != dataA.ghostProbability() ) isOK = false;
  } else if ( 1.e-7 < std::abs( ( dataA.ghostProbability() - dataB.ghostProbability() ) / dataB.ghostProbability() ) )
    isOK = false;
  unsigned int kk;
  for ( kk = 0; dataA.lhcbIDs().size() != kk; ++kk ) {
    if ( dataA.lhcbIDs()[kk].lhcbID() != dataB.lhcbIDs()[kk].lhcbID() ) isOK = false;
  }
  const LHCb::Track::ExtraInfo& oExtra = dataA.extraInfo();
  const LHCb::Track::ExtraInfo& tExtra = dataB.extraInfo();
  if ( oExtra.size() != tExtra.size() ) isOK = false;
  LHCb::Track::ExtraInfo::const_iterator oIt = oExtra.begin();
  LHCb::Track::ExtraInfo::const_iterator tIt = tExtra.begin();
  for ( kk = 0; tExtra.size() > kk; ++kk, ++oIt, ++tIt ) {
    if ( ( *oIt ).first != ( *tIt ).first ) isOK = false;
    if ( 0. == ( *tIt ).second ) {
      if ( 0. != ( *oIt ).second ) isOK = false;
    } else if ( 1.e-7 < std::abs( ( ( *oIt ).second - ( *tIt ).second ) / ( *tIt ).second ) )
      isOK = false;
  }

  if ( dataA.nStates() != dataB.nStates() ) isOK = false;

  if ( !isOK || MSG::DEBUG >= parent().msgLevel() ) {
    parent().info() << "===== Track key " << dataA.key() << endmsg;
    parent().info() << format( "Old   chi2 %10.4f  nDoF %6i flags %8x nLhcbID %4d nExtra %4d  nStates %4d",
                               dataA.chi2PerDoF(), dataA.nDoF(), dataA.flags(), dataA.lhcbIDs().size(), oExtra.size(),
                               dataA.nStates() );
    parent().info() << format( " Likelihood %10.6f ghostProba %10.8f", dataA.likelihood(), dataA.ghostProbability() )
                    << endmsg;
    parent().info() << format( "Test  chi2 %10.4f  nDoF %6i flags %8x nLhcbID %4d nExtra %4d  nStates %4d",
                               dataB.chi2PerDoF(), dataB.nDoF(), dataB.flags(), dataB.lhcbIDs().size(), tExtra.size(),
                               dataB.nStates() );
    parent().info() << format( " Likelihood %10.6f ghostProba %10.8f", dataB.likelihood(), dataB.ghostProbability() )
                    << endmsg;
    for ( kk = 0; dataA.lhcbIDs().size() != kk; ++kk ) {
      parent().info() << format( "   old ID %8x   new %8x", dataA.lhcbIDs()[kk].lhcbID(), dataB.lhcbIDs()[kk].lhcbID() )
                      << endmsg;
    }
    oIt = oExtra.begin();
    tIt = tExtra.begin();
    for ( kk = 0; oExtra.size() != kk; ++kk, ++oIt, ++tIt ) {
      parent().info() << format( "   old Extra %5d %12.4f     new %5d %12.4f", ( *oIt ).first, ( *oIt ).second,
                                 ( *tIt ).first, ( *tIt ).second )
                      << endmsg;
    }
  }

  //== Compare the states. The track number won't be reported...

  for ( kk = 0; ( dataA.nStates() > kk ) && ( dataB.nStates() > kk ); ++kk ) {
    compareStates( *dataA.states()[kk], *dataB.states()[kk] );
  }

  return ( isOK ? StatusCode::SUCCESS : StatusCode::FAILURE );
}

StatusCode TrackPacker::check( const DataVector& dataA, const DataVector& dataB ) const {
  StatusCode sc = StatusCode::SUCCESS;

  /*
    //This check would fail as Tracks from reconstruction have version 0
    //Unpacked tracks are set to have version 5
    //Don't know why but it is what it is
    if ( (int) dataA.version() != (int) dataB.version() ) {
    parent().error() << "Version number mis-match "
                     << (int) dataA.version() <<"  "<< (int) dataB.version()
                     << endmsg;

    return StatusCode::FAILURE;
  }
  */
  if ( dataA.size() != dataB.size() ) {
    parent().err() << "Old Track size " << dataA.size() << " differs form Test " << dataB.size() << endmsg;
    return StatusCode::FAILURE;
  }

  auto itOld  = dataA.begin();
  auto itTest = dataB.begin();
  while ( dataA.end() != itOld ) {
    if ( sc ) sc = check( **itOld, **itTest );
    ++itOld;
    ++itTest;
  }

  return sc;
}

void TrackPacker::compareStates( const LHCb::State& oSta, const LHCb::State& tSta ) const {
  bool isOK = true;

  if ( 5.e-5 < std::abs( oSta.z() - tSta.z() ) ) isOK = false;
  if ( 5.e-5 < std::abs( oSta.x() - tSta.x() ) ) isOK = false;
  if ( 5.e-5 < std::abs( oSta.y() - tSta.y() ) ) isOK = false;
  if ( 5.e-8 < std::abs( oSta.tx() - tSta.tx() ) ) isOK = false;
  if ( 5.e-8 < std::abs( oSta.ty() - tSta.ty() ) ) isOK = false;
  const auto oP = safe_divide( 1.0, oSta.qOverP() );
  const auto tP = safe_divide( 1.0, tSta.qOverP() );
  if ( !( std::abs( oSta.qOverP() ) > 0 && std::abs( tSta.qOverP() ) > 0 ) ) {
    parent().info() << "=== State has q/p == 0 ! " << endmsg;
    parent().info() << "     old "
                    << format( " %12.5f %12.5f %12.5f %12.9f %12.9f %12.3f", oSta.z(), oSta.x(), oSta.y(), oSta.tx(),
                               oSta.ty(), oP )
                    << endmsg;
    parent().info() << "    test "
                    << format( " %12.5f %12.5f %12.5f %12.9f %12.9f %12.3f", tSta.z(), tSta.x(), tSta.y(), tSta.tx(),
                               tSta.ty(), tP )
                    << endmsg;
  }
  if ( 5.e-3 < std::abs( oP - tP ) ) isOK = false;

  const auto oDiag = std::array{safe_sqrt( oSta.errX2() ), safe_sqrt( oSta.errY2() ), safe_sqrt( oSta.errTx2() ),
                                safe_sqrt( oSta.errTy2() ), safe_sqrt( oSta.errQOverP2() )};

  const auto tDiag = std::array{safe_sqrt( tSta.errX2() ), safe_sqrt( tSta.errY2() ), safe_sqrt( tSta.errTx2() ),
                                safe_sqrt( tSta.errTy2() ), safe_sqrt( tSta.errQOverP2() )};

  if ( 5.e-5 < std::abs( oDiag[0] - tDiag[0] ) ) isOK = false;
  if ( 5.e-5 < std::abs( oDiag[1] - tDiag[1] ) ) isOK = false;
  if ( 5.e-8 < std::abs( oDiag[2] - tDiag[2] ) ) isOK = false;
  if ( 5.e-8 < std::abs( oDiag[3] - tDiag[3] ) ) isOK = false;
  //== Don't report problem if the term saturated: 2.e9 times energy scale 1.e-2
  if ( 5. < std::abs( oDiag[4] * oP * 1.e5 - tDiag[4] * tP * 1.e5 ) && std::abs( tDiag[4] * tP * 1.e5 ) < 1.999e7 )
    isOK = false;

  const auto oFrac = std::array{safe_divide( oSta.covariance()( 1, 0 ), oDiag[1] * oDiag[0] ),
                                safe_divide( oSta.covariance()( 2, 0 ), oDiag[2] * oDiag[0] ),
                                safe_divide( oSta.covariance()( 2, 1 ), oDiag[2] * oDiag[1] ),
                                safe_divide( oSta.covariance()( 3, 0 ), oDiag[3] * oDiag[0] ),
                                safe_divide( oSta.covariance()( 3, 1 ), oDiag[3] * oDiag[1] ),
                                safe_divide( oSta.covariance()( 3, 2 ), oDiag[3] * oDiag[2] ),
                                safe_divide( oSta.covariance()( 4, 0 ), oDiag[4] * oDiag[0] ),
                                safe_divide( oSta.covariance()( 4, 1 ), oDiag[4] * oDiag[1] ),
                                safe_divide( oSta.covariance()( 4, 2 ), oDiag[4] * oDiag[2] ),
                                safe_divide( oSta.covariance()( 4, 3 ), oDiag[4] * oDiag[3] )};

  const auto tFrac = std::array{safe_divide( tSta.covariance()( 1, 0 ), tDiag[1] * tDiag[0] ),
                                safe_divide( tSta.covariance()( 2, 0 ), tDiag[2] * tDiag[0] ),
                                safe_divide( tSta.covariance()( 2, 1 ), tDiag[2] * tDiag[1] ),
                                safe_divide( tSta.covariance()( 3, 0 ), tDiag[3] * tDiag[0] ),
                                safe_divide( tSta.covariance()( 3, 1 ), tDiag[3] * tDiag[1] ),
                                safe_divide( tSta.covariance()( 3, 2 ), tDiag[3] * tDiag[2] ),
                                safe_divide( tSta.covariance()( 4, 0 ), tDiag[4] * tDiag[0] ),
                                safe_divide( tSta.covariance()( 4, 1 ), tDiag[4] * tDiag[1] ),
                                safe_divide( tSta.covariance()( 4, 2 ), tDiag[4] * tDiag[2] ),
                                safe_divide( tSta.covariance()( 4, 3 ), tDiag[4] * tDiag[3] )};

  unsigned int kk;
  for ( kk = 0; oFrac.size() > kk; ++kk ) {
    if ( 2.e-5 < std::abs( oFrac[kk] - tFrac[kk] ) ) isOK = false;
  }

  if ( MSG::VERBOSE >= parent().msgLevel() ) isOK = false; //== force printing
  if ( !isOK ) {
    parent().info() << "=== State differ: " << endmsg;
    parent().info() << "     old "
                    << format( " %12.5f %12.5f %12.5f %12.9f %12.9f %12.3f", oSta.z(), oSta.x(), oSta.y(), oSta.tx(),
                               oSta.ty(), oP )
                    << endmsg;
    parent().info() << "    test "
                    << format( " %12.5f %12.5f %12.5f %12.9f %12.9f %12.3f", tSta.z(), tSta.x(), tSta.y(), tSta.tx(),
                               tSta.ty(), tP )
                    << endmsg;
    parent().info() << format( " old Diag %10.5f %10.5f %12.9f %12.9f %12.3f", oDiag[0], oDiag[1], oDiag[2], oDiag[3],
                               oDiag[4] * oP * 1.e5 )
                    << endmsg;
    parent().info() << format( "test Diag %10.5f %10.5f %12.9f %12.9f %12.3f", tDiag[0], tDiag[1], tDiag[2], tDiag[3],
                               tDiag[4] * tP * 1.e5 )
                    << endmsg;
    parent().info() << " old Frac ";
    for ( kk = 0; oFrac.size() > kk; ++kk ) { parent().info() << format( " %8.5f", oFrac[kk] ); }
    parent().info() << endmsg << "test Frac ";
    for ( kk = 0; tFrac.size() > kk; ++kk ) { parent().info() << format( " %8.5f", tFrac[kk] ); }
    parent().info() << endmsg;
  }
}