Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-08-31 08:22:18

0001 #include "MicromegasDriftQA.h"
0002 
0003 #include <qautils/QAHistManagerDef.h>
0004 
0005 #include <fun4all/Fun4AllHistoManager.h>
0006 #include <fun4all/Fun4AllReturnCodes.h>
0007 
0008 #include <phool/PHCompositeNode.h>
0009 #include <phool/getClass.h>
0010 #include <phool/phool.h>  // for PHWHERE
0011 
0012 #include <g4detectors/PHG4CylinderGeomContainer.h>
0013 #include <micromegas/CylinderGeomMicromegas.h>
0014 #include <micromegas/MicromegasDefs.h>
0015 #include <trackbase/ActsGeometry.h>
0016 #include <trackbase/TrackFitUtils.h>
0017 #include <trackbase/TrkrCluster.h>
0018 #include <trackbase/TrkrClusterContainer.h>
0019 #include <trackbase/TrkrDefs.h>
0020 #include <trackbase_historic/SvtxTrack.h>
0021 #include <trackbase_historic/SvtxTrackMap.h>
0022 
0023 #include <TDirectory.h>
0024 #include <TF1.h>
0025 #include <TF2.h>
0026 #include <TH1.h>
0027 #include <TH2.h>
0028 #include <TVector3.h>
0029 
0030 #include <array>
0031 #include <cassert>
0032 #include <climits>
0033 #include <cmath>
0034 #include <format>
0035 #include <iostream>
0036 #include <string>
0037 #include <vector>
0038 
0039 namespace
0040 {
0041   template <class T>
0042   class range_adaptor
0043   {
0044    public:
0045     explicit range_adaptor(const T& range)
0046       : m_range(range)
0047     {
0048     }
0049     const typename T::first_type& begin() { return m_range.first; }
0050     const typename T::second_type& end() { return m_range.second; }
0051 
0052    private:
0053     T m_range;
0054   };
0055 
0056   double normalize_angle(double phi)
0057   {
0058     while (phi < 0)
0059     {
0060       phi += 2 * M_PI;
0061     }
0062     while (phi >= 2 * M_PI)
0063     {
0064       phi -= 2 * M_PI;
0065     }
0066     return phi;
0067   }
0068 
0069   bool phi_in_range(double phi, double min, double max)
0070   {
0071     phi = normalize_angle(phi);
0072     min = normalize_angle(min);
0073     max = normalize_angle(max);
0074     return (min < max) ? (phi >= min && phi <= max)
0075                        : (phi >= min || phi <= max);
0076   }
0077 
0078   //! helix-plane intersection via Newton-Raphson
0079   //  identical to the version in MicromegasTrackEvaluator_hp.cc
0080   bool helix_plane_intersection(
0081       double t_min,
0082       double t_max,
0083       double zmin,
0084       double zmax,
0085       double R,
0086       double X0,
0087       double Y0,
0088       double intersect_rz,
0089       double slope_rz,
0090       const TVector3& ptile,
0091       const TVector3& ntile,
0092       TVector3& intersect)
0093   {
0094     // number of iterations and tolerance for Newton-Raphson method
0095     const int max_iter = 10;
0096     const double tol = 1e-6;
0097 
0098     // define C
0099     const double C = ntile.X() * (X0 - ptile.X()) + ntile.Y() * (Y0 - ptile.Y()) + ntile.Z() * (intersect_rz - ptile.Z());
0100 
0101     // define the function and the corresponding derivative used in the Newton-Raphson method
0102     auto f = [&](double t)
0103     {
0104       const double xt = X0 + R * std::cos(t);
0105       const double yt = Y0 + R * std::sin(t);
0106       const double Rt = std::sqrt(xt * xt + yt * yt);
0107       return ntile.X() * R * std::cos(t) + ntile.Y() * R * std::sin(t) + ntile.Z() * slope_rz * Rt + C;
0108     };
0109 
0110     auto df = [&](double t)
0111     {
0112       const double xt = X0 + R * std::cos(t);
0113       const double yt = Y0 + R * std::sin(t);
0114       const double Rt = std::sqrt(xt * xt + yt * yt);
0115       return -ntile.X() * R * std::sin(t) + ntile.Y() * R * std::cos(t) + ntile.Z() * R * slope_rz * (Y0 * std::cos(t) - X0 * std::sin(t)) / Rt;
0116     };
0117 
0118     auto solve_from = [&](double t_seed, TVector3& result) -> bool
0119     {
0120       double t = t_seed;
0121       for (int i = 0; i < max_iter; ++i)
0122       {
0123         const double ft = f(t);
0124         const double dft = df(t);
0125         if (std::abs(dft) < 1e-8)
0126         {
0127           return false;
0128         }
0129         const double t_new = t - ft / dft;
0130 
0131         const double x = X0 + R * std::cos(t_new);
0132         const double y = Y0 + R * std::sin(t_new);
0133         const double Rt_n = std::sqrt(x * x + y * y);
0134         const double z = slope_rz * Rt_n + intersect_rz;
0135         const double phi = std::atan2(y, x);
0136 
0137         const TVector3 cand(x, y, z);
0138         const bool phi_ok = phi_in_range(phi, t_min - 1e-4, t_max + 1e-4);
0139         const bool z_ok = (z >= zmin - 1e-4 && z <= zmax + 1e-4);
0140         const bool proj_ok = (std::abs(ntile.Dot(cand - ptile)) <= 0.05);
0141 
0142         if (std::abs(t_new - t) < tol && proj_ok && phi_ok && z_ok)
0143         {
0144           result = cand;
0145           return true;
0146         }
0147         t = t_new;
0148       }
0149       return false;
0150     };
0151 
0152     auto wrap = [&](double t)
0153     {
0154       while (t > t_max)
0155       {
0156         t -= 2 * M_PI;
0157       }
0158       while (t < t_min)
0159       {
0160         t += 2 * M_PI;
0161       }
0162       return t;
0163     };
0164 
0165     // the helix-plane equation can have more than one solution:
0166     // look for a solution within the tile acceptance from three different phi seeds
0167     std::vector<double> t_seeds(3);
0168     const double t_center = 0.5 * (t_min + t_max);
0169     const double delta = 2.0 * M_PI / 3.0;
0170     for (int i = 0; i < 3; ++i)
0171     {
0172       t_seeds[i]=wrap(t_center + i * delta);
0173     }
0174 
0175     for (const double t_seed : t_seeds)
0176     {
0177       if (solve_from(t_seed, intersect))
0178       {
0179         return true;
0180       }
0181     }
0182     return false;
0183   }
0184 
0185   //! piecewise fit function used for the drift velocity extraction
0186   //  par[0] = constrained slope, par[1..8] = per-tile offsets
0187   double fit_function_2d(double* x, double* par)
0188   {
0189     const int itile = static_cast<int>(std::floor(x[0]));
0190     const double z = x[1];
0191     if (itile < 0 || itile >= 8)
0192     {
0193       TF2::RejectPoint();
0194       return 0.;
0195     }
0196     return par[itile + 1] + par[0] * z;
0197   }
0198 
0199   //! z-view tile names
0200   const std::array<const char*, 8> k_tile_names =
0201       {"SCOZ", "SCIZ", "NCIZ", "NCOZ", "SEZ", "NEZ", "SWZ", "NWZ"};
0202 
0203   //! number of z bins of the dz vs z histograms
0204   constexpr int k_nzbins = 220;
0205 
0206   //! z_track range (cm)
0207   constexpr double k_max_z = 110;
0208 
0209   //! dz range (cm)
0210   constexpr double k_max_dz = 10;
0211 
0212 }  // namespace
0213 
0214 //____________________________________________________________________________..
0215 MicromegasDriftQA::MicromegasDriftQA(const std::string& name)
0216   : SubsysReco(name)
0217 {
0218 }
0219 
0220 //____________________________________________________________________________..
0221 int MicromegasDriftQA::InitRun(PHCompositeNode* topNode)
0222 {
0223   if (Verbosity())
0224   {
0225     std::cout << Name() << "::InitRun"
0226               << " drift_velocity=" << m_drift_velocity << " cm/ns"
0227               << " min_tpc_layer=" << m_min_tpc_layer
0228               << " max_tpc_layer=" << m_max_tpc_layer
0229               << std::endl;
0230   }
0231 
0232   const auto res = load_nodes(topNode);
0233   if (res != Fun4AllReturnCodes::EVENT_OK)
0234   {
0235     return res;
0236   }
0237 
0238   createHistos();
0239 
0240   // reference histograms initialized in header file to histos in HistoManager
0241   auto* hm = QAHistManagerDef::getHistoManager();
0242   assert(hm);
0243 
0244   for (int itile = 0; itile < 8; itile++)
0245   {
0246     h_ztrk_dz[itile] = dynamic_cast<TH2*>(hm->getHisto(std::format("{}ztrk_dz_{}", getHistoPrefix(), k_tile_names[itile])));
0247   }
0248   h_dz = dynamic_cast<TH1*>(hm->getHisto(std::format("{}dz", getHistoPrefix())));
0249   h_tile = dynamic_cast<TH1*>(hm->getHisto(std::format("{}tile", getHistoPrefix())));
0250   h_ylocal = dynamic_cast<TH1*>(hm->getHisto(std::format("{}ylocal", getHistoPrefix())));
0251   h_ntracks = dynamic_cast<TH1*>(hm->getHisto(std::format("{}ntracks", getHistoPrefix())));
0252   h_driftSummary = dynamic_cast<TH1*>(hm->getHisto(std::format("{}driftSummary", getHistoPrefix())));
0253 
0254   return Fun4AllReturnCodes::EVENT_OK;
0255 }
0256 
0257 //____________________________________________________________________________..
0258 int MicromegasDriftQA::process_event(PHCompositeNode* topNode)
0259 {
0260   const auto res = load_nodes(topNode);
0261   if (res != Fun4AllReturnCodes::EVENT_OK)
0262   {
0263     return res;
0264   }
0265 
0266   int nmatched = 0;
0267 
0268   for (const auto& [track_id, track] : *m_track_map)
0269   {
0270     // require valid beam-crossing
0271     const auto crossing = track->get_crossing();
0272     if (crossing == SHRT_MAX)
0273     {
0274       continue;
0275     }
0276 
0277     // collect distortion-corrected TPC cluster positions in the selected layer range
0278     std::vector<Acts::Vector3> tpc_positions;
0279     for (const auto* seed : {track->get_silicon_seed(), track->get_tpc_seed()})
0280     {
0281       if (!seed)
0282       {
0283         continue;
0284       }
0285       for (auto it = seed->begin_cluster_keys(); it != seed->end_cluster_keys(); ++it)
0286       {
0287         const auto ckey = *it;
0288         if (TrkrDefs::getTrkrId(ckey) != TrkrDefs::tpcId)
0289         {
0290           continue;
0291         }
0292         const auto layer = TrkrDefs::getLayer(ckey);
0293         if (layer < m_min_tpc_layer || layer >= m_max_tpc_layer)
0294         {
0295           continue;
0296         }
0297         auto* cl = m_cluster_map->findCluster(ckey);
0298         if (cl)
0299         {
0300           tpc_positions.push_back(
0301               m_globalPositionWrapper.getGlobalPositionDistortionCorrected(ckey, cl, crossing));
0302         }
0303       }
0304     }
0305 
0306     // need at least 3 TPC clusters in range
0307     if (tpc_positions.size() < 3)
0308     {
0309       continue;
0310     }
0311 
0312     // helix fit: straight line in r-z, circle in x-y
0313     const auto [slope_rz, intersect_rz] = TrackFitUtils::line_fit(tpc_positions);
0314     const auto [R, X0, Y0] = TrackFitUtils::circle_fit_by_taubin(tpc_positions);
0315 
0316     // reject badly reconstructed / low-pT tracks
0317     if (R < 40.0)
0318     {
0319       continue;
0320     }
0321 
0322     // extrapolate to the TPOT z-view modules
0323     const auto mm_range = m_micromegas_geomcontainer->get_begin_end();
0324     for (const auto& [mm_layer, base_layergeom] : range_adaptor(mm_range))
0325     {
0326       const auto* layergeom = static_cast<const CylinderGeomMicromegas*>(base_layergeom);
0327       assert(layergeom);
0328 
0329       // skip the phi layer; only the z-view layer matters here
0330       if (layergeom->get_segmentation_type() != MicromegasDefs::SegmentationType::SEGMENTATION_Z)
0331       {
0332         continue;
0333       }
0334 
0335       const double layer_radius = layergeom->get_radius();
0336       auto [xplus, yplus, xminus, yminus] =
0337           TrackFitUtils::circle_circle_intersection(layer_radius, R, X0, Y0);
0338 
0339       if (!std::isfinite(xplus))
0340       {
0341         continue;
0342       }
0343 
0344       // pick the solution closest in phi to the last TPC cluster
0345       const double last_phi = std::atan2(tpc_positions.back().y(), tpc_positions.back().x());
0346       const double phi_plus = std::atan2(yplus, xplus);
0347       const double phi_minus = std::atan2(yminus, xminus);
0348       const double phi = (std::abs(last_phi - phi_plus) < std::abs(last_phi - phi_minus)) ? phi_plus : phi_minus;
0349 
0350       const double r_cyl = layer_radius;
0351       const double z_cyl = intersect_rz + slope_rz * r_cyl;
0352       const TVector3 world_cyl(r_cyl * std::cos(phi), r_cyl * std::sin(phi), z_cyl);
0353 
0354       const int tileid = layergeom->find_tile_cylindrical(world_cyl);
0355       if (tileid < 0)
0356       {
0357         continue;
0358       }
0359 
0360       const auto tile_center = layergeom->get_world_from_local_coords(tileid, m_tGeometry, {0, 0});
0361       const TVector3 ptile(tile_center.x(), tile_center.y(), tile_center.z());
0362 
0363       const auto tile_norm = layergeom->get_world_from_local_vect(tileid, m_tGeometry, {0, 0, 1});
0364       const TVector3 ntile(tile_norm.x(), tile_norm.y(), tile_norm.z());
0365 
0366       const auto phi_range = layergeom->get_phi_range(tileid, m_tGeometry);
0367       const double zmin = layergeom->get_zmin();
0368       const double zmax = layergeom->get_zmax();
0369 
0370       TVector3 intersection;
0371       if (!helix_plane_intersection(phi_range.first, phi_range.second, zmin, zmax,
0372                                     R, X0, Y0, intersect_rz, slope_rz, ptile, ntile, intersection))
0373       {
0374         continue;
0375       }
0376 
0377       const auto local_intersection = layergeom->get_local_from_world_coords(
0378           tileid, m_tGeometry, {intersection.x(), intersection.y(), intersection.z()});
0379       const double y_local = local_intersection.y();
0380 
0381       // reject track states near the tile edge
0382       if (std::abs(y_local) > m_y_local_cut)
0383       {
0384         continue;
0385       }
0386 
0387       // find the nearest TPOT cluster on this tile
0388       const auto hitsetkey = MicromegasDefs::genHitSetKey(mm_layer, MicromegasDefs::SegmentationType::SEGMENTATION_Z, tileid);
0389       const auto clusrange = m_cluster_map->getClusters(hitsetkey);
0390 
0391       double dmin = -1;
0392       double z_cluster = 0;
0393       for (const auto& [ckey, cl] : range_adaptor(clusrange))
0394       {
0395         const double cl_y_local = cl->getLocalY();
0396         const double d = std::abs(y_local - cl_y_local);
0397         if (dmin < 0 || d < dmin)
0398         {
0399           dmin = d;
0400           const auto gpos = m_globalPositionWrapper.getGlobalPositionDistortionCorrected(ckey, cl, crossing);
0401           z_cluster = gpos.z();
0402         }
0403       }
0404 
0405       // require cluster within the z search window
0406       if (dmin < 0 || dmin > m_z_search_win)
0407       {
0408         continue;
0409       }
0410 
0411       // fill histograms
0412       const double z_track = intersection.z();
0413       const double dz = z_track - z_cluster;
0414 
0415       h_ztrk_dz[tileid]->Fill(z_track, dz);
0416       h_dz->Fill(dz);
0417       h_tile->Fill(tileid);
0418       h_ylocal->Fill(y_local);
0419 
0420       ++nmatched;
0421       break;
0422     }
0423   }
0424 
0425   h_ntracks->Fill(nmatched);
0426 
0427   return Fun4AllReturnCodes::EVENT_OK;
0428 }
0429 
0430 //____________________________________________________________________________..
0431 int MicromegasDriftQA::End(PHCompositeNode* /*topNode*/)
0432 {
0433   if (!(h_ztrk_dz[0] && h_driftSummary))
0434   {
0435     std::cout << PHWHERE << " histograms not found, skipping drift velocity fit." << std::endl;
0436     return Fun4AllReturnCodes::EVENT_OK;
0437   }
0438 
0439   int nEntries = 0;
0440   for (const auto* h : h_ztrk_dz)
0441   {
0442     nEntries += static_cast<int>(h->GetEntries());
0443   }
0444   if (Verbosity())
0445   {
0446     std::cout << Name() << "::End - fitting " << nEntries << " entries" << std::endl;
0447   }
0448 
0449   // record input drift velocity even if the fit is skipped
0450   h_driftSummary->SetBinContent(3, m_drift_velocity);
0451 
0452   if (nEntries < 8 * m_min_slice_entries)
0453   {
0454     std::cout << Name() << "::End - not enough entries (" << nEntries << "), skipping drift velocity fit." << std::endl;
0455     return Fun4AllReturnCodes::EVENT_OK;
0456   }
0457 
0458   // build mean-dz TH2 via FitSlicesY, one tile at a time
0459   // x = tile [0,8), y = z_track (cm), content = mean dz (cm)
0460   auto* h_fit = new TH2F("h_fit_micromegas", "", 8, 0, 8, k_nzbins, -k_max_z, k_max_z);
0461   h_fit->SetDirectory(nullptr);
0462 
0463   for (int itile = 0; itile < 8; ++itile)
0464   {
0465     auto* h2d = h_ztrk_dz[itile];
0466 
0467     // fit vertical slices; require a minimum of m_min_slice_entries per slice
0468     TObjArray slices;
0469     slices.SetOwner(kTRUE);
0470     h2d->FitSlicesY(nullptr, 0, -1, m_min_slice_entries, "QNR", &slices);
0471     auto* h_mean = dynamic_cast<TH1*>(slices.At(1));
0472     if (!h_mean)
0473     {
0474       continue;
0475     }
0476 
0477     for (int iz = 1; iz <= h_mean->GetNbinsX(); ++iz)
0478     {
0479       const double entries = h2d->Integral(iz, iz, 1, h2d->GetNbinsY());
0480       if (entries > 0)
0481       {
0482         h_fit->SetBinContent(itile + 1, iz, h_mean->GetBinContent(iz));
0483       }
0484     }
0485   }
0486 
0487   // 2D piecewise fit: the eight tiles are fitted simultaneously with a shared
0488   // slope and per-tile offsets. This eliminates the need for perfect
0489   // translational TPOT alignment.
0490   auto* fit2d = new TF2("fit2d_micromegas", fit_function_2d, 0, 8, -k_max_z, k_max_z, 9);
0491   for (int i = 0; i < 9; ++i)
0492   {
0493     fit2d->SetParameter(i, 0.0);
0494   }
0495   h_fit->Fit(fit2d, "0RQ");
0496 
0497   const double slope = fit2d->GetParameter(0);
0498   const double slope_err = fit2d->GetParError(0);
0499   const double new_drift = m_drift_velocity / (1.0 + slope);
0500   const double drift_err = m_drift_velocity / std::pow(1.0 + slope, 2) * slope_err;
0501 
0502   std::cout << Name() << "::End"
0503             << " slope=" << slope
0504             << " input_drift=" << m_drift_velocity << " cm/ns"
0505             << " new_drift=" << new_drift << " cm/ns +/- " << drift_err << " cm/ns"
0506             << std::endl;
0507 
0508   // store fit results in the summary histogram
0509   h_driftSummary->SetBinContent(1, slope);
0510   h_driftSummary->SetBinContent(2, slope_err);
0511   h_driftSummary->SetBinContent(4, new_drift);
0512   h_driftSummary->SetBinContent(5, drift_err);
0513 
0514   delete fit2d;
0515   delete h_fit;
0516 
0517   return Fun4AllReturnCodes::EVENT_OK;
0518 }
0519 
0520 //____________________________________________________________________________..
0521 int MicromegasDriftQA::load_nodes(PHCompositeNode* topNode)
0522 {
0523   m_tGeometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
0524   if (!m_tGeometry)
0525   {
0526     std::cout << PHWHERE << " ActsGeometry node missing, abort." << std::endl;
0527     return Fun4AllReturnCodes::ABORTRUN;
0528   }
0529 
0530   m_micromegas_geomcontainer = findNode::getClass<PHG4CylinderGeomContainer>(topNode, "CYLINDERGEOM_MICROMEGAS_FULL");
0531   if (!m_micromegas_geomcontainer)
0532   {
0533     std::cout << PHWHERE << " CYLINDERGEOM_MICROMEGAS_FULL node missing, abort." << std::endl;
0534     return Fun4AllReturnCodes::ABORTRUN;
0535   }
0536 
0537   m_track_map = findNode::getClass<SvtxTrackMap>(topNode, m_trackmapname);
0538   if (!m_track_map)
0539   {
0540     std::cout << PHWHERE << " " << m_trackmapname << " node missing, abort." << std::endl;
0541     return Fun4AllReturnCodes::ABORTRUN;
0542   }
0543 
0544   m_cluster_map = findNode::getClass<TrkrClusterContainer>(topNode, "TRKR_CLUSTER");
0545   if (!m_cluster_map)
0546   {
0547     std::cout << PHWHERE << " TRKR_CLUSTER node missing, abort." << std::endl;
0548     return Fun4AllReturnCodes::ABORTRUN;
0549   }
0550 
0551   m_globalPositionWrapper.loadNodes(topNode);
0552 
0553   return Fun4AllReturnCodes::EVENT_OK;
0554 }
0555 
0556 //____________________________________________________________________________..
0557 std::string MicromegasDriftQA::getHistoPrefix() const
0558 {
0559   // define prefix to all histos in HistoManager
0560   return std::string("h_") + Name() + std::string("_");
0561 }
0562 
0563 //____________________________________________________________________________..
0564 void MicromegasDriftQA::createHistos()
0565 {
0566   // initialize HistoManager
0567   auto* hm = QAHistManagerDef::getHistoManager();
0568   assert(hm);
0569 
0570   // create and register histos in HistoManager
0571   for (int itile = 0; itile < 8; itile++)
0572   {
0573     auto* h = new TH2F(std::format("{}ztrk_dz_{}", getHistoPrefix(), k_tile_names[itile]).c_str(),
0574                        std::format("{};z_{{track}} (cm);#Deltaz (track#minuscluster) (cm)", k_tile_names[itile]).c_str(),
0575                        k_nzbins, -k_max_z, k_max_z, 100, -k_max_dz, k_max_dz);
0576     hm->registerHisto(h);
0577   }
0578 
0579   {
0580     auto* h = new TH1F(std::format("{}dz", getHistoPrefix()).c_str(),
0581                        ";#Deltaz (track#minuscluster) (cm);track states", 100, -k_max_dz, k_max_dz);
0582     hm->registerHisto(h);
0583   }
0584 
0585   {
0586     auto* h = new TH1F(std::format("{}tile", getHistoPrefix()).c_str(),
0587                        ";tile;track states", 8, -0.5, 7.5);
0588     for (int itile = 0; itile < 8; itile++)
0589     {
0590       h->GetXaxis()->SetBinLabel(itile + 1, k_tile_names[itile]);
0591     }
0592     hm->registerHisto(h);
0593   }
0594 
0595   {
0596     auto* h = new TH1F(std::format("{}ylocal", getHistoPrefix()).c_str(),
0597                        ";y_{local} (cm);track states", 100, -30, 30);
0598     hm->registerHisto(h);
0599   }
0600 
0601   {
0602     auto* h = new TH1F(std::format("{}ntracks", getHistoPrefix()).c_str(),
0603                        ";matched track states per event;events", 20, -0.5, 19.5);
0604     hm->registerHisto(h);
0605   }
0606 
0607   {
0608     // summary of the drift velocity fit performed in End()
0609     auto* h = new TH1F(std::format("{}driftSummary", getHistoPrefix()).c_str(),
0610                        "drift velocity fit summary", 5, 0.5, 5.5);
0611     h->GetXaxis()->SetBinLabel(1, "slope");
0612     h->GetXaxis()->SetBinLabel(2, "slope_err");
0613     h->GetXaxis()->SetBinLabel(3, "v_{in} (cm/ns)");
0614     h->GetXaxis()->SetBinLabel(4, "v_{new} (cm/ns)");
0615     h->GetXaxis()->SetBinLabel(5, "v_{new} err (cm/ns)");
0616     hm->registerHisto(h);
0617   }
0618 }