Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-08-31 08:21:23

0001 #include "Tpc_PolyClusterizer.h"
0002 
0003 #include "IdealPadMap.h"
0004 #include "Tpc_AssembledTrack.h"
0005 #include "Tpc_AssembledTrackContainer.h"
0006 #include "Tpc_PolyClusterContainerv1.h"
0007 #include "Tpc_PolyClusterv1.h"
0008 #include "TpcCrossingDecision.h"
0009 #include "TpcCrossingDecisionContainer.h"
0010 
0011 #include <cdbobjects/CDBTTree.h>
0012 #include <fun4all/Fun4AllReturnCodes.h>
0013 
0014 #include <ffamodules/CDBInterface.h>
0015 #include <phool/PHCompositeNode.h>
0016 #include <phool/PHIODataNode.h>
0017 #include <phool/PHNodeIterator.h>
0018 #include <phool/PHObject.h>
0019 #include <phool/getClass.h>
0020 
0021 #include <trackbase/TpcDefs.h>
0022 #include <trackbase/TrkrDefs.h>
0023 #include <trackbase/TrkrHit.h>
0024 #include <trackbase/TrkrHitSet.h>
0025 #include <trackbase/TrkrHitSetContainer.h>
0026 
0027 #include <trackbase/ActsGeometry.h>
0028 #include <g4detectors/PHG4CylinderGeom.h>  // for PHG4CylinderGeom
0029 #include <g4detectors/PHG4CylinderGeomContainer.h>
0030 #include <g4detectors/PHG4TpcGeom.h>
0031 #include <g4detectors/PHG4TpcGeomContainer.h>
0032 
0033 
0034 #include <TPolyLine3D.h>
0035 #include <phgarfield/PHGarfield.h>
0036 
0037 #include <algorithm>
0038 #include <array>
0039 #include <cmath>
0040 #include <iostream>
0041 #include <limits>
0042 #include <map>
0043 #include <set>
0044 #include <vector>
0045 
0046 namespace
0047 {
0048   constexpr unsigned int FirstLayer = 7;
0049   constexpr unsigned int LastLayer = 54;
0050   constexpr unsigned int NLayers = LastLayer - FirstLayer + 1;
0051   constexpr unsigned int NSides = 2;
0052   constexpr unsigned int NSectors = 12;
0053   constexpr double PhiConsistencyTolerance = 1.0e-10;
0054 
0055   double wrap_phi(double phi)
0056   {
0057     while (phi > M_PI)
0058     {
0059       phi -= 2.0 * M_PI;
0060     }
0061     while (phi <= -M_PI)
0062     {
0063       phi += 2.0 * M_PI;
0064     }
0065     return phi;
0066   }
0067 
0068   double unwrap_phi_near(double phi, const double reference)
0069   {
0070     while (phi - reference > M_PI)
0071     {
0072       phi -= 2.0 * M_PI;
0073     }
0074     while (phi - reference <= -M_PI)
0075     {
0076       phi += 2.0 * M_PI;
0077     }
0078     return phi;
0079   }
0080 
0081   double clamp_unit(const double value)
0082   {
0083     return std::max(0.0, std::min(1.0, value));
0084   }
0085 
0086   double square(const double value)
0087   {
0088     return value * value;
0089   }
0090 
0091   double phi_sample_fraction(const unsigned int sample)
0092   {
0093     if (Tpc_PolyClusterizer::NPhiSamples <= 1U)
0094     {
0095       return 0.0;
0096     }
0097     return static_cast<double>(sample) / static_cast<double>(Tpc_PolyClusterizer::NPhiSamples - 1U);
0098   }
0099 }  // namespace
0100 
0101 Tpc_PolyClusterizer::Tpc_PolyClusterizer(const std::string& name)
0102   : SubsysReco(name)
0103   , m_inputNodeName("TPC_ASSEMBLEDTRACKS")
0104   , m_outputNodeName("TPC_POLYCLUSTERS")
0105 {
0106 }
0107 
0108 Tpc_PolyClusterizer::~Tpc_PolyClusterizer()
0109 {
0110   delete m_idealPadMap;
0111   m_idealPadMap = nullptr;
0112   delete m_garfield;
0113   m_garfield = nullptr;
0114 }
0115 
0116 int Tpc_PolyClusterizer::InitRun(PHCompositeNode* topNode)
0117 {
0118   if (getNodes(topNode) != Fun4AllReturnCodes::EVENT_OK)
0119   {
0120     return Fun4AllReturnCodes::ABORTRUN;
0121   }
0122   if (createNodes(topNode) != Fun4AllReturnCodes::EVENT_OK)
0123   {
0124     return Fun4AllReturnCodes::ABORTRUN;
0125   }
0126 
0127   delete m_idealPadMap;
0128   m_idealPadMap = new IdealPadMap();
0129   if (m_idealPadMap->load_from_cdb(Verbosity()) != 0 || !m_idealPadMap->is_loaded())
0130   {
0131     std::cerr << Name() << "::InitRun - failed to load IdealPadMap" << std::endl;
0132     return Fun4AllReturnCodes::ABORTRUN;
0133   }
0134 
0135   PHG4TpcGeom *layergeom = m_geomContainerTpc->GetLayerCellGeom(20); 
0136   double rot_x = layergeom->get_rot_x();
0137   double rot_y = layergeom->get_rot_y();
0138   double rot_z = layergeom->get_rot_z();
0139   double place_x = layergeom->get_place_x();
0140   double place_y = layergeom->get_place_y();
0141   double place_z = layergeom->get_place_z();
0142   if (use_survey_geometry) 
0143   {
0144     m_tpcMove = {place_x, place_y, place_z};
0145     m_tpcRotations = {{{rot_x, rot_y, rot_z}, {0.0, 0.0, 0.0}}};
0146   }
0147 
0148   delete m_garfield;
0149   // m_garfield = new PHGarfield(Name() + "_PHGarfield");
0150   const std::string electricFieldMap = CDBInterface::instance()->getUrl("Tpc_PolySeeding_EField");
0151   
0152   // sphenix_3d_ibf_field_new.root sphenix_rossegger_garfield_field.root;
0153 
0154   const auto kefffile = CDBInterface::instance()->getUrl("Tpc_PolyClusterizer_kEff");
0155   
0156   if (!kefffile.empty())
0157   {
0158     auto keffcdbtree = std::make_unique<CDBTTree>(kefffile);
0159     keffcdbtree->LoadCalibrations();
0160     m_kEffSide0 = keffcdbtree->GetSingleFloatValue("keffside0");
0161     m_kEffSide1 = keffcdbtree->GetSingleFloatValue("keffside1");
0162 
0163     std::cout << PHWHERE << "Poly clusterizer loading calibrations from " << kefffile << std::endl 
0164               <<"    with values keffside0 = " << m_kEffSide0 << ", keffside1 = " << m_kEffSide1 << std::endl;
0165   }
0166 
0167 
0168   m_garfield = new PHGarfield(Name() + "_PHGarfield", electricFieldMap, m_kEffSide0, m_kEffSide1);
0169   configure_garfield(m_garfield);
0170   if (m_garfield->InitRun(topNode) != Fun4AllReturnCodes::EVENT_OK)
0171   {
0172     std::cerr << Name() << "::InitRun - PHGarfield InitRun failed" << std::endl;
0173     return Fun4AllReturnCodes::ABORTRUN;
0174   }
0175 
0176   if (!build_drift_lookup())
0177   {
0178     return Fun4AllReturnCodes::ABORTRUN;
0179   }
0180 
0181   m_event = 0;
0182   return Fun4AllReturnCodes::EVENT_OK;
0183 }
0184 
0185 void Tpc_PolyClusterizer::configure_garfield(PHGarfield* garfield) const
0186 {
0187   if (!garfield)
0188   {
0189     return;
0190   }
0191 
0192   garfield->MoveTpc(m_tpcMove[0], m_tpcMove[1], m_tpcMove[2]);
0193   for (const auto& rotation : m_tpcRotations)
0194   {
0195     garfield->RotateTpc(rotation[0], rotation[1], rotation[2]);
0196   }
0197   garfield->SetCMVoltageDefault(m_cmVoltageDefault);
0198 }
0199 
0200 int Tpc_PolyClusterizer::getNodes(PHCompositeNode* topNode)
0201 {
0202   m_assembledTracks = findNode::getClass<Tpc_AssembledTrackContainer>(topNode, m_inputNodeName);
0203   if (!m_assembledTracks)
0204   {
0205     std::cerr << Name() << "::getNodes - missing " << m_inputNodeName << std::endl;
0206     return Fun4AllReturnCodes::ABORTRUN;
0207   }
0208 
0209   m_hits = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0210   if (!m_hits)
0211   {
0212     std::cerr << Name() << "::getNodes - missing TRKR_HITSET" << std::endl;
0213     return Fun4AllReturnCodes::ABORTRUN;
0214   }
0215 
0216   m_crossingDecisions = findNode::getClass<TpcCrossingDecisionContainer>(topNode, m_crossingDecisionNodeName);
0217   if (!m_crossingDecisions)
0218   {
0219     std::cerr << Name() << "::getNodes - missing " << m_crossingDecisionNodeName << std::endl;
0220     return Fun4AllReturnCodes::ABORTRUN;
0221   }
0222 
0223   m_geomContainerTpc = findNode::getClass<PHG4TpcGeomContainer>(topNode, "TPCGEOMCONTAINER");
0224   if (!m_geomContainerTpc)
0225   {
0226     std::cerr << Name() << "::getNodes - missing TPCGEOMCONTAINER" << std::endl;
0227     return Fun4AllReturnCodes::ABORTRUN;
0228   }
0229 
0230   return Fun4AllReturnCodes::EVENT_OK;
0231 }
0232 
0233 int Tpc_PolyClusterizer::createNodes(PHCompositeNode* topNode)
0234 {
0235   PHNodeIterator iter(topNode);
0236   PHCompositeNode* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0237   if (!dstNode)
0238   {
0239     dstNode = new PHCompositeNode("DST");
0240     topNode->addNode(dstNode);
0241   }
0242 
0243   m_clusters = findNode::getClass<Tpc_PolyClusterContainer>(topNode, m_outputNodeName);
0244   if (!m_clusters)
0245   {
0246     m_clusters = new Tpc_PolyClusterContainerv1();
0247     PHIODataNode<PHObject>* node = new PHIODataNode<PHObject>(m_clusters, m_outputNodeName, "PHObject");
0248     dstNode->addNode(node);
0249     std::cout << Name() << "::createNodes - created " << m_outputNodeName << " node" << std::endl;
0250   }
0251 
0252   return Fun4AllReturnCodes::EVENT_OK;
0253 }
0254 
0255 unsigned int Tpc_PolyClusterizer::drift_lookup_index(const unsigned int layer_index,
0256                                                      const unsigned int side,
0257                                                      const unsigned int sector,
0258                                                      const unsigned int sample)
0259 {
0260   return (((layer_index * NSides + side) * NSectors + sector) * NPhiSamples + sample);
0261 }
0262 
0263 bool Tpc_PolyClusterizer::build_drift_lookup()
0264 {
0265   if (!m_idealPadMap || !m_garfield)
0266   {
0267     return false;
0268   }
0269   if (m_reverseDriftStepNs <= 0.0 || !std::isfinite(m_reverseDriftStepNs))
0270   {
0271     return false;
0272   }
0273 
0274   for (DriftPolyline& polyline : m_driftLookup)
0275   {
0276     polyline.phi = 0.0;
0277     polyline.points.clear();
0278   }
0279 
0280   unsigned int nbuilt = 0;
0281   for (unsigned int layer = FirstLayer; layer <= LastLayer; ++layer)
0282   {
0283     const unsigned int layer_index = layer - FirstLayer;
0284     const double radius = m_idealPadMap->get_radius(layer);
0285     const unsigned int pads_per_sector = m_idealPadMap->get_pads_per_sector_for_layer(layer);
0286     if (!std::isfinite(radius) || pads_per_sector == 0U)
0287     {
0288       std::cerr << Name() << "::build_drift_lookup - invalid geometry for layer " << layer << std::endl;
0289       return false;
0290     }
0291 
0292     for (unsigned int side = 0; side < NSides; ++side)
0293     {
0294       const double z0 = (side == 0U) ? m_startZSouth : m_startZNorth;
0295       for (unsigned int sector = 0; sector < NSectors; ++sector)
0296       {
0297         for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0298         {
0299           const unsigned int local_phibin = static_cast<unsigned int>(std::llround(
0300               phi_sample_fraction(sample) * static_cast<double>(pads_per_sector - 1U)));
0301           const unsigned int global_pad = sector * pads_per_sector + local_phibin;
0302           const double phi_local = m_idealPadMap->get_phi(side, sector, layer, local_phibin);
0303           const double phi_global = m_idealPadMap->get_phi(side, layer, global_pad);
0304           if (!std::isfinite(phi_local) || !std::isfinite(phi_global))
0305           {
0306             std::cerr << Name() << "::build_drift_lookup - invalid phi for layer " << layer
0307                       << " side " << side
0308                       << " sector " << sector
0309                       << " local_phibin " << local_phibin
0310                       << " global_pad " << global_pad
0311                       << " phi_local " << phi_local
0312                       << " phi_global " << phi_global << std::endl;
0313             return false;
0314           }
0315 
0316           const double phi_difference = wrap_phi(phi_global - phi_local);
0317           if (std::abs(phi_difference) > PhiConsistencyTolerance)
0318           {
0319             std::cerr << Name() << "::build_drift_lookup - inconsistent IdealPadMap phi overloads"
0320                       << " layer " << layer
0321                       << " side " << side
0322                       << " sector " << sector
0323                       << " local_phibin " << local_phibin
0324                       << " global_pad " << global_pad
0325                       << " phi_local " << phi_local
0326                       << " phi_global " << phi_global
0327                       << " phi_difference " << phi_difference << std::endl;
0328             return false;
0329           }
0330 
0331           const double phi = phi_local;
0332           const double x0 = radius * std::cos(phi);
0333           const double y0 = radius * std::sin(phi);
0334           TPolyLine3D* drift = m_garfield->ReverseDrift(x0, y0, z0, m_reverseDriftStepNs);
0335           if (!drift || drift->GetN() <= 0)
0336           {
0337             delete drift;
0338             std::cerr << Name() << "::build_drift_lookup - ReverseDrift failed for layer " << layer
0339                       << " side " << side << " sector " << sector << " sample " << sample << std::endl;
0340             return false;
0341           }
0342 
0343           const int npoints = drift->GetN();
0344           const Float_t* xyz = drift->GetP();
0345           if (!xyz || npoints <= 0)
0346           {
0347             delete drift;
0348             std::cerr << Name() << "::build_drift_lookup - empty drift points for layer " << layer
0349                       << " side " << side << " sector " << sector << " sample " << sample << std::endl;
0350             return false;
0351           }
0352 
0353           DriftPolyline& polyline = m_driftLookup[drift_lookup_index(layer_index, side, sector, sample)];
0354           polyline.phi = phi;
0355           polyline.points.resize(static_cast<std::size_t>(npoints));
0356           for (int ipoint = 0; ipoint < npoints; ++ipoint)
0357           {
0358             const int idx = 3 * ipoint;
0359             DriftPoint& point = polyline.points[static_cast<std::size_t>(ipoint)];
0360             const double output_r = std::hypot(static_cast<double>(xyz[idx]), static_cast<double>(xyz[idx + 1]));
0361             const double output_phi = unwrap_phi_near(std::atan2(static_cast<double>(xyz[idx + 1]), static_cast<double>(xyz[idx])), phi);
0362             point.delta_r = static_cast<float>(output_r - radius);
0363             point.delta_phi = static_cast<float>(output_phi - phi);
0364             point.z = xyz[idx + 2];
0365           }
0366           delete drift;
0367           ++nbuilt;
0368         }
0369       }
0370     }
0371   }
0372 
0373   for (unsigned int layer = FirstLayer; layer <= LastLayer; ++layer)
0374   {
0375     const unsigned int layer_index = layer - FirstLayer;
0376     const double radius = m_idealPadMap->get_radius(layer);
0377     for (unsigned int side = 0; side < NSides; ++side)
0378     {
0379       const DriftPolyline& reference = m_driftLookup[drift_lookup_index(layer_index, side, 0, 0)];
0380       bool symmetry_ok = !reference.points.empty() && std::isfinite(radius);
0381       unsigned int length_mismatches = 0;
0382       double max_delta_r = 0.0;
0383       double max_delta_phi_arc = 0.0;
0384       double max_delta_z = 0.0;
0385 
0386       for (unsigned int sector = 0; sector < NSectors; ++sector)
0387       {
0388         for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0389         {
0390           const DriftPolyline& polyline = m_driftLookup[drift_lookup_index(layer_index, side, sector, sample)];
0391           if (polyline.points.size() != reference.points.size())
0392           {
0393             ++length_mismatches;
0394             symmetry_ok = false;
0395           }
0396 
0397           const std::size_t npoints = std::min(reference.points.size(), polyline.points.size());
0398           for (std::size_t ipoint = 0; ipoint < npoints; ++ipoint)
0399           {
0400             const DriftPoint& ref_point = reference.points[ipoint];
0401             const DriftPoint& point = polyline.points[ipoint];
0402             max_delta_r = std::max(max_delta_r, std::abs(static_cast<double>(point.delta_r - ref_point.delta_r)));
0403             max_delta_phi_arc = std::max(max_delta_phi_arc,
0404                                          std::abs(radius * wrap_phi(static_cast<double>(point.delta_phi - ref_point.delta_phi))));
0405             max_delta_z = std::max(max_delta_z, std::abs(static_cast<double>(point.z - ref_point.z)));
0406           }
0407         }
0408       }
0409 
0410       if (max_delta_r > 0.001 ||
0411           max_delta_phi_arc > 0.001 ||
0412           max_delta_z > 0.001)
0413       {
0414         symmetry_ok = false;
0415       }
0416 
0417       std::cout << Name() << "::build_drift_lookup - phi symmetry "
0418                 << (symmetry_ok ? "holds" : "broken")
0419                 << " layer=" << layer
0420                 << " side=" << side
0421                 << " max_dr=" << max_delta_r
0422                 << " max_r_dphi=" << max_delta_phi_arc
0423                 << " max_dz=" << max_delta_z
0424                 << " length_mismatches=" << length_mismatches << std::endl;
0425     }
0426   }
0427 
0428   if (Verbosity() > 0)
0429   {
0430     std::cout << Name() << "::build_drift_lookup - built " << nbuilt << " drift polylines" << std::endl;
0431   }
0432   return nbuilt == NLayers * NSides * NSectors * NPhiSamples;
0433 }
0434 
0435 bool Tpc_PolyClusterizer::sample_drift_lookup(const unsigned int layer,
0436                                               const unsigned int side,
0437                                               const unsigned int pad,
0438                                               const unsigned int tbin,
0439                                               const short crossing,
0440                                               double& x,
0441                                               double& y,
0442                                               double& z) const
0443 {
0444   if (!m_idealPadMap)
0445   {
0446     return false;
0447   }
0448   if (layer < FirstLayer || layer > LastLayer)
0449   {
0450     return false;
0451   }
0452   if (side >= NSides)
0453   {
0454     return false;
0455   }
0456   if (m_reverseDriftStepNs <= 0.0 || !std::isfinite(m_reverseDriftStepNs))
0457   {
0458     return false;
0459   }
0460 
0461   const unsigned int pads_per_sector = m_idealPadMap->get_pads_per_sector_for_layer(layer);
0462   if (pads_per_sector == 0U)
0463   {
0464     return false;
0465   }
0466 
0467   const unsigned int sector = pad / pads_per_sector;
0468   if (sector >= NSectors)
0469   {
0470     return false;
0471   }
0472 
0473   const double hit_radius = m_idealPadMap->get_radius(layer);
0474   const double hit_phi = m_idealPadMap->get_phi(side, layer, pad);
0475   if (!std::isfinite(hit_radius) || !std::isfinite(hit_phi))
0476   {
0477     return false;
0478   }
0479 
0480   const double target_time_ns = (static_cast<double>(tbin) - m_t0) * m_tpcAdcClock - static_cast<double>(crossing) * m_crossingPeriodNs;
0481   if (target_time_ns <= 0.0 || !std::isfinite(target_time_ns))
0482   {
0483     return false;
0484   }
0485 
0486   const unsigned int layer_index = layer - FirstLayer;
0487   std::array<const DriftPolyline*, NPhiSamples> samples{};
0488   std::array<double, NPhiSamples> sample_phi{};
0489   for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0490   {
0491     samples[sample] = &m_driftLookup[drift_lookup_index(layer_index, side, sector, sample)];
0492     if (samples[sample]->points.empty())
0493     {
0494       return false;
0495     }
0496     sample_phi[sample] = samples[sample]->phi;
0497     if (sample > 0U)
0498     {
0499       sample_phi[sample] = unwrap_phi_near(sample_phi[sample], sample_phi[sample - 1U]);
0500     }
0501   }
0502 
0503   const double unwrapped_hit_phi = unwrap_phi_near(hit_phi, sample_phi[NPhiSamples / 2U]);
0504   const bool increasing = sample_phi[NPhiSamples - 1U] >= sample_phi[0];
0505 
0506   bool bracket_found = false;
0507   unsigned int sample0 = 0;
0508   unsigned int sample1 = 0;
0509   double phi_fraction = 0.0;
0510   if ((increasing && unwrapped_hit_phi <= sample_phi[0]) || (!increasing && unwrapped_hit_phi >= sample_phi[0]))
0511   {
0512     sample0 = 0;
0513     sample1 = 0;
0514     bracket_found = true;
0515   }
0516   else if ((increasing && unwrapped_hit_phi >= sample_phi[NPhiSamples - 1U]) ||
0517            (!increasing && unwrapped_hit_phi <= sample_phi[NPhiSamples - 1U]))
0518   {
0519     sample0 = NPhiSamples - 1U;
0520     sample1 = NPhiSamples - 1U;
0521     bracket_found = true;
0522   }
0523   else
0524   {
0525     for (unsigned int sample = 0; sample + 1U < NPhiSamples; ++sample)
0526     {
0527       const bool in_interval = increasing ? (unwrapped_hit_phi >= sample_phi[sample] && unwrapped_hit_phi <= sample_phi[sample + 1U]) : (unwrapped_hit_phi <= sample_phi[sample] && unwrapped_hit_phi >= sample_phi[sample + 1U]);
0528       if (!in_interval)
0529       {
0530         continue;
0531       }
0532 
0533       sample0 = sample;
0534       sample1 = sample + 1U;
0535       const double denom = sample_phi[sample1] - sample_phi[sample0];
0536       phi_fraction = (denom != 0.0) ? clamp_unit((unwrapped_hit_phi - sample_phi[sample0]) / denom) : 0.0;
0537       bracket_found = true;
0538       break;
0539     }
0540   }
0541 
0542   if (!bracket_found)
0543   {
0544     std::cerr << Name() << "::sample_drift_lookup - failed to bracket hit phi"
0545               << " layer " << layer
0546               << " side " << side
0547               << " sector " << sector
0548               << " pad " << pad
0549               << " hit_phi " << hit_phi
0550               << " unwrapped_hit_phi " << unwrapped_hit_phi
0551               << " sample_phi";
0552     for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0553     {
0554       std::cerr << " " << sample_phi[sample];
0555     }
0556     std::cerr << std::endl;
0557     return false;
0558   }
0559 
0560   auto sample_time = [this, target_time_ns](const DriftPolyline& polyline,
0561                                             double& delta_r,
0562                                             double& delta_phi,
0563                                             double& point_z) -> bool
0564   {
0565     const int npoints = static_cast<int>(polyline.points.size());
0566     if (npoints <= 0)
0567     {
0568       return false;
0569     }
0570 
0571     const double max_time_ns = static_cast<double>(npoints - 1) * m_reverseDriftStepNs;
0572     if (target_time_ns > max_time_ns)
0573     {
0574       return false;
0575     }
0576 
0577     const double fbin = target_time_ns / m_reverseDriftStepNs;
0578     const int i0 = std::min(static_cast<int>(std::floor(fbin)), npoints - 1);
0579     const int i1 = std::min(i0 + 1, npoints - 1);
0580     const double frac = fbin - static_cast<double>(i0);
0581 
0582     const DriftPoint& p0 = polyline.points[static_cast<std::size_t>(i0)];
0583     const DriftPoint& p1 = polyline.points[static_cast<std::size_t>(i1)];
0584     const double dphi0 = static_cast<double>(p0.delta_phi);
0585     const double dphi1 = unwrap_phi_near(static_cast<double>(p1.delta_phi), dphi0);
0586     delta_r = static_cast<double>(p0.delta_r) + frac * static_cast<double>(p1.delta_r - p0.delta_r);
0587     delta_phi = dphi0 + frac * (dphi1 - dphi0);
0588     point_z = static_cast<double>(p0.z) + frac * static_cast<double>(p1.z - p0.z);
0589     return std::isfinite(delta_r) && std::isfinite(delta_phi) && std::isfinite(point_z);
0590   };
0591 
0592   double delta_r0 = 0.0;
0593   double delta_phi0 = 0.0;
0594   double z0 = 0.0;
0595   const bool valid0 = sample_time(*samples[sample0], delta_r0, delta_phi0, z0);
0596 
0597   double delta_r1 = 0.0;
0598   double delta_phi1 = 0.0;
0599   double z1 = 0.0;
0600   const bool same_sample = sample0 == sample1;
0601   const bool valid1 = same_sample ? valid0 : sample_time(*samples[sample1], delta_r1, delta_phi1, z1);
0602   if (same_sample)
0603   {
0604     delta_r1 = delta_r0;
0605     delta_phi1 = delta_phi0;
0606     z1 = z0;
0607   }
0608 
0609   if (!valid0 && !valid1)
0610   {
0611     return false;
0612   }
0613 
0614   double delta_r = 0.0;
0615   double delta_phi = 0.0;
0616   if (valid0 && valid1 && !same_sample)
0617   {
0618     delta_r = delta_r0 + phi_fraction * (delta_r1 - delta_r0);
0619     const double unwrapped_delta_phi1 = unwrap_phi_near(delta_phi1, delta_phi0);
0620     delta_phi = delta_phi0 + phi_fraction * (unwrapped_delta_phi1 - delta_phi0);
0621     z = z0 + phi_fraction * (z1 - z0);
0622   }
0623   else if (valid0)
0624   {
0625     delta_r = delta_r0;
0626     delta_phi = delta_phi0;
0627     z = z0;
0628   }
0629   else
0630   {
0631     delta_r = delta_r1;
0632     delta_phi = delta_phi1;
0633     z = z1;
0634   }
0635 
0636   const double radius = hit_radius + delta_r;
0637   const double output_phi = unwrapped_hit_phi + delta_phi;
0638   x = radius * std::cos(output_phi);
0639   y = radius * std::sin(output_phi);
0640   return std::isfinite(x) && std::isfinite(y) && std::isfinite(z);
0641 }
0642 
0643 bool Tpc_PolyClusterizer::make_xyz_point(TrkrDefs::hitsetkey hsk,
0644                                          TrkrDefs::hitkey hk,
0645                                          const short crossing,
0646                                          Point& p) const
0647 {
0648   if (!m_hits || !m_idealPadMap)
0649   {
0650     return false;
0651   }
0652 
0653   TrkrHitSet* hitset = m_hits->findHitSet(hsk);
0654   if (!hitset)
0655   {
0656     return false;
0657   }
0658   TrkrHit* hit = hitset->getHit(hk);
0659   if (!hit)
0660   {
0661     return false;
0662   }
0663 
0664   const unsigned int layer = TrkrDefs::getLayer(hsk);
0665   const unsigned int hit_side = TpcDefs::getSide(hsk);
0666   const unsigned int pad = TpcDefs::getPad(hk);
0667   const unsigned int tbin = TpcDefs::getTBin(hk);
0668   const double adc = hit->getAdc();
0669 
0670   double x = 0.0;
0671   double y = 0.0;
0672   double z = 0.0;
0673   if (!sample_drift_lookup(layer, hit_side, pad, tbin, crossing, x, y, z))
0674   {
0675     return false;
0676   }
0677 
0678   p.hitsetkey = hsk;
0679   p.hitkey = hk;
0680   p.layer = layer;
0681   p.side = hit_side;
0682   p.pad = pad;
0683   p.tbin = tbin;
0684   p.adc = adc;
0685   p.x = x;
0686   p.y = y;
0687   p.z = z;
0688   return true;
0689 }
0690 
0691 Tpc_PolyClusterizer::ClusterParameters
0692 Tpc_PolyClusterizer::make_cluster_parameters(const std::vector<Point>& points,
0693                                              const Centroid& centroid,
0694                                              const int side) const
0695 {
0696   ClusterParameters params;
0697   if (points.empty() || !centroid.ok || !m_idealPadMap)
0698   {
0699     return params;
0700   }
0701 
0702   std::set<unsigned int> pads;
0703   std::set<unsigned int> tbins;
0704   std::map<unsigned int, double> adc_by_pad;
0705   for (const Point& p : points)
0706   {
0707     params.adc += p.adc;
0708     pads.insert(p.pad);
0709     tbins.insert(p.tbin);
0710     adc_by_pad[p.pad] += p.adc;
0711   }
0712 
0713   params.phi_width = static_cast<unsigned int>(pads.size());
0714   params.time_width = static_cast<unsigned int>(tbins.size());
0715 
0716   unsigned int max_adc_pad = 0;
0717   double max_adc = -std::numeric_limits<double>::max();
0718   for (const auto& pad_adc : adc_by_pad)
0719   {
0720     if (pad_adc.second > max_adc)
0721     {
0722       max_adc = pad_adc.second;
0723       max_adc_pad = pad_adc.first;
0724     }
0725   }
0726 
0727   const unsigned int total_phibins = m_idealPadMap->get_total_phibins(centroid.layer);
0728   const double pad_phi_width = total_phibins > 0U ? 2.0 * M_PI / static_cast<double>(total_phibins) : 0.0;
0729   const double cluster_phi = std::atan2(centroid.y, centroid.x);
0730   const double max_adc_phi = m_idealPadMap->get_phi(static_cast<unsigned int>(side), centroid.layer, max_adc_pad);
0731   if (pad_phi_width > 0.0 && std::isfinite(cluster_phi) && std::isfinite(max_adc_phi))
0732   {
0733     params.phase = wrap_phi(cluster_phi - max_adc_phi) / pad_phi_width;
0734   }
0735 
0736   return params;
0737 }
0738 
0739 Tpc_PolyClusterizer::Centroid
0740 Tpc_PolyClusterizer::make_centroid(const std::vector<Point>& points)
0741 {
0742   Centroid c;
0743   if (points.empty())
0744   {
0745     return c;
0746   }
0747 
0748   double sx = 0.0;
0749   double sy = 0.0;
0750   double sz = 0.0;
0751   for (const Point& p : points)
0752   {
0753     sx += p.x;
0754     sy += p.y;
0755     sz += p.z;
0756   }
0757 
0758   const double n = static_cast<double>(points.size());
0759   c.x = sx / n;
0760   c.y = sy / n;
0761   c.z = sz / n;
0762 
0763   double sxx = 0.0;
0764   double syy = 0.0;
0765   double szz = 0.0;
0766   for (const Point& p : points)
0767   {
0768     const double dx = p.x - c.x;
0769     const double dy = p.y - c.y;
0770     const double dz = p.z - c.z;
0771     sxx += dx * dx;
0772     syy += dy * dy;
0773     szz += dz * dz;
0774   }
0775 
0776   c.rms_x = std::sqrt(sxx / n);
0777   c.rms_y = std::sqrt(syy / n);
0778   c.rms_z = std::sqrt(szz / n);
0779   c.layer = points.front().layer;
0780   c.ok = std::isfinite(c.x) && std::isfinite(c.y) && std::isfinite(c.z);
0781   return c;
0782 }
0783 
0784 int Tpc_PolyClusterizer::process_event(PHCompositeNode* topNode)
0785 {
0786   if (!m_assembledTracks || !m_clusters || !m_crossingDecisions || !m_garfield) { return Fun4AllReturnCodes::EVENT_OK;
0787 }
0788 
0789   ActsGeometry* tGeometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
0790   if (!tGeometry)
0791   {
0792     std::cerr << Name() << "::process_event - missing ActsGeometry, using RMS fallback for errors" << std::endl;
0793   }
0794   m_clusters->Reset();
0795 
0796   const unsigned int nassembled = m_assembledTracks->size();
0797   unsigned int nclusters = 0;
0798   std::map<TrkrDefs::hitsetkey, unsigned int> next_cluster_index_by_hitset;
0799 
0800   for (int side = 0; side < 2; ++side)
0801   {
0802     for (unsigned int sector = 0; sector < 12; ++sector)
0803     {
0804       for (unsigned int iassembled = 0; iassembled < nassembled; ++iassembled)
0805       {
0806         const Tpc_AssembledTrack* assembled = m_assembledTracks->get_track(iassembled);
0807         if (!assembled) { continue;
0808 }
0809         if (assembled->get_side() != side) { continue;
0810 }
0811         if (assembled->get_first_sector() % 12U != sector) { continue;
0812 }
0813         const TpcCrossingDecision* crossing_decision = m_crossingDecisions->get_decision(assembled->get_track_id());
0814         if (!crossing_decision) { continue;
0815 }
0816         const unsigned char selected_tier = crossing_decision->get_selected_tier();
0817         if (selected_tier > m_maxAcceptedTier) { continue;
0818 }
0819         const short selected_crossing = crossing_decision->get_selected_crossing();
0820 
0821 
0822         std::map<TrkrDefs::hitsetkey, std::vector<Point>> points_by_hitset;
0823         for (unsigned int ih = 0; ih < assembled->size_hit_indices(); ++ih)
0824         {
0825           const Tpc_AssembledTrack::HitIndex hi = assembled->get_hit_index(ih);
0826           if (TpcDefs::getSide(hi.first) != static_cast<unsigned int>(side)) { continue;
0827 }
0828 
0829           Point p;
0830           if (make_xyz_point(hi.first, hi.second, selected_crossing, p)) { points_by_hitset[p.hitsetkey].push_back(p);
0831 }
0832         }
0833         if (points_by_hitset.empty()) { continue;
0834 }
0835 
0836         for (const auto& hitset_points : points_by_hitset)
0837         {
0838           const TrkrDefs::hitsetkey cluster_hitsetkey = hitset_points.first;
0839           const std::vector<Point>& points = hitset_points.second;
0840           const Centroid centroid = make_centroid(points);
0841           if (!centroid.ok) { continue;
0842 }
0843 
0844           const unsigned int cluster_index = next_cluster_index_by_hitset[cluster_hitsetkey]++;
0845           const TrkrDefs::cluskey trkr_cluster_key = TrkrDefs::genClusKey(cluster_hitsetkey, cluster_index);
0846 
0847           Tpc_PolyClusterv1* out = new Tpc_PolyClusterv1();
0848           out->set_event(m_event);
0849           out->set_cluster_id(m_clusters->size());
0850           out->set_source_assembled_track_id(assembled->get_track_id());
0851           out->set_trkr_cluster_key(trkr_cluster_key);
0852           out->set_side(side);
0853           out->set_centroid_x(centroid.x);
0854           out->set_centroid_y(centroid.y);
0855           out->set_centroid_z(centroid.z);
0856 
0857           double phi_error = std::hypot(centroid.rms_x, centroid.rms_y);
0858           double z_error = std::fabs(centroid.rms_z);
0859           PHG4TpcGeom* layergeom = m_geomContainerTpc ? m_geomContainerTpc->GetLayerCellGeom(centroid.layer) : nullptr;
0860           if (layergeom && tGeometry)
0861           {
0862             double adc_sum = 0.0;
0863             double iphi_sum = 0.0;
0864             double iphi2_sum = 0.0;
0865             double t_sum = 0.0;
0866             double t2_sum = 0.0;
0867             int phibinhi = -1;
0868             int phibinlo = std::numeric_limits<int>::max();
0869             int tbinhi = -1;
0870             int tbinlo = std::numeric_limits<int>::max();
0871 
0872             for (const Point& p : points)
0873             {
0874               if (p.adc <= 0.0) { continue;
0875 }
0876 
0877               const int iphi = static_cast<int>(p.pad);
0878               const int it = static_cast<int>(p.tbin);
0879               const double adc = p.adc;
0880               phibinhi = std::max(iphi, phibinhi);
0881               phibinlo = std::min(iphi, phibinlo);
0882               tbinhi = std::max(it, tbinhi);
0883               tbinlo = std::min(it, tbinlo);
0884 
0885               iphi_sum += static_cast<double>(iphi) * adc;
0886               iphi2_sum += square(static_cast<double>(iphi)) * adc;
0887 
0888               const double t = layergeom->get_zcenter(it);
0889               t_sum += t * adc;
0890               t2_sum += square(t) * adc;
0891               adc_sum += adc;
0892             }
0893 
0894             const double drift_velocity = tGeometry->get_drift_velocity();
0895             if (adc_sum > 0.0 && std::isfinite(drift_velocity))
0896             {
0897               const double radius = layergeom->get_radius();
0898               const double clusiphi = iphi_sum / adc_sum;
0899               const double clust = t_sum / adc_sum;
0900               const double phi_cov = std::max(0.0, (iphi2_sum / adc_sum - square(clusiphi)) * square(layergeom->get_phistep()));
0901               const double t_cov = std::max(0.0, t2_sum / adc_sum - square(clust));
0902               const double phi_err_square = (phibinhi == phibinlo) ?
0903                   9.0 * (square(radius * layergeom->get_phistep()) / 12.0) :
0904                   square(radius) * phi_cov / (adc_sum * 0.14);
0905               const double t_err_square = (tbinhi == tbinlo) ?
0906                   9.0 * (square(layergeom->get_zstep()) / 12.0) :
0907                   t_cov / (adc_sum * 0.14);
0908 
0909               if (phi_err_square >= 0.0 && std::isfinite(phi_err_square)) { phi_error = std::sqrt(phi_err_square);
0910 }
0911               const double z_err_square = t_err_square * square(drift_velocity);
0912               if (z_err_square >= 0.0 && std::isfinite(z_err_square)) { z_error = std::sqrt(z_err_square);
0913 }
0914             }
0915           }
0916 
0917           out->set_rms_x(phi_error);
0918           out->set_rms_y(0.0);
0919           out->set_rms_z(z_error);
0920 
0921           const ClusterParameters params = make_cluster_parameters(points, centroid, static_cast<int>(points.front().side));
0922           out->set_adc(params.adc);
0923           out->set_phi_width(params.phi_width);
0924           out->set_time_width(params.time_width);
0925           out->set_phase(params.phase);
0926           for (const Point& p : points) { out->add_hit(p.hitsetkey, p.hitkey, p.x, p.y, p.z);
0927 }
0928           if (out->size_hits() == 0)
0929           {
0930             delete out;
0931             continue;
0932           }
0933           m_clusters->add_cluster(out);
0934           ++nclusters;
0935         }
0936       }
0937     }
0938   }
0939 
0940   if (Verbosity() > 0)
0941   {
0942     std::cout << Name() << "::process_event - event " << m_event
0943               << " assembled_tracks=" << nassembled
0944               << " poly_clusters=" << m_clusters->size()
0945               << " layer_clusters=" << nclusters << std::endl;
0946   }
0947 
0948   ++m_event;
0949   return Fun4AllReturnCodes::EVENT_OK;
0950 }