Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
PackedTrack.cpp 23.57 KiB
/*****************************************************************************\
* (c) Copyright 2000-2018 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"
#include "GaudiAlg/GaudiAlgorithm.h"
using namespace LHCb;

//-----------------------------------------------------------------------------

void TrackPacker::pack( const Data& track, PackedData& ptrack, PackedDataVector& ptracks ) const {
  // check version
  const auto ver = ptracks.version();
  if ( isSupportedVer( ver ) ) {

    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 ( UNLIKELY( 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 ( UNLIKELY( 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& E : track.extraInfo() ) {
      ptracks.extras().emplace_back( std::make_pair( E.first, StandardPacker::fltPacked( E.second ) ) );
    }
    ptrack.lastExtra = ptracks.extras().size();
  }
}

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

void TrackPacker::convertState( const LHCb::State& state, PackedDataVector& ptracks ) const {
  ptracks.states().emplace_back( LHCb::PackedState() );
  auto& newState = ptracks.states().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 * fabs( 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 version
  const auto ver = ptracks.version();
  if ( isSupportedVer( ver ) ) {

    // 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.tracks().empty() && &ptracks.tracks().front() == &ptrack ) ) {
      m_lastPackedDataV = &ptracks;
      resetWrappingCounts();
    }

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

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

    // Apply protection for short int wrapping
    if ( UNLIKELY( ver < 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 ( UNLIKELY( 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 !" )
          .ignore();
      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 ), []( const int i ) { return LHCb::LHCbID( i ); } );
    // schema change: sorting no longer needed when we write DSTs with sorted lhcbids
    if ( ver <= 1 ) { std::sort( lhcbids.begin(), lhcbids.end() ); }
    track.addSortedToLhcbIDs( lhcbids );

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

    // protection for short int wrapping
    if ( UNLIKELY( ver < 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 ( UNLIKELY( 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 !" )
          .ignore();
      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
    int firstExtra = ptrack.firstExtra;
    int lastExtra  = ptrack.lastExtra;

    // protect against short int wrapping
    if ( UNLIKELY( ver < 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 ( UNLIKELY( 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 !" )
          .ignore();
      lastExtra = firstExtra = 0;
    }

    // fill the extras
    std::for_each( std::next( ptracks.extras().begin(), firstExtra ), std::next( ptracks.extras().begin(), lastExtra ),
                   [&]( const std::pair<int, int>& info ) // could be auto& with C++14
                   { track.addInfo( info.first, StandardPacker::fltPacked( info.second ) ); } );

    //== Cleanup extraInfo and set likelihood/ghostProbability for old data
    if ( UNLIKELY( ver <= 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 );
      track.setLikelihood( track.info( 1, 999 ) );         // was the key of likelihood...
      track.setGhostProbability( track.info( 102, 999 ) ); // was the key of ghost probability
      track.eraseInfo( 1 );
      track.eraseInfo( 102 );
    }
  }
}

void TrackPacker::unpack( const PackedDataVector& ptracks, DataVector& tracks ) const {
  // Check version
  const auto ver = ptracks.version();
  if ( isSupportedVer( ver ) ) {

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

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

    // unpack
    for ( const auto& ptrack : ptracks.tracks() ) {
      LHCb::Track* 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 ) * fabs( 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 < fabs( ( 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 < fabs( ( dataA.likelihood() - dataB.likelihood() ) / dataB.likelihood() ) )
    isOK = false;
  if ( 0. == dataB.ghostProbability() ) {
    if ( 0. != dataA.ghostProbability() ) isOK = false;
  } else if ( 1.e-7 < fabs( ( 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 < fabs( ( ( *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;

  if ( dataA.version() != dataB.version() ) { return parent().Error( "Version number mis-match" ); }

  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 < fabs( oSta.z() - tSta.z() ) ) isOK = false;
  if ( 5.e-5 < fabs( oSta.x() - tSta.x() ) ) isOK = false;
  if ( 5.e-5 < fabs( oSta.y() - tSta.y() ) ) isOK = false;
  if ( 5.e-8 < fabs( oSta.tx() - tSta.tx() ) ) isOK = false;
  if ( 5.e-8 < fabs( 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 ( !( fabs( oSta.qOverP() ) > 0 && fabs( 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 < fabs( 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 < fabs( oDiag[0] - tDiag[0] ) ) isOK = false;
  if ( 5.e-5 < fabs( oDiag[1] - tDiag[1] ) ) isOK = false;
  if ( 5.e-8 < fabs( oDiag[2] - tDiag[2] ) ) isOK = false;
  if ( 5.e-8 < fabs( oDiag[3] - tDiag[3] ) ) isOK = false;
  //== Don't report problem if the term saturated: 2.e9 times energy scale 1.e-2
  if ( 5. < fabs( oDiag[4] * oP * 1.e5 - tDiag[4] * tP * 1.e5 ) && fabs( 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 < fabs( 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;
  }
}