File indexing completed on 2026-08-31 08:21:25
0001 #include "TpcCrossingFinder.h"
0002
0003 #include "IdealPadMap.h"
0004 #include "Tpc_AssembledTrack.h"
0005 #include "Tpc_AssembledTrackContainer.h"
0006 #include "TpcCrossingDecisionContainerv1.h"
0007 #include "TpcCrossingDecisionv1.h"
0008 #include "Tpc_FittingTools.h"
0009
0010 #include <cdbobjects/CDBTTree.h>
0011 #include <fun4all/Fun4AllReturnCodes.h>
0012
0013 #include <phool/PHCompositeNode.h>
0014 #include <phool/PHIODataNode.h>
0015 #include <phool/PHNodeIterator.h>
0016 #include <phool/PHObject.h>
0017 #include <phool/getClass.h>
0018
0019 #include <ffamodules/CDBInterface.h>
0020
0021 #include <globalvertex/SvtxVertex.h>
0022 #include <globalvertex/SvtxVertexMap.h>
0023 #include <trackbase/InttDefs.h>
0024 #include <trackbase/TpcDefs.h>
0025 #include <trackbase/TrkrClusterContainer.h>
0026 #include <trackbase/TrkrDefs.h>
0027 #include <trackbase/TrkrHit.h>
0028 #include <trackbase/TrkrHitSet.h>
0029 #include <trackbase/TrkrHitSetContainer.h>
0030
0031 #include <phgarfield/PHGarfield.h>
0032 #include <TPolyLine3D.h>
0033
0034 #include <g4detectors/PHG4TpcGeom.h>
0035 #include <g4detectors/PHG4TpcGeomContainer.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 <utility>
0045 #include <vector>
0046
0047 namespace
0048 {
0049 constexpr unsigned int FirstLayer = 7;
0050 constexpr unsigned int LastLayer = 54;
0051 constexpr unsigned int NLayers = LastLayer - FirstLayer + 1;
0052 constexpr unsigned int NSides = 2;
0053 constexpr unsigned int NSectors = 12;
0054 constexpr double PhiConsistencyTolerance = 1.0e-10;
0055 constexpr unsigned char InvalidConfidenceTier = std::numeric_limits<unsigned char>::max();
0056
0057 double wrap_phi(double phi)
0058 {
0059 while (phi > M_PI) { phi -= 2.0 * M_PI;
0060 }
0061 while (phi <= -M_PI) { phi += 2.0 * M_PI;
0062 }
0063 return phi;
0064 }
0065
0066 double unwrap_phi_near(double phi, const double reference)
0067 {
0068 while (phi - reference > M_PI) { phi -= 2.0 * M_PI;
0069 }
0070 while (phi - reference <= -M_PI) { phi += 2.0 * M_PI;
0071 }
0072 return phi;
0073 }
0074
0075 double clamp_unit(const double value)
0076 {
0077 return std::max(0.0, std::min(1.0, value));
0078 }
0079
0080 double phi_sample_fraction(const unsigned int sample)
0081 {
0082 if (TpcCrossingFinder::NPhiSamples <= 1U) { return 0.0;
0083 }
0084 return static_cast<double>(sample) / static_cast<double>(TpcCrossingFinder::NPhiSamples - 1U);
0085 }
0086
0087 float finite_float_or_nan(const double value)
0088 {
0089 return std::isfinite(value) ? static_cast<float>(value) : std::numeric_limits<float>::quiet_NaN();
0090 }
0091 }
0092
0093 TpcCrossingFinder::TpcCrossingFinder(const std::string& name)
0094 : SubsysReco(name)
0095 {
0096 }
0097
0098 TpcCrossingFinder::~TpcCrossingFinder()
0099 {
0100 delete m_idealPadMap;
0101 m_idealPadMap = nullptr;
0102 delete m_garfield;
0103 m_garfield = nullptr;
0104 }
0105
0106 int TpcCrossingFinder::InitRun(PHCompositeNode* topNode)
0107 {
0108 if (getNodes(topNode) != Fun4AllReturnCodes::EVENT_OK) { return Fun4AllReturnCodes::ABORTRUN;
0109 }
0110 if (createNodes(topNode) != Fun4AllReturnCodes::EVENT_OK) { return Fun4AllReturnCodes::ABORTRUN;
0111 }
0112
0113 delete m_idealPadMap;
0114 m_idealPadMap = new IdealPadMap();
0115 if (m_idealPadMap->load_from_cdb(Verbosity()) != 0 || !m_idealPadMap->is_loaded())
0116 {
0117 std::cerr << Name() << "::InitRun - failed to load IdealPadMap" << std::endl;
0118 return Fun4AllReturnCodes::ABORTRUN;
0119 }
0120
0121 PHG4TpcGeom* layergeom = m_geomContainerTpc->GetLayerCellGeom(20);
0122 if (layergeom)
0123 {
0124 const double rot_x = layergeom->get_rot_x();
0125 const double rot_y = layergeom->get_rot_y();
0126 const double rot_z = layergeom->get_rot_z();
0127 const double place_x = layergeom->get_place_x();
0128 const double place_y = layergeom->get_place_y();
0129 const double place_z = layergeom->get_place_z();
0130 if (use_survey_geometry)
0131 {
0132 m_tpcMove = {{place_x, place_y, place_z}};
0133 m_tpcRotations = {{{{rot_x, rot_y, rot_z}}, {{0.0, 0.0, 0.0}}}};
0134 }
0135 }
0136
0137 delete m_garfield;
0138 const std::string electricFieldMap = CDBInterface::instance()->getUrl("Tpc_PolySeeding_EField");
0139
0140 const auto kefffile = CDBInterface::instance()->getUrl("Tpc_PolyClusterizer_kEff");
0141
0142 if (!kefffile.empty())
0143 {
0144 auto keffcdbtree = std::make_unique<CDBTTree>(kefffile);
0145 keffcdbtree->LoadCalibrations();
0146 m_kEffSide0 = keffcdbtree->GetSingleFloatValue("keffside0");
0147 m_kEffSide1 = keffcdbtree->GetSingleFloatValue("keffside1");
0148 }
0149
0150 m_garfield = new PHGarfield(Name() + "_PHGarfield", electricFieldMap, m_kEffSide0, m_kEffSide1);
0151 configure_garfield(m_garfield);
0152 if (m_garfield->InitRun(topNode) != Fun4AllReturnCodes::EVENT_OK)
0153 {
0154 std::cerr << Name() << "::InitRun - PHGarfield InitRun failed" << std::endl;
0155 return Fun4AllReturnCodes::ABORTRUN;
0156 }
0157
0158 if (!build_drift_lookup()) { return Fun4AllReturnCodes::ABORTRUN;
0159 }
0160
0161 m_event = 0;
0162 return Fun4AllReturnCodes::EVENT_OK;
0163 }
0164
0165 int TpcCrossingFinder::getNodes(PHCompositeNode* topNode)
0166 {
0167 m_assembledTracks = findNode::getClass<Tpc_AssembledTrackContainer>(topNode, m_inputNodeName);
0168 if (!m_assembledTracks)
0169 {
0170 std::cerr << Name() << "::getNodes - missing " << m_inputNodeName << std::endl;
0171 return Fun4AllReturnCodes::ABORTRUN;
0172 }
0173
0174 m_hits = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0175 if (!m_hits)
0176 {
0177 std::cerr << Name() << "::getNodes - missing TRKR_HITSET" << std::endl;
0178 return Fun4AllReturnCodes::ABORTRUN;
0179 }
0180
0181 m_clusterMap = findNode::getClass<TrkrClusterContainer>(topNode, "TRKR_CLUSTER");
0182 if (!m_clusterMap && Verbosity() > 0)
0183 {
0184 std::cout << Name() << "::getNodes - optional TRKR_CLUSTER node not found" << std::endl;
0185 }
0186
0187 m_geomContainerTpc = findNode::getClass<PHG4TpcGeomContainer>(topNode, "TPCGEOMCONTAINER");
0188 if (!m_geomContainerTpc)
0189 {
0190 std::cerr << Name() << "::getNodes - missing TPCGEOMCONTAINER" << std::endl;
0191 return Fun4AllReturnCodes::ABORTRUN;
0192 }
0193
0194 m_vertexMap = findNode::getClass<SvtxVertexMap>(topNode, m_vertexMapNodeName);
0195 if (!m_vertexMap && Verbosity() > 0)
0196 {
0197 std::cout << Name() << "::getNodes - optional " << m_vertexMapNodeName << " node not found" << std::endl;
0198 }
0199
0200 return Fun4AllReturnCodes::EVENT_OK;
0201 }
0202
0203 int TpcCrossingFinder::createNodes(PHCompositeNode* topNode)
0204 {
0205 PHNodeIterator iter(topNode);
0206 PHCompositeNode* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0207 if (!dstNode)
0208 {
0209 dstNode = new PHCompositeNode("DST");
0210 topNode->addNode(dstNode);
0211 }
0212
0213 m_decisions = findNode::getClass<TpcCrossingDecisionContainerv1>(topNode, m_outputNodeName);
0214 if (!m_decisions)
0215 {
0216 m_decisions = new TpcCrossingDecisionContainerv1();
0217 PHIODataNode<PHObject>* node = new PHIODataNode<PHObject>(m_decisions, m_outputNodeName, "PHObject");
0218 dstNode->addNode(node);
0219 std::cout << Name() << "::createNodes - created " << m_outputNodeName << " node" << std::endl;
0220 }
0221
0222 return Fun4AllReturnCodes::EVENT_OK;
0223 }
0224
0225 void TpcCrossingFinder::configure_garfield(PHGarfield* garfield) const
0226 {
0227 if (!garfield) { return;
0228 }
0229
0230 garfield->MoveTpc(m_tpcMove[0], m_tpcMove[1], m_tpcMove[2]);
0231 for (const auto& rotation : m_tpcRotations)
0232 {
0233 garfield->RotateTpc(rotation[0], rotation[1], rotation[2]);
0234 }
0235 garfield->SetCMVoltageDefault(m_cmVoltageDefault);
0236 }
0237
0238 unsigned int TpcCrossingFinder::drift_lookup_index(const unsigned int layer_index,
0239 const unsigned int side,
0240 const unsigned int sector,
0241 const unsigned int sample)
0242 {
0243 return (((layer_index * NSides + side) * NSectors + sector) * NPhiSamples + sample);
0244 }
0245
0246 bool TpcCrossingFinder::build_drift_lookup()
0247 {
0248 if (!m_idealPadMap || !m_garfield) { return false;
0249 }
0250 if (m_reverseDriftStepNs <= 0.0 || !std::isfinite(m_reverseDriftStepNs)) { return false;
0251 }
0252
0253 m_maxLookupTimeNs = std::numeric_limits<double>::max();
0254 for (DriftPolyline& polyline : m_driftLookup)
0255 {
0256 polyline.phi = 0.0;
0257 polyline.points.clear();
0258 }
0259
0260 unsigned int nbuilt = 0;
0261 for (unsigned int layer = FirstLayer; layer <= LastLayer; ++layer)
0262 {
0263 const unsigned int layer_index = layer - FirstLayer;
0264 const double radius = m_idealPadMap->get_radius(layer);
0265 const unsigned int pads_per_sector = m_idealPadMap->get_pads_per_sector_for_layer(layer);
0266 if (!std::isfinite(radius) || pads_per_sector == 0U) { return false;
0267 }
0268
0269 for (unsigned int side = 0; side < NSides; ++side)
0270 {
0271 const double z0 = (side == 0U) ? m_startZSouth : m_startZNorth;
0272 for (unsigned int sector = 0; sector < NSectors; ++sector)
0273 {
0274 for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0275 {
0276 const unsigned int local_phibin = static_cast<unsigned int>(std::llround(
0277 phi_sample_fraction(sample) * static_cast<double>(pads_per_sector - 1U)));
0278 const unsigned int global_pad = sector * pads_per_sector + local_phibin;
0279 const double phi_local = m_idealPadMap->get_phi(side, sector, layer, local_phibin);
0280 const double phi_global = m_idealPadMap->get_phi(side, layer, global_pad);
0281 if (!std::isfinite(phi_local) || !std::isfinite(phi_global)) { return false;
0282 }
0283 if (std::abs(wrap_phi(phi_global - phi_local)) > PhiConsistencyTolerance) { return false;
0284 }
0285
0286 const double x0 = radius * std::cos(phi_local);
0287 const double y0 = radius * std::sin(phi_local);
0288 TPolyLine3D* drift = m_garfield->ReverseDrift(x0, y0, z0, m_reverseDriftStepNs);
0289 if (!drift || drift->GetN() <= 0)
0290 {
0291 delete drift;
0292 return false;
0293 }
0294
0295 const int npoints = drift->GetN();
0296 const Float_t* xyz = drift->GetP();
0297 if (!xyz || npoints <= 0)
0298 {
0299 delete drift;
0300 return false;
0301 }
0302
0303 DriftPolyline& polyline = m_driftLookup[drift_lookup_index(layer_index, side, sector, sample)];
0304 polyline.phi = phi_local;
0305 polyline.points.resize(static_cast<std::size_t>(npoints));
0306 for (int ipoint = 0; ipoint < npoints; ++ipoint)
0307 {
0308 const int idx = 3 * ipoint;
0309 DriftPoint& point = polyline.points[static_cast<std::size_t>(ipoint)];
0310 const double output_r = std::hypot(static_cast<double>(xyz[idx]), static_cast<double>(xyz[idx + 1]));
0311 const double output_phi = unwrap_phi_near(std::atan2(static_cast<double>(xyz[idx + 1]), static_cast<double>(xyz[idx])), phi_local);
0312 point.delta_r = static_cast<float>(output_r - radius);
0313 point.delta_phi = static_cast<float>(output_phi - phi_local);
0314 point.z = xyz[idx + 2];
0315 }
0316 m_maxLookupTimeNs = std::min(m_maxLookupTimeNs, static_cast<double>(npoints - 1) * m_reverseDriftStepNs);
0317 delete drift;
0318 ++nbuilt;
0319 }
0320 }
0321 }
0322 }
0323
0324 if (Verbosity() > 0)
0325 {
0326 std::cout << Name() << "::build_drift_lookup - built " << nbuilt
0327 << " drift polylines max_lookup_time_ns=" << m_maxLookupTimeNs << std::endl;
0328 }
0329 return nbuilt == NLayers * NSides * NSectors * NPhiSamples && std::isfinite(m_maxLookupTimeNs) && m_maxLookupTimeNs > 0.0;
0330 }
0331
0332 bool TpcCrossingFinder::sample_drift_lookup(const unsigned int layer,
0333 const unsigned int side,
0334 const unsigned int pad,
0335 const unsigned int tbin,
0336 const short crossing,
0337 double& x,
0338 double& y,
0339 double& z) const
0340 {
0341 if (!m_idealPadMap) { return false;
0342 }
0343 if (layer < FirstLayer || layer > LastLayer) { return false;
0344 }
0345 if (side >= NSides) { return false;
0346 }
0347 if (m_reverseDriftStepNs <= 0.0 || !std::isfinite(m_reverseDriftStepNs)) { return false;
0348 }
0349
0350 const unsigned int pads_per_sector = m_idealPadMap->get_pads_per_sector_for_layer(layer);
0351 if (pads_per_sector == 0U) { return false;
0352 }
0353 const unsigned int sector = pad / pads_per_sector;
0354 if (sector >= NSectors) { return false;
0355 }
0356
0357 const double hit_radius = m_idealPadMap->get_radius(layer);
0358 const double hit_phi = m_idealPadMap->get_phi(side, layer, pad);
0359 if (!std::isfinite(hit_radius) || !std::isfinite(hit_phi)) { return false;
0360 }
0361
0362 const double target_time_ns = (static_cast<double>(tbin) - m_t0) * m_tpcAdcClock
0363 - static_cast<double>(crossing) * m_crossingPeriodNs;
0364 if (target_time_ns <= 0.0 || !std::isfinite(target_time_ns)) { return false;
0365 }
0366
0367 const unsigned int layer_index = layer - FirstLayer;
0368 std::array<const DriftPolyline*, NPhiSamples> samples{};
0369 std::array<double, NPhiSamples> sample_phi{};
0370 for (unsigned int sample = 0; sample < NPhiSamples; ++sample)
0371 {
0372 samples[sample] = &m_driftLookup[drift_lookup_index(layer_index, side, sector, sample)];
0373 if (samples[sample]->points.empty()) { return false;
0374 }
0375 sample_phi[sample] = samples[sample]->phi;
0376 if (sample > 0U) { sample_phi[sample] = unwrap_phi_near(sample_phi[sample], sample_phi[sample - 1U]);
0377 }
0378 }
0379
0380 const double unwrapped_hit_phi = unwrap_phi_near(hit_phi, sample_phi[NPhiSamples / 2U]);
0381 const bool increasing = sample_phi[NPhiSamples - 1U] >= sample_phi[0];
0382
0383 bool bracket_found = false;
0384 unsigned int sample0 = 0;
0385 unsigned int sample1 = 0;
0386 double phi_fraction = 0.0;
0387 if ((increasing && unwrapped_hit_phi <= sample_phi[0]) || (!increasing && unwrapped_hit_phi >= sample_phi[0]))
0388 {
0389 bracket_found = true;
0390 }
0391 else if ((increasing && unwrapped_hit_phi >= sample_phi[NPhiSamples - 1U]) ||
0392 (!increasing && unwrapped_hit_phi <= sample_phi[NPhiSamples - 1U]))
0393 {
0394 sample0 = NPhiSamples - 1U;
0395 sample1 = NPhiSamples - 1U;
0396 bracket_found = true;
0397 }
0398 else
0399 {
0400 for (unsigned int sample = 0; sample + 1U < NPhiSamples; ++sample)
0401 {
0402 const bool in_interval = increasing ?
0403 (unwrapped_hit_phi >= sample_phi[sample] && unwrapped_hit_phi <= sample_phi[sample + 1U]) :
0404 (unwrapped_hit_phi <= sample_phi[sample] && unwrapped_hit_phi >= sample_phi[sample + 1U]);
0405 if (!in_interval) { continue;
0406 }
0407
0408 sample0 = sample;
0409 sample1 = sample + 1U;
0410 const double denom = sample_phi[sample1] - sample_phi[sample0];
0411 phi_fraction = (denom != 0.0) ? clamp_unit((unwrapped_hit_phi - sample_phi[sample0]) / denom) : 0.0;
0412 bracket_found = true;
0413 break;
0414 }
0415 }
0416 if (!bracket_found) { return false;
0417 }
0418
0419 auto sample_time = [this, target_time_ns](const DriftPolyline& polyline,
0420 double& delta_r,
0421 double& delta_phi,
0422 double& point_z) -> bool
0423 {
0424 const int npoints = static_cast<int>(polyline.points.size());
0425 if (npoints <= 0) { return false;
0426 }
0427 const double max_time_ns = static_cast<double>(npoints - 1) * m_reverseDriftStepNs;
0428 if (target_time_ns > max_time_ns) { return false;
0429 }
0430
0431 const double fbin = target_time_ns / m_reverseDriftStepNs;
0432 const int i0 = std::min(static_cast<int>(std::floor(fbin)), npoints - 1);
0433 const int i1 = std::min(i0 + 1, npoints - 1);
0434 const double frac = fbin - static_cast<double>(i0);
0435
0436 const DriftPoint& p0 = polyline.points[static_cast<std::size_t>(i0)];
0437 const DriftPoint& p1 = polyline.points[static_cast<std::size_t>(i1)];
0438 const double dphi0 = static_cast<double>(p0.delta_phi);
0439 const double dphi1 = unwrap_phi_near(static_cast<double>(p1.delta_phi), dphi0);
0440 delta_r = static_cast<double>(p0.delta_r) + frac * static_cast<double>(p1.delta_r - p0.delta_r);
0441 delta_phi = dphi0 + frac * (dphi1 - dphi0);
0442 point_z = static_cast<double>(p0.z) + frac * static_cast<double>(p1.z - p0.z);
0443 return std::isfinite(delta_r) && std::isfinite(delta_phi) && std::isfinite(point_z);
0444 };
0445
0446 double delta_r0 = 0.0;
0447 double delta_phi0 = 0.0;
0448 double z0 = 0.0;
0449 const bool valid0 = sample_time(*samples[sample0], delta_r0, delta_phi0, z0);
0450 double delta_r1 = 0.0;
0451 double delta_phi1 = 0.0;
0452 double z1 = 0.0;
0453 const bool same_sample = sample0 == sample1;
0454 const bool valid1 = same_sample ? valid0 : sample_time(*samples[sample1], delta_r1, delta_phi1, z1);
0455 if (same_sample)
0456 {
0457 delta_r1 = delta_r0;
0458 delta_phi1 = delta_phi0;
0459 z1 = z0;
0460 }
0461 if (!valid0 && !valid1) { return false;
0462 }
0463
0464 double delta_r = 0.0;
0465 double delta_phi = 0.0;
0466 if (valid0 && valid1 && !same_sample)
0467 {
0468 delta_r = delta_r0 + phi_fraction * (delta_r1 - delta_r0);
0469 const double unwrapped_delta_phi1 = unwrap_phi_near(delta_phi1, delta_phi0);
0470 delta_phi = delta_phi0 + phi_fraction * (unwrapped_delta_phi1 - delta_phi0);
0471 z = z0 + phi_fraction * (z1 - z0);
0472 }
0473 else if (valid0)
0474 {
0475 delta_r = delta_r0;
0476 delta_phi = delta_phi0;
0477 z = z0;
0478 }
0479 else
0480 {
0481 delta_r = delta_r1;
0482 delta_phi = delta_phi1;
0483 z = z1;
0484 }
0485
0486 const double radius = hit_radius + delta_r;
0487 const double output_phi = unwrapped_hit_phi + delta_phi;
0488 x = radius * std::cos(output_phi);
0489 y = radius * std::sin(output_phi);
0490 return std::isfinite(x) && std::isfinite(y) && std::isfinite(z);
0491 }
0492
0493 bool TpcCrossingFinder::make_xyz_point(TrkrDefs::hitsetkey hsk,
0494 TrkrDefs::hitkey hk,
0495 short crossing,
0496 Point& p) const
0497 {
0498 const unsigned int layer = TrkrDefs::getLayer(hsk);
0499 const unsigned int hit_side = TpcDefs::getSide(hsk);
0500 const unsigned int pad = TpcDefs::getPad(hk);
0501 const unsigned int tbin = TpcDefs::getTBin(hk);
0502
0503 double x = 0.0;
0504 double y = 0.0;
0505 double z = 0.0;
0506 if (!sample_drift_lookup(layer, hit_side, pad, tbin, crossing, x, y, z)) { return false;
0507 }
0508
0509 p.hitsetkey = hsk;
0510 p.hitkey = hk;
0511 p.layer = layer;
0512 p.side = hit_side;
0513 p.pad = pad;
0514 p.tbin = tbin;
0515 p.x = x;
0516 p.y = y;
0517 p.z = z;
0518 return true;
0519 }
0520
0521 bool TpcCrossingFinder::find_time_extrema(const Tpc_AssembledTrack* track,
0522 TrkrDefs::hitsetkey& min_hsk,
0523 TrkrDefs::hitkey& min_hk,
0524 TrkrDefs::hitsetkey& max_hsk,
0525 TrkrDefs::hitkey& max_hk) const
0526 {
0527 if (!track || track->size_hit_indices() == 0) { return false;
0528 }
0529
0530 unsigned int min_tbin = std::numeric_limits<unsigned int>::max();
0531 unsigned int max_tbin = 0;
0532 bool found = false;
0533 for (unsigned int ih = 0; ih < track->size_hit_indices(); ++ih)
0534 {
0535 const Tpc_AssembledTrack::HitIndex hi = track->get_hit_index(ih);
0536 const unsigned int tbin = TpcDefs::getTBin(hi.second);
0537 if (!found || tbin < min_tbin)
0538 {
0539 min_tbin = tbin;
0540 min_hsk = hi.first;
0541 min_hk = hi.second;
0542 }
0543 if (!found || tbin > max_tbin)
0544 {
0545 max_tbin = tbin;
0546 max_hsk = hi.first;
0547 max_hk = hi.second;
0548 }
0549 found = true;
0550 }
0551 return found;
0552 }
0553
0554 std::set<short> TpcCrossingFinder::get_available_crossings() const
0555 {
0556 std::set<short> available_crossings;
0557 if (!m_vertexMap) { return available_crossings;
0558 }
0559
0560 for (auto & iter : *m_vertexMap)
0561 {
0562 const SvtxVertex* vertex = iter.second;
0563 if (!vertex) { continue;
0564 }
0565 available_crossings.insert(vertex->get_beam_crossing());
0566 }
0567 return available_crossings;
0568 }
0569
0570 std::set<short> TpcCrossingFinder::get_intt_crossings() const
0571 {
0572 std::set<short> intt_crossings;
0573 if (!m_clusterMap) { return intt_crossings;
0574 }
0575
0576 for (const auto& hitsetkey : m_clusterMap->getHitSetKeys())
0577 {
0578 if (static_cast<TrkrDefs::TrkrId>(TrkrDefs::getTrkrId(hitsetkey)) != TrkrDefs::TrkrId::inttId) { continue;
0579 }
0580 intt_crossings.insert(static_cast<short>(InttDefs::getTimeBucketId(hitsetkey)));
0581 }
0582 return intt_crossings;
0583 }
0584
0585 std::map<short, std::vector<TpcCrossingFinder::SiliconVertexHypothesis>>
0586 TpcCrossingFinder::get_vertices_by_crossing() const
0587 {
0588 std::map<short, std::vector<SiliconVertexHypothesis>> vertices_by_crossing;
0589 if (!m_vertexMap) { return vertices_by_crossing;
0590 }
0591
0592 for (auto & iter : *m_vertexMap)
0593 {
0594 const SvtxVertex* vertex = iter.second;
0595 if (!vertex) { continue;
0596 }
0597
0598 SiliconVertexHypothesis hyp;
0599 hyp.crossing = vertex->get_beam_crossing();
0600 hyp.vertex_id = iter.first;
0601 hyp.x = vertex->get_x();
0602 hyp.y = vertex->get_y();
0603 hyp.z = vertex->get_z();
0604 const double errzz = vertex->get_error(2, 2);
0605 hyp.sigma_z = errzz >= 0.0 ? std::sqrt(errzz) : std::numeric_limits<double>::quiet_NaN();
0606 hyp.ntracks = vertex->size_tracks();
0607 vertices_by_crossing[hyp.crossing].push_back(hyp);
0608 }
0609 return vertices_by_crossing;
0610 }
0611
0612 std::vector<std::pair<TrkrDefs::hitsetkey, TrkrDefs::hitkey>>
0613 TpcCrossingFinder::select_representatives(const Tpc_AssembledTrack* track,
0614 TrkrDefs::hitsetkey min_hsk,
0615 TrkrDefs::hitkey min_hk,
0616 TrkrDefs::hitsetkey max_hsk,
0617 TrkrDefs::hitkey max_hk) const
0618 {
0619 std::vector<std::pair<TrkrDefs::hitsetkey, TrkrDefs::hitkey>> representatives;
0620 auto add_unique = [&representatives](const Tpc_AssembledTrack::HitIndex& hi)
0621 {
0622 if (std::find(representatives.begin(), representatives.end(), hi) == representatives.end())
0623 {
0624 representatives.push_back(hi);
0625 }
0626 };
0627
0628 add_unique({min_hsk, min_hk});
0629 add_unique({max_hsk, max_hk});
0630 if (!track) { return representatives;
0631 }
0632
0633 std::map<unsigned int, Tpc_AssembledTrack::HitIndex> hit_by_layer;
0634 for (unsigned int ih = 0; ih < track->size_hit_indices(); ++ih)
0635 {
0636 const Tpc_AssembledTrack::HitIndex hi = track->get_hit_index(ih);
0637 hit_by_layer.emplace(TrkrDefs::getLayer(hi.first), hi);
0638 }
0639 if (hit_by_layer.empty()) { return representatives;
0640 }
0641
0642 add_unique(hit_by_layer.begin()->second);
0643 add_unique(hit_by_layer.rbegin()->second);
0644
0645 std::vector<unsigned int> layers;
0646 layers.reserve(hit_by_layer.size());
0647 for (const auto& item : hit_by_layer) { layers.push_back(item.first);
0648 }
0649 const unsigned int fractions[] = {1U, 2U};
0650 for (const unsigned int fraction : fractions)
0651 {
0652 if (layers.size() < 3U) { continue;
0653 }
0654 const std::size_t index = (fraction * (layers.size() - 1U)) / 3U;
0655 add_unique(hit_by_layer[layers[index]]);
0656 }
0657
0658 return representatives;
0659 }
0660
0661 TpcCrossingFinder::ZFitResult TpcCrossingFinder::estimate_tpc_z0_diagnostics(std::vector<Point> points) const
0662 {
0663 ZFitResult result;
0664 if (points.size() < 2U) { return result;
0665 }
0666 std::sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
0667 return a.layer < b.layer;
0668 });
0669
0670 std::vector<double> s(points.size(), 0.0);
0671 for (std::size_t i = 1; i < points.size(); ++i)
0672 {
0673 s[i] = s[i - 1] + std::hypot(points[i].x - points[i - 1].x, points[i].y - points[i - 1].y);
0674 }
0675
0676 std::vector<Tpc_FittingTools::FitPoint> fit_points;
0677 fit_points.reserve(points.size());
0678 std::vector<Tpc_FittingTools::FitPoint> radial_fit_points;
0679 radial_fit_points.reserve(points.size());
0680 for (std::size_t i = 0; i < points.size(); ++i)
0681 {
0682 fit_points.emplace_back(s[i], points[i].z, 1.0);
0683 radial_fit_points.emplace_back(std::hypot(points[i].x, points[i].y), points[i].z, 1.0);
0684 }
0685 const Tpc_FittingTools::LineFit zfit = Tpc_FittingTools::fitLine(fit_points);
0686 const Tpc_FittingTools::LineFit radial_zfit = Tpc_FittingTools::fitLine(radial_fit_points);
0687 if (!zfit.ok || !radial_zfit.ok) { return result;
0688 }
0689
0690 double best_s = s.front();
0691 double best_d2 = std::numeric_limits<double>::max();
0692 for (std::size_t i = 0; i + 1U < points.size(); ++i)
0693 {
0694 const double vx = points[i + 1U].x - points[i].x;
0695 const double vy = points[i + 1U].y - points[i].y;
0696 const double len2 = vx * vx + vy * vy;
0697 double frac = 0.0;
0698 if (len2 > 0.0)
0699 {
0700 frac = clamp_unit(-(points[i].x * vx + points[i].y * vy) / len2);
0701 }
0702 const double x = points[i].x + frac * vx;
0703 const double y = points[i].y + frac * vy;
0704 const double d2 = x * x + y * y;
0705 if (d2 < best_d2)
0706 {
0707 best_d2 = d2;
0708 best_s = s[i] + frac * (s[i + 1U] - s[i]);
0709 }
0710 }
0711
0712 result.valid = std::isfinite(best_s) && std::isfinite(best_d2);
0713 result.slope = zfit.slope;
0714 result.intercept = zfit.intercept;
0715 result.chi2 = zfit.chi2;
0716 result.ndf = zfit.ndof;
0717 result.s_at_pca = best_s;
0718 result.minimum_radius = std::sqrt(best_d2);
0719 result.z_at_pca = zfit.slope * best_s + zfit.intercept;
0720 result.z_at_r0 = radial_zfit.intercept;
0721 result.points = std::move(points);
0722 result.path_length.reserve(s.size());
0723 for (const double value : s) { result.path_length.push_back(finite_float_or_nan(value));
0724 }
0725 result.valid = result.valid && std::isfinite(result.z_at_pca) && std::isfinite(result.z_at_r0);
0726 return result;
0727 }
0728
0729 bool TpcCrossingFinder::estimate_tpc_z0(std::vector<Point>& points, double& z0) const
0730 {
0731 const ZFitResult result = estimate_tpc_z0_diagnostics(points);
0732 if (!result.valid) { return false;
0733 }
0734 z0 = result.z_at_r0;
0735 return true;
0736 }
0737
0738 bool TpcCrossingFinder::point_in_tpc(const Point& p) const
0739 {
0740 const double r = std::hypot(p.x, p.y);
0741 const double inner = m_idealPadMap ? m_idealPadMap->get_radius(FirstLayer) : 0.0;
0742 const double outer = m_idealPadMap ? m_idealPadMap->get_radius(LastLayer) : 0.0;
0743 return std::isfinite(r) && std::isfinite(p.z) &&
0744 r >= inner - m_radialTolerance &&
0745 r <= outer + m_radialTolerance &&
0746 p.z >= -m_tpcHalfLength - m_zTolerance &&
0747 p.z <= m_tpcHalfLength + m_zTolerance;
0748 }
0749
0750 bool TpcCrossingFinder::point_in_correct_side(const Point& p) const
0751 {
0752 if (p.side == 0U) { return p.z >= -m_tpcHalfLength - m_zTolerance && p.z <= m_centralMembraneTolerance;
0753 }
0754 if (p.side == 1U) { return p.z >= -m_centralMembraneTolerance && p.z <= m_tpcHalfLength + m_zTolerance;
0755 }
0756 return false;
0757 }
0758
0759 TpcCrossingFinder::Candidate
0760 TpcCrossingFinder::test_candidate(const Tpc_AssembledTrack* track,
0761 short crossing,
0762 TrkrDefs::hitsetkey min_hsk,
0763 TrkrDefs::hitkey min_hk,
0764 TrkrDefs::hitsetkey max_hsk,
0765 TrkrDefs::hitkey max_hk,
0766 const std::map<short, std::vector<SiliconVertexHypothesis>>& vertices_by_crossing) const
0767 {
0768 Candidate candidate;
0769 candidate.crossing = crossing;
0770 candidate.silicon_vertex_id = std::numeric_limits<unsigned int>::max();
0771 candidate.tpc_z0 = std::numeric_limits<double>::quiet_NaN();
0772 candidate.silicon_vertex_z = std::numeric_limits<double>::quiet_NaN();
0773 candidate.delta_z = std::numeric_limits<double>::quiet_NaN();
0774
0775 TpcCrossingCandidate& qa = candidate.qa;
0776 qa.crossing = crossing;
0777 qa.is_available_from_intt = true;
0778 qa.passes_time_window = true;
0779 qa.was_tested = true;
0780 qa.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::Unknown);
0781 qa.max_lookup_time_ns = finite_float_or_nan(m_maxLookupTimeNs);
0782 qa.min_tbin_time_crossing0_ns = finite_float_or_nan((static_cast<double>(TpcDefs::getTBin(min_hk)) - m_t0) * m_tpcAdcClock);
0783 qa.max_tbin_time_crossing0_ns = finite_float_or_nan((static_cast<double>(TpcDefs::getTBin(max_hk)) - m_t0) * m_tpcAdcClock);
0784 qa.candidate_min_time_ns = finite_float_or_nan(static_cast<double>(qa.min_tbin_time_crossing0_ns) - static_cast<double>(crossing) * m_crossingPeriodNs);
0785 qa.candidate_max_time_ns = finite_float_or_nan(static_cast<double>(qa.max_tbin_time_crossing0_ns) - static_cast<double>(crossing) * m_crossingPeriodNs);
0786 qa.min_time_margin_ns = qa.candidate_min_time_ns;
0787 qa.max_time_margin_ns = finite_float_or_nan(m_maxLookupTimeNs - static_cast<double>(qa.candidate_max_time_ns));
0788 qa.candidate_qa_bits |= FromInttCrossing | PassedTimeWindow;
0789
0790 auto fill_point_summary = [](const Point& point,
0791 unsigned int& layer,
0792 unsigned int& side,
0793 unsigned int& pad,
0794 unsigned int& tbin,
0795 float& x,
0796 float& y,
0797 float& z,
0798 float& r,
0799 float& phi)
0800 {
0801 layer = point.layer;
0802 side = point.side;
0803 pad = point.pad;
0804 tbin = point.tbin;
0805 x = finite_float_or_nan(point.x);
0806 y = finite_float_or_nan(point.y);
0807 z = finite_float_or_nan(point.z);
0808 r = finite_float_or_nan(std::hypot(point.x, point.y));
0809 phi = finite_float_or_nan(std::atan2(point.y, point.x));
0810 };
0811
0812 auto distance_to_padplane = [this](const Point& point) -> float
0813 {
0814 const double padplane_z = point.side == 0U ? -m_tpcHalfLength : m_tpcHalfLength;
0815 return finite_float_or_nan(std::abs(point.z - padplane_z));
0816 };
0817
0818 Point min_point;
0819 Point max_point;
0820 qa.min_endpoint_garfield_valid = make_xyz_point(min_hsk, min_hk, crossing, min_point);
0821 qa.max_endpoint_garfield_valid = make_xyz_point(max_hsk, max_hk, crossing, max_point);
0822 if (qa.min_endpoint_garfield_valid)
0823 {
0824 fill_point_summary(min_point, qa.min_time_layer, qa.min_time_side, qa.min_time_pad, qa.min_time_tbin,
0825 qa.min_time_x, qa.min_time_y, qa.min_time_z, qa.min_time_r, qa.min_time_phi);
0826 qa.min_time_distance_to_central_membrane = finite_float_or_nan(std::abs(min_point.z));
0827 qa.min_time_distance_to_padplane = distance_to_padplane(min_point);
0828 qa.min_time_inside_tpc = point_in_tpc(min_point);
0829 qa.min_time_correct_side = point_in_correct_side(min_point);
0830 qa.candidate_qa_bits |= MinEndpointGarfieldOK;
0831 }
0832 if (qa.max_endpoint_garfield_valid)
0833 {
0834 fill_point_summary(max_point, qa.max_time_layer, qa.max_time_side, qa.max_time_pad, qa.max_time_tbin,
0835 qa.max_time_x, qa.max_time_y, qa.max_time_z, qa.max_time_r, qa.max_time_phi);
0836 qa.max_time_distance_to_central_membrane = finite_float_or_nan(std::abs(max_point.z));
0837 qa.max_time_distance_to_padplane = distance_to_padplane(max_point);
0838 qa.max_time_inside_tpc = point_in_tpc(max_point);
0839 qa.max_time_correct_side = point_in_correct_side(max_point);
0840 qa.candidate_qa_bits |= MaxEndpointGarfieldOK;
0841 }
0842
0843 if (!qa.min_endpoint_garfield_valid || !qa.max_endpoint_garfield_valid)
0844 {
0845 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::GarfieldValid);
0846 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::GarfieldInvalid);
0847 qa.rejection_status = candidate.rejection_status;
0848 return candidate;
0849 }
0850
0851 qa.endpoints_inside_tpc = qa.min_time_inside_tpc && qa.max_time_inside_tpc;
0852 if (qa.endpoints_inside_tpc) { qa.candidate_qa_bits |= EndpointsInsideTPC;
0853 }
0854 if (!qa.endpoints_inside_tpc)
0855 {
0856 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::InsideTpc);
0857 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::OutsideTpcVolume);
0858 qa.rejection_status = candidate.rejection_status;
0859 return candidate;
0860 }
0861
0862 qa.endpoints_on_correct_side = qa.min_time_correct_side && qa.max_time_correct_side;
0863 if (qa.endpoints_on_correct_side) { qa.candidate_qa_bits |= EndpointsCorrectSide;
0864 }
0865 if (!qa.endpoints_on_correct_side)
0866 {
0867 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::CorrectSide);
0868 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::WrongTpcSide);
0869 qa.rejection_status = candidate.rejection_status;
0870 return candidate;
0871 }
0872
0873 std::vector<Point> points;
0874 for (const auto& hi : select_representatives(track, min_hsk, min_hk, max_hsk, max_hk))
0875 {
0876 Point p;
0877 if (!make_xyz_point(hi.first, hi.second, crossing, p)) { continue;
0878 }
0879 if (!point_in_tpc(p) || !point_in_correct_side(p)) { continue;
0880 }
0881 points.push_back(p);
0882 }
0883
0884 const ZFitResult fit = estimate_tpc_z0_diagnostics(points);
0885 qa.fit_valid = fit.valid;
0886 if (!fit.valid)
0887 {
0888 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::FitValid);
0889 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::FitFailed);
0890 qa.rejection_status = candidate.rejection_status;
0891 return candidate;
0892 }
0893
0894 qa.candidate_qa_bits |= FitSuccessful;
0895 qa.n_fit_points = fit.points.size();
0896 qa.z_vs_s_slope = finite_float_or_nan(fit.slope);
0897 qa.z_vs_s_intercept = finite_float_or_nan(fit.intercept);
0898 qa.z_fit_chi2 = finite_float_or_nan(fit.chi2);
0899 qa.z_fit_ndf = fit.ndf;
0900 qa.s_at_transverse_pca = finite_float_or_nan(fit.s_at_pca);
0901 qa.minimum_transverse_radius = finite_float_or_nan(fit.minimum_radius);
0902 qa.tpc_z_at_transverse_pca = finite_float_or_nan(fit.z_at_pca);
0903 qa.tpc_z_at_r0 = finite_float_or_nan(fit.z_at_r0);
0904 for (std::size_t i = 0; i < fit.points.size(); ++i)
0905 {
0906 const Point& point = fit.points[i];
0907 qa.fit_point_layer.push_back(point.layer);
0908 qa.fit_point_pad.push_back(point.pad);
0909 qa.fit_point_tbin.push_back(point.tbin);
0910 qa.fit_point_x.push_back(finite_float_or_nan(point.x));
0911 qa.fit_point_y.push_back(finite_float_or_nan(point.y));
0912 qa.fit_point_z.push_back(finite_float_or_nan(point.z));
0913 qa.fit_point_r.push_back(finite_float_or_nan(std::hypot(point.x, point.y)));
0914 qa.fit_point_s.push_back(i < fit.path_length.size() ? fit.path_length[i] : TpcCrossingCandidate::nan());
0915 }
0916
0917 std::map<unsigned int, Tpc_AssembledTrack::HitIndex> hit_by_layer;
0918 if (track)
0919 {
0920 for (unsigned int ih = 0; ih < track->size_hit_indices(); ++ih)
0921 {
0922 const Tpc_AssembledTrack::HitIndex hi = track->get_hit_index(ih);
0923 hit_by_layer.emplace(TrkrDefs::getLayer(hi.first), hi);
0924 }
0925 }
0926 if (!hit_by_layer.empty())
0927 {
0928 Point inner;
0929 if (make_xyz_point(hit_by_layer.begin()->second.first, hit_by_layer.begin()->second.second, crossing, inner))
0930 {
0931 qa.inner_layer = inner.layer;
0932 qa.inner_tbin = inner.tbin;
0933 qa.inner_x = finite_float_or_nan(inner.x);
0934 qa.inner_y = finite_float_or_nan(inner.y);
0935 qa.inner_z = finite_float_or_nan(inner.z);
0936 }
0937 Point outer;
0938 if (make_xyz_point(hit_by_layer.rbegin()->second.first, hit_by_layer.rbegin()->second.second, crossing, outer))
0939 {
0940 qa.outer_layer = outer.layer;
0941 qa.outer_tbin = outer.tbin;
0942 qa.outer_x = finite_float_or_nan(outer.x);
0943 qa.outer_y = finite_float_or_nan(outer.y);
0944 qa.outer_z = finite_float_or_nan(outer.z);
0945 }
0946 }
0947
0948 candidate.tpc_z0 = fit.z_at_r0;
0949 if (!std::isfinite(candidate.tpc_z0) || std::abs(candidate.tpc_z0 - m_collisionZ) > m_maxCandidateVertexZ)
0950 {
0951 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::VertexCompatible);
0952 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::VertexIncompatible);
0953 qa.rejection_status = candidate.rejection_status;
0954 return candidate;
0955 }
0956
0957 const auto vertex_iter = vertices_by_crossing.find(crossing);
0958 if (vertex_iter != vertices_by_crossing.end())
0959 {
0960 qa.n_vertices_at_crossing = vertex_iter->second.size();
0961 double best_abs_delta = std::numeric_limits<double>::max();
0962 for (const SiliconVertexHypothesis& vertex : vertex_iter->second)
0963 {
0964 const double delta_z = fit.z_at_r0 - vertex.z;
0965 const double abs_delta = std::abs(delta_z);
0966 const double pull_z = std::isfinite(vertex.sigma_z) && vertex.sigma_z > 0.0 ? delta_z / vertex.sigma_z : std::numeric_limits<double>::quiet_NaN();
0967 qa.candidate_vertex_ids.push_back(vertex.vertex_id);
0968 qa.candidate_vertex_z.push_back(finite_float_or_nan(vertex.z));
0969 qa.candidate_vertex_sigma_z.push_back(finite_float_or_nan(vertex.sigma_z));
0970 qa.candidate_vertex_ntracks.push_back(vertex.ntracks);
0971 qa.candidate_vertex_delta_z.push_back(finite_float_or_nan(delta_z));
0972 qa.candidate_vertex_pull_z.push_back(finite_float_or_nan(pull_z));
0973 if (!std::isfinite(abs_delta) || abs_delta >= best_abs_delta) { continue;
0974 }
0975
0976 best_abs_delta = abs_delta;
0977 candidate.has_silicon_vertex = true;
0978 candidate.silicon_vertex_id = vertex.vertex_id;
0979 candidate.silicon_vertex_z = vertex.z;
0980 candidate.delta_z = delta_z;
0981 qa.closest_vertex_id = vertex.vertex_id;
0982 qa.closest_vertex_z = finite_float_or_nan(vertex.z);
0983 qa.closest_vertex_sigma_z = finite_float_or_nan(vertex.sigma_z);
0984 qa.closest_vertex_delta_z = finite_float_or_nan(delta_z);
0985 qa.closest_vertex_abs_delta_z = finite_float_or_nan(abs_delta);
0986 qa.closest_vertex_pull_z = finite_float_or_nan(pull_z);
0987 }
0988 }
0989
0990 qa.has_silicon_vertex = candidate.has_silicon_vertex;
0991 if (qa.has_silicon_vertex) { qa.candidate_qa_bits |= HasSiliconVertex;
0992 }
0993 candidate.vertex_compatible = candidate.has_silicon_vertex && std::abs(candidate.delta_z) <= m_maxVertexDz;
0994 qa.vertex_compatible = candidate.vertex_compatible;
0995 if (qa.vertex_compatible) { qa.candidate_qa_bits |= PassesVertexDz;
0996 }
0997
0998 candidate.tpc_valid = true;
0999 qa.tpc_valid = candidate.tpc_valid;
1000 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::FinalRanking);
1001 qa.rejection_status = candidate.rejection_status;
1002 return candidate;
1003 }
1004
1005 int TpcCrossingFinder::process_event(PHCompositeNode* topNode)
1006 {
1007 if (getNodes(topNode) != Fun4AllReturnCodes::EVENT_OK) { return Fun4AllReturnCodes::ABORTEVENT;
1008 }
1009 if (!m_assembledTracks || !m_decisions) { return Fun4AllReturnCodes::EVENT_OK;
1010 }
1011 m_decisions->Reset();
1012
1013 std::set<short> available_crossings = get_available_crossings();
1014 const std::set<short> intt_crossings = get_intt_crossings();
1015 available_crossings.insert(intt_crossings.begin(), intt_crossings.end());
1016 const auto vertices_by_crossing = get_vertices_by_crossing();
1017 const bool has_vertex_map = m_vertexMap != nullptr;
1018
1019 if (Verbosity() > 0)
1020 {
1021 std::cout << Name() << "::process_event - event " << m_event << " available candidate crossings:";
1022 for (const short crossing : available_crossings) { std::cout << " " << crossing;
1023 }
1024 std::cout << " | intt crossings:";
1025 for (const short crossing : intt_crossings) { std::cout << " " << crossing;
1026 }
1027 std::cout << " | vertices by crossing:";
1028 for (const auto& item : vertices_by_crossing)
1029 {
1030 std::cout << " " << item.first << "(" << item.second.size() << ":";
1031 for (std::size_t ivtx = 0; ivtx < item.second.size(); ++ivtx)
1032 {
1033 const SiliconVertexHypothesis& vertex = item.second[ivtx];
1034 if (ivtx != 0U) { std::cout << ",";
1035 }
1036 std::cout << " id=" << vertex.vertex_id
1037 << " x=" << vertex.x
1038 << " y=" << vertex.y
1039 << " z=" << vertex.z;
1040 }
1041 std::cout << ")";
1042 }
1043 std::cout << std::endl;
1044 }
1045
1046 const unsigned int nassembled = m_assembledTracks->size();
1047 for (unsigned int iassembled = 0; iassembled < nassembled; ++iassembled)
1048 {
1049 const Tpc_AssembledTrack* assembled = m_assembledTracks->get_track(iassembled);
1050 if (!assembled) { continue;
1051 }
1052
1053 TpcCrossingDecisionv1* decision = new TpcCrossingDecisionv1();
1054 std::vector<TpcCrossingCandidate> candidate_qa_records;
1055 auto add_decision_with_candidates = [&candidate_qa_records, decision, this]()
1056 {
1057 for (const TpcCrossingCandidate& candidate : candidate_qa_records) { decision->add_candidate(candidate);
1058 }
1059 m_decisions->add_decision(decision);
1060 };
1061 decision->set_assembled_track_id(assembled->get_track_id());
1062 decision->set_number_of_available_crossings(static_cast<unsigned short>(std::min<std::size_t>(available_crossings.size(), std::numeric_limits<unsigned short>::max())));
1063
1064 if (available_crossings.empty())
1065 {
1066 decision->set_status(TpcCrossingStatus::NoInttCrossings);
1067 add_decision_with_candidates();
1068 continue;
1069 }
1070
1071 TrkrDefs::hitsetkey min_hsk = 0;
1072 TrkrDefs::hitkey min_hk = 0;
1073 TrkrDefs::hitsetkey max_hsk = 0;
1074 TrkrDefs::hitkey max_hk = 0;
1075 if (!find_time_extrema(assembled, min_hsk, min_hk, max_hsk, max_hk))
1076 {
1077 for (const short crossing : available_crossings)
1078 {
1079 TpcCrossingCandidate qa;
1080 qa.crossing = crossing;
1081 qa.is_available_from_intt = intt_crossings.contains(crossing);
1082 qa.candidate_qa_bits = qa.is_available_from_intt ? FromInttCrossing : 0U;
1083 qa.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::NoValidCrossing);
1084 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::PassedTimeWindow);
1085 candidate_qa_records.push_back(qa);
1086 }
1087 decision->set_status(TpcCrossingStatus::NoValidCrossing);
1088 add_decision_with_candidates();
1089 continue;
1090 }
1091
1092 const unsigned int min_tbin = TpcDefs::getTBin(min_hk);
1093 const unsigned int max_tbin = TpcDefs::getTBin(max_hk);
1094 std::vector<short> allowed_crossings;
1095 for (const short crossing : available_crossings)
1096 {
1097 const double min_time0_ns = (static_cast<double>(min_tbin) - m_t0) * m_tpcAdcClock;
1098 const double max_time0_ns = (static_cast<double>(max_tbin) - m_t0) * m_tpcAdcClock;
1099 const double min_time_ns = min_time0_ns - static_cast<double>(crossing) * m_crossingPeriodNs;
1100 const double max_time_ns = max_time0_ns - static_cast<double>(crossing) * m_crossingPeriodNs;
1101 const bool passes_time_window = std::isfinite(min_time_ns) && std::isfinite(max_time_ns) &&
1102 min_time_ns > 0.0 && max_time_ns <= m_maxLookupTimeNs;
1103 if (passes_time_window)
1104 {
1105 allowed_crossings.push_back(crossing);
1106 }
1107 else
1108 {
1109 TpcCrossingCandidate qa;
1110 qa.crossing = crossing;
1111 qa.is_available_from_intt = intt_crossings.contains(crossing);
1112 qa.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::NoAllowedCrossing);
1113 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::PassedTimeWindow);
1114 qa.min_tbin_time_crossing0_ns = finite_float_or_nan(min_time0_ns);
1115 qa.max_tbin_time_crossing0_ns = finite_float_or_nan(max_time0_ns);
1116 qa.candidate_min_time_ns = finite_float_or_nan(min_time_ns);
1117 qa.candidate_max_time_ns = finite_float_or_nan(max_time_ns);
1118 qa.max_lookup_time_ns = finite_float_or_nan(m_maxLookupTimeNs);
1119 qa.min_time_margin_ns = finite_float_or_nan(min_time_ns);
1120 qa.max_time_margin_ns = finite_float_or_nan(m_maxLookupTimeNs - max_time_ns);
1121 qa.min_time_layer = TrkrDefs::getLayer(min_hsk);
1122 qa.min_time_side = TpcDefs::getSide(min_hsk);
1123 qa.min_time_pad = TpcDefs::getPad(min_hk);
1124 qa.min_time_tbin = min_tbin;
1125 qa.max_time_layer = TrkrDefs::getLayer(max_hsk);
1126 qa.max_time_side = TpcDefs::getSide(max_hsk);
1127 qa.max_time_pad = TpcDefs::getPad(max_hk);
1128 qa.max_time_tbin = max_tbin;
1129 qa.candidate_qa_bits = qa.is_available_from_intt ? FromInttCrossing : 0U;
1130 candidate_qa_records.push_back(qa);
1131 }
1132 }
1133 decision->set_number_of_allowed_crossings(static_cast<unsigned short>(std::min<std::size_t>(allowed_crossings.size(), std::numeric_limits<unsigned short>::max())));
1134
1135 if (allowed_crossings.empty())
1136 {
1137 decision->set_status(TpcCrossingStatus::NoAllowedCrossing);
1138 add_decision_with_candidates();
1139 continue;
1140 }
1141
1142 std::vector<Candidate> valid_candidates;
1143 unsigned char last_rejection = static_cast<unsigned char>(TpcCrossingStatus::NoValidCrossing);
1144 for (const short crossing : allowed_crossings)
1145 {
1146 Candidate candidate = test_candidate(assembled, crossing, min_hsk, min_hk, max_hsk, max_hk, vertices_by_crossing);
1147 decision->set_number_of_tested_crossings(decision->get_number_of_tested_crossings() + 1U);
1148 candidate.qa.is_available_from_intt = intt_crossings.contains(crossing);
1149 if (!candidate.qa.is_available_from_intt) { candidate.qa.candidate_qa_bits &= ~FromInttCrossing;
1150 }
1151 candidate_qa_records.push_back(candidate.qa);
1152 if (candidate.tpc_valid)
1153 {
1154 valid_candidates.push_back(candidate);
1155 }
1156 else if (candidate.rejection_status != 0U)
1157 {
1158 last_rejection = candidate.rejection_status;
1159 }
1160 }
1161 for (Candidate& candidate : valid_candidates)
1162 {
1163 if (candidate.has_silicon_vertex)
1164 {
1165 const double abs_delta = std::isfinite(candidate.delta_z) ? std::abs(candidate.delta_z) : std::numeric_limits<double>::max();
1166 candidate.confidence_tier = abs_delta <= m_maxVertexDz ? 0U : 1U;
1167 candidate.confidence_score = abs_delta;
1168 }
1169 else
1170 {
1171 const double abs_z0 = std::isfinite(candidate.tpc_z0) ? std::abs(candidate.tpc_z0 - m_collisionZ) : std::numeric_limits<double>::max();
1172 candidate.confidence_tier = 2U;
1173 candidate.confidence_score = abs_z0;
1174 if (abs_z0 > m_maxTier2BeamlineZ)
1175 {
1176 candidate.tpc_valid = false;
1177 candidate.rejection_status = static_cast<unsigned char>(TpcCrossingStatus::VertexIncompatible);
1178 last_rejection = candidate.rejection_status;
1179 }
1180 }
1181 }
1182
1183 std::vector<Candidate> ranked_candidates;
1184 for (const Candidate& candidate : valid_candidates)
1185 {
1186 if (!candidate.tpc_valid) { continue;
1187 }
1188 if (candidate.confidence_tier == InvalidConfidenceTier) { continue;
1189 }
1190 if (!std::isfinite(candidate.confidence_score)) { continue;
1191 }
1192 ranked_candidates.push_back(candidate);
1193 }
1194
1195 for (TpcCrossingCandidate& qa : candidate_qa_records)
1196 {
1197 const auto iq = std::find_if(valid_candidates.begin(), valid_candidates.end(), [&qa](const Candidate& candidate) { return candidate.crossing == qa.crossing; });
1198 if (iq == valid_candidates.end()) { continue;
1199 }
1200 qa.confidence_tier = iq->confidence_tier;
1201 qa.confidence_score = finite_float_or_nan(iq->confidence_score);
1202 qa.tpc_valid = iq->tpc_valid;
1203 if (!iq->tpc_valid && iq->rejection_status != 0U) { qa.rejection_status = iq->rejection_status;
1204 }
1205 }
1206
1207 decision->set_number_of_tpc_valid_crossings(static_cast<unsigned short>(std::min<std::size_t>(ranked_candidates.size(), std::numeric_limits<unsigned short>::max())));
1208 const auto n_vertex_compatible = std::count_if(ranked_candidates.begin(), ranked_candidates.end(), [](const Candidate& candidate) { return candidate.confidence_tier == 0U; });
1209 decision->set_number_of_vertex_compatible_crossings(static_cast<unsigned short>(std::min<std::size_t>(n_vertex_compatible, std::numeric_limits<unsigned short>::max())));
1210
1211 if (ranked_candidates.empty())
1212 {
1213 decision->set_status(last_rejection != 0U ? last_rejection : static_cast<unsigned char>(TpcCrossingStatus::NoValidCrossing));
1214 add_decision_with_candidates();
1215 continue;
1216 }
1217
1218 std::sort(ranked_candidates.begin(), ranked_candidates.end(), [](const Candidate& a, const Candidate& b) {
1219 if (a.confidence_tier != b.confidence_tier) { return a.confidence_tier < b.confidence_tier;
1220 }
1221 if (a.confidence_score != b.confidence_score) { return a.confidence_score < b.confidence_score;
1222 }
1223 return a.crossing < b.crossing;
1224 });
1225
1226 const Candidate& best_candidate = ranked_candidates.front();
1227 const double best_score = best_candidate.confidence_score;
1228 double second_score = std::numeric_limits<double>::infinity();
1229 for (std::size_t icandidate = 1; icandidate < ranked_candidates.size(); ++icandidate)
1230 {
1231 if (ranked_candidates[icandidate].confidence_tier != best_candidate.confidence_tier) { break;
1232 }
1233 second_score = ranked_candidates[icandidate].confidence_score;
1234 break;
1235 }
1236 const bool close_rival = std::isfinite(second_score) && second_score - best_score <= m_minBestSecondSeparation;
1237
1238 auto selected_status = TpcCrossingStatus::SelectedByContainment;
1239 if (best_candidate.confidence_tier == 0U)
1240 {
1241 selected_status = close_rival ? TpcCrossingStatus::SelectedByVertexAmbiguous : TpcCrossingStatus::SelectedByVertex;
1242 }
1243 else if (best_candidate.confidence_tier == 1U)
1244 {
1245 selected_status = TpcCrossingStatus::SelectedByVertexLoose;
1246 }
1247 else if (best_candidate.confidence_tier == 2U)
1248 {
1249 selected_status = close_rival ? TpcCrossingStatus::SelectedByContainmentAmbiguous : TpcCrossingStatus::SelectedByContainment;
1250 }
1251
1252 decision->set_best_abs_delta_z(finite_float_or_nan(best_score));
1253 decision->set_second_best_abs_delta_z(finite_float_or_nan(second_score));
1254 decision->set_selected_crossing(best_candidate.crossing);
1255 decision->set_tpc_z0(finite_float_or_nan(best_candidate.tpc_z0));
1256 decision->set_silicon_vertex_id(best_candidate.silicon_vertex_id);
1257 decision->set_silicon_vertex_z(finite_float_or_nan(best_candidate.silicon_vertex_z));
1258 decision->set_delta_z(finite_float_or_nan(best_candidate.delta_z));
1259 decision->set_selected_tier(best_candidate.confidence_tier);
1260 decision->set_selected_score(finite_float_or_nan(best_candidate.confidence_score));
1261 decision->set_status(selected_status);
1262
1263 std::vector<short> by_vertex;
1264 by_vertex.reserve(valid_candidates.size());
1265 std::vector<short> by_collision;
1266 by_collision.reserve(valid_candidates.size());
1267 for (const Candidate& candidate : valid_candidates)
1268 {
1269 by_vertex.push_back(candidate.crossing);
1270 by_collision.push_back(candidate.crossing);
1271 }
1272 std::sort(by_vertex.begin(), by_vertex.end(), [&valid_candidates](short a, short b) {
1273 const auto ia = std::find_if(valid_candidates.begin(), valid_candidates.end(), [a](const Candidate& c) { return c.crossing == a; });
1274 const auto ib = std::find_if(valid_candidates.begin(), valid_candidates.end(), [b](const Candidate& c) { return c.crossing == b; });
1275 const double da = std::isfinite(ia->delta_z) ? std::abs(ia->delta_z) : std::numeric_limits<double>::max();
1276 const double db = std::isfinite(ib->delta_z) ? std::abs(ib->delta_z) : std::numeric_limits<double>::max();
1277 if (da != db) { return da < db;
1278 }
1279 return a < b;
1280 });
1281 std::sort(by_collision.begin(), by_collision.end(), [this, &valid_candidates](short a, short b) {
1282 const auto ia = std::find_if(valid_candidates.begin(), valid_candidates.end(), [a](const Candidate& c) { return c.crossing == a; });
1283 const auto ib = std::find_if(valid_candidates.begin(), valid_candidates.end(), [b](const Candidate& c) { return c.crossing == b; });
1284 const double da = std::isfinite(ia->tpc_z0) ? std::abs(ia->tpc_z0 - m_collisionZ) : std::numeric_limits<double>::max();
1285 const double db = std::isfinite(ib->tpc_z0) ? std::abs(ib->tpc_z0 - m_collisionZ) : std::numeric_limits<double>::max();
1286 if (da != db) { return da < db;
1287 }
1288 return a < b;
1289 });
1290
1291 for (TpcCrossingCandidate& qa : candidate_qa_records)
1292 {
1293 const auto iv = std::find(by_vertex.begin(), by_vertex.end(), qa.crossing);
1294 if (iv != by_vertex.end()) { qa.candidate_rank_by_abs_delta_z = std::distance(by_vertex.begin(), iv);
1295 }
1296 const auto ic = std::find(by_collision.begin(), by_collision.end(), qa.crossing);
1297 if (ic != by_collision.end()) { qa.candidate_rank_by_abs_collision_z = std::distance(by_collision.begin(), ic);
1298 }
1299 const auto iq = std::find_if(valid_candidates.begin(), valid_candidates.end(), [&qa](const Candidate& candidate) { return candidate.crossing == qa.crossing; });
1300 if (iq != valid_candidates.end())
1301 {
1302 qa.confidence_tier = iq->confidence_tier;
1303 qa.confidence_score = finite_float_or_nan(iq->confidence_score);
1304 qa.tpc_valid = iq->tpc_valid;
1305 if (!iq->tpc_valid && iq->rejection_status != 0U) { qa.rejection_status = iq->rejection_status;
1306 }
1307 }
1308 qa.is_selected = qa.crossing == decision->get_selected_crossing();
1309 if (qa.is_selected)
1310 {
1311 qa.candidate_qa_bits |= IsSelected;
1312 qa.first_failed_stage = static_cast<unsigned char>(TpcCrossingCandidateStage::Selected);
1313 }
1314 if (qa.candidate_rank_by_abs_delta_z == 0) { qa.candidate_qa_bits |= IsBestVertexCandidate;
1315 }
1316 }
1317
1318 add_decision_with_candidates();
1319 }
1320
1321 if (Verbosity() > 0)
1322 {
1323 std::cout << Name() << "::process_event - event " << m_event
1324 << " assembled_tracks=" << nassembled
1325 << " decisions=" << m_decisions->size()
1326 << " available_crossings=" << available_crossings.size()
1327 << " vertex_map=" << has_vertex_map
1328 << std::endl;
1329 }
1330
1331 ++m_event;
1332 return Fun4AllReturnCodes::EVENT_OK;
1333 }