Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-08-31 08:19:54

0001 #include "nnscalemap.h"
0002 
0003 #include <ffamodules/CDBInterface.h>
0004 
0005 #include <fun4all/Fun4AllReturnCodes.h>
0006 
0007 #include <phool/PHCompositeNode.h>
0008 #include <phool/PHIODataNode.h>
0009 #include <phool/PHNode.h>
0010 #include <phool/PHNodeIterator.h>
0011 #include <phool/PHObject.h>
0012 #include <phool/getClass.h>
0013 #include <phool/phool.h>
0014 
0015 #include <trackbase_historic/SvtxTrack.h>
0016 #include <trackbase_historic/SvtxTrackMap.h>
0017 #include <trackbase_historic/SvtxTrackMap_v2.h>
0018 #include <trackbase_historic/SvtxTrack_v4.h>
0019 
0020 #include <algorithm>
0021 #include <cmath>
0022 #include <fstream>
0023 #include <iostream>
0024 #include <map>
0025 #include <sstream>
0026 
0027 namespace
0028 {
0029 // lower bracketing index i such that grid[i] <= x <= grid[i+1] (x already clamped to [grid.front(), grid.back()]),
0030 // plus the fractional distance to the next grid point.
0031 size_t bracketIndex(const std::vector<double>& grid, double x, double& frac)
0032 {
0033   auto it = std::upper_bound(grid.begin(), grid.end(), x);
0034   size_t i = (it == grid.begin()) ? 0 : std::distance(grid.begin(), it) - 1;
0035   i = std::min(i, grid.size() - 2);
0036   const double lo = grid[i];
0037   const double hi = grid[i + 1];
0038   frac = (hi > lo) ? (x - lo) / (hi - lo) : 0.0;
0039   return i;
0040 }
0041 
0042 std::vector<double> uniqueSorted(std::vector<double> v)
0043 {
0044   std::sort(v.begin(), v.end());
0045   std::vector<double> out;
0046   for (double x : v)
0047   {
0048     if (out.empty() || std::abs(x - out.back()) > 1e-6 * std::max(1.0, std::abs(x)))
0049     {
0050       out.push_back(x);
0051     }
0052   }
0053   return out;
0054 }
0055 
0056 // returns -1 if x is not found on grid within tolerance
0057 long findIndex(const std::vector<double>& grid, double x)
0058 {
0059   auto it = std::lower_bound(grid.begin(), grid.end(), x);
0060   if (it != grid.begin() && (it == grid.end() || std::abs(*it - x) > std::abs(*std::prev(it) - x)))
0061   {
0062     --it;
0063   }
0064   if (it == grid.end())
0065   {
0066     return -1;
0067   }
0068   const long idx = std::distance(grid.begin(), it);
0069   if (std::abs(grid[idx] - x) > 1e-6 * std::max(1.0, std::abs(x)))
0070   {
0071     return -1;
0072   }
0073   return idx;
0074 }
0075 
0076 std::string trim(const std::string& s)
0077 {
0078   const size_t a = s.find_first_not_of(" \t\r\n");
0079   if (a == std::string::npos)
0080   {
0081     return "";
0082   }
0083   const size_t b = s.find_last_not_of(" \t\r\n");
0084   return s.substr(a, b - a + 1);
0085 }
0086 
0087 std::vector<std::string> splitCsvLine(const std::string& line)
0088 {
0089   std::vector<std::string> out;
0090   std::stringstream ss(line);
0091   std::string tok;
0092   while (std::getline(ss, tok, ','))
0093   {
0094     out.push_back(trim(tok));
0095   }
0096   return out;
0097 }
0098 }  // namespace
0099 
0100 NNScaleMap::NNScaleMap(const std::string& name)
0101   : SubsysReco(name)
0102 {
0103 }
0104 
0105 int NNScaleMap::Init(PHCompositeNode* /*topNode*/)
0106 {
0107   if (!m_useCDB && m_lookupFile.empty())
0108   {
0109     std::cout << PHWHERE << " no kappa lookup source configured -- call setKappaLookupFile(path)"
0110                << " and/or setUseCDB(true)." << std::endl;
0111     return Fun4AllReturnCodes::ABORTRUN;
0112   }
0113   return Fun4AllReturnCodes::EVENT_OK;
0114 }
0115 
0116 int NNScaleMap::InitRun(PHCompositeNode* topNode)
0117 {
0118   const std::string path = ResolveLookupFile();
0119   if (path.empty())
0120   {
0121     std::cout << PHWHERE << " could not resolve a kappa lookup file for this run"
0122                << (m_useCDB ? (" (CDB domain '" + m_cdbDomainName + "' and no local fallback set)") : "")
0123                << std::endl;
0124     return Fun4AllReturnCodes::ABORTRUN;
0125   }
0126 
0127   if (path != m_loadedFile)
0128   {
0129     if (!LoadLookupTable(path))
0130     {
0131       std::cout << PHWHERE << " failed to load kappa lookup table from '" << path << "'" << std::endl;
0132       return Fun4AllReturnCodes::ABORTRUN;
0133     }
0134     m_loadedFile = path;
0135   }
0136   else if (Verbosity() > 0)
0137   {
0138     std::cout << "NNScaleMap: '" << path << "' already loaded, reusing it for this run" << std::endl;
0139   }
0140 
0141   const int ret = CreateNodes(topNode);
0142   if (ret != Fun4AllReturnCodes::EVENT_OK)
0143   {
0144     return ret;
0145   }
0146   return Fun4AllReturnCodes::EVENT_OK;
0147 }
0148 
0149 int NNScaleMap::process_event(PHCompositeNode* topNode)
0150 {
0151   const int ret = GetNodes(topNode);
0152   if (ret != Fun4AllReturnCodes::EVENT_OK)
0153   {
0154     return ret;
0155   }
0156 
0157   m_outputTrackMap->Reset();
0158 
0159   for (const auto& entry : *m_inputTrackMap)
0160   {
0161     const SvtxTrack* track = entry.second;
0162     if (!track)
0163     {
0164       continue;
0165     }
0166     ++m_nTracksSeen;
0167 
0168     const float px = track->get_px();
0169     const float py = track->get_py();
0170     const float pz = track->get_pz();
0171     const float pt = std::sqrt(px * px + py * py);
0172 
0173     SvtxTrack_v4 corrected(*track);
0174 
0175     if (pt <= 0.0F)
0176     {
0177       ++m_nTracksSkippedZeroPt;
0178     }
0179     else
0180     {
0181       const float eta = std::asinh(pz / pt);
0182       const float phi = std::atan2(py, px);
0183       const float kappa = GetKappa(track->get_charge(), pt, eta, phi);
0184 
0185       corrected.set_px(kappa * px);
0186       corrected.set_py(kappa * py);
0187       corrected.set_pz(kappa * pz);
0188 
0189       if (m_scaleCovariance)
0190       {
0191         // momentum components live at covariance indices 3,4,5 (x,y,z,px,py,pz);
0192         // scaling px,py,pz by kappa scales those rows/columns of the covariance
0193         // by kappa for each momentum index involved (kappa^2 if both are).
0194         for (int i = 0; i < 6; ++i)
0195         {
0196           for (int j = i; j < 6; ++j)
0197           {
0198             const bool iMom = (i >= 3);
0199             const bool jMom = (j >= 3);
0200             if (!iMom && !jMom)
0201             {
0202               continue;
0203             }
0204             const float scale = (iMom && jMom) ? kappa * kappa : kappa;
0205             corrected.set_error(i, j, scale * track->get_error(i, j));
0206           }
0207         }
0208       }
0209 
0210       ++m_nTracksCorrected;
0211     }
0212 
0213     m_outputTrackMap->insertWithKey(&corrected, track->get_id());
0214   }
0215 
0216   ++m_nEvents;
0217   return Fun4AllReturnCodes::EVENT_OK;
0218 }
0219 
0220 int NNScaleMap::End(PHCompositeNode* /*topNode*/)
0221 {
0222   std::cout << "NNScaleMap::End - processed " << m_nEvents << " events, "
0223              << m_nTracksSeen << " tracks seen, " << m_nTracksCorrected << " corrected, "
0224              << m_nTracksSkippedZeroPt << " skipped (pT<=0)" << std::endl;
0225   return Fun4AllReturnCodes::EVENT_OK;
0226 }
0227 
0228 void NNScaleMap::Print(const std::string& what) const
0229 {
0230   std::cout << "NNScaleMap::Print - " << what
0231              << ", useCDB=" << m_useCDB
0232              << ", cdbDomain=" << m_cdbDomainName
0233              << ", lookupFile=" << m_lookupFile
0234              << ", loaded=" << m_loadedFile
0235              << ", input=" << m_inputTrackMapName
0236              << ", output=" << m_outputTrackMapName
0237              << ", grid=" << m_ptEdges.size() << "(pT) x" << m_etaEdges.size()
0238              << "(eta) x" << m_phiEdges.size() << "(phi)"
0239              << std::endl;
0240 }
0241 
0242 std::string NNScaleMap::ResolveLookupFile() const
0243 {
0244   if (m_useCDB)
0245   {
0246     // CDBInterface::getUrl resolves the payload for this run's CDB_GLOBALTAG/TIMESTAMP
0247     // (both must already be set on recoConsts by the macro, same as any other CDB
0248     // payload). If the domain has no entry (and no "<domain>_default" entry) for this
0249     // run, it falls back to returning m_lookupFile verbatim -- so a local override
0250     // still works even with CDB enabled.
0251     return CDBInterface::instance()->getUrl(m_cdbDomainName, m_lookupFile);
0252   }
0253   return m_lookupFile;
0254 }
0255 
0256 int NNScaleMap::CreateNodes(PHCompositeNode* topNode)
0257 {
0258   PHNodeIterator iter(topNode);
0259 
0260   auto* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0261   if (!dstNode)
0262   {
0263     std::cout << PHWHERE << " DST node missing, doing nothing." << std::endl;
0264     return Fun4AllReturnCodes::ABORTRUN;
0265   }
0266 
0267   PHNodeIterator iter_dst(dstNode);
0268   auto* svtxNode = dynamic_cast<PHCompositeNode*>(iter_dst.findFirst("PHCompositeNode", "SVTX"));
0269   if (!svtxNode)
0270   {
0271     svtxNode = new PHCompositeNode("SVTX");
0272     dstNode->addNode(svtxNode);
0273   }
0274 
0275   auto* existing = findNode::getClass<SvtxTrackMap>(topNode, m_outputTrackMapName);
0276   if (existing)
0277   {
0278     if (Verbosity() > 0)
0279     {
0280       std::cout << PHWHERE << " node " << m_outputTrackMapName << " already exists, reusing it." << std::endl;
0281     }
0282     m_outputTrackMap = existing;
0283     return Fun4AllReturnCodes::EVENT_OK;
0284   }
0285 
0286   m_outputTrackMap = new SvtxTrackMap_v2();
0287   auto* node = new PHIODataNode<PHObject>(m_outputTrackMap, m_outputTrackMapName, "PHObject");
0288   svtxNode->addNode(node);
0289 
0290   if (Verbosity() > 0)
0291   {
0292     std::cout << "NNScaleMap: added " << m_outputTrackMapName << " node under DST/SVTX" << std::endl;
0293   }
0294 
0295   return Fun4AllReturnCodes::EVENT_OK;
0296 }
0297 
0298 int NNScaleMap::GetNodes(PHCompositeNode* topNode)
0299 {
0300   m_inputTrackMap = findNode::getClass<SvtxTrackMap>(topNode, m_inputTrackMapName);
0301   if (!m_inputTrackMap)
0302   {
0303     std::cout << PHWHERE << " missing required node " << m_inputTrackMapName << std::endl;
0304     return Fun4AllReturnCodes::ABORTEVENT;
0305   }
0306 
0307   if (!m_outputTrackMap)
0308   {
0309     m_outputTrackMap = findNode::getClass<SvtxTrackMap>(topNode, m_outputTrackMapName);
0310   }
0311   if (!m_outputTrackMap)
0312   {
0313     std::cout << PHWHERE << " missing output node " << m_outputTrackMapName << std::endl;
0314     return Fun4AllReturnCodes::ABORTEVENT;
0315   }
0316 
0317   return Fun4AllReturnCodes::EVENT_OK;
0318 }
0319 
0320 bool NNScaleMap::LoadLookupTable(const std::string& path)
0321 {
0322   std::ifstream fin(path);
0323   if (!fin.is_open())
0324   {
0325     return false;
0326   }
0327 
0328   // The CSV may start with '#'-prefixed convention/provenance comment lines,
0329   // then a header row naming its columns (e.g.
0330   // "q,pT,curv,eta,phi,kappa,kappa_curv,eps,delta" for a curvature-space
0331   // training run, or plain "q,pT,eta,phi,kappa"), then data rows. Only
0332   // q,pT,eta,phi,kappa are needed for deployment; look them up by name
0333   // instead of assuming a fixed column count/order so a future column
0334   // reshuffle or addition doesn't silently misread the grid.
0335   std::map<std::string, int> colIndex;
0336   std::string line;
0337   bool haveHeader = false;
0338   while (!haveHeader && std::getline(fin, line))
0339   {
0340     if (line.empty() || line[0] == '#')
0341     {
0342       continue;
0343     }
0344     const std::vector<std::string> cols = splitCsvLine(line);
0345     for (size_t i = 0; i < cols.size(); ++i)
0346     {
0347       colIndex[cols[i]] = static_cast<int>(i);
0348     }
0349     haveHeader = true;
0350   }
0351 
0352   if (!haveHeader)
0353   {
0354     std::cout << PHWHERE << " kappa lookup file '" << path << "' has no header row" << std::endl;
0355     return false;
0356   }
0357 
0358   const char* required[] = {"q", "pT", "eta", "phi", "kappa"};
0359   for (const char* name : required)
0360   {
0361     if (!colIndex.contains(name))
0362     {
0363       std::cout << PHWHERE << " kappa lookup file '" << path
0364                  << "' header is missing required column '" << name << "'" << std::endl;
0365       return false;
0366     }
0367   }
0368   const int iQ = colIndex["q"];
0369   const int iPt = colIndex["pT"];
0370   const int iEta = colIndex["eta"];
0371   const int iPhi = colIndex["phi"];
0372   const int iKappa = colIndex["kappa"];
0373   const int nColsNeeded = 1 + std::max({iQ, iPt, iEta, iPhi, iKappa});
0374 
0375   std::vector<double> qs;
0376   std::vector<double> pts;
0377   std::vector<double> etas;
0378   std::vector<double> phis;
0379   std::vector<float> kappas;
0380 
0381   while (std::getline(fin, line))
0382   {
0383     if (line.empty() || line[0] == '#')
0384     {
0385       continue;
0386     }
0387 
0388     const std::vector<std::string> cols = splitCsvLine(line);
0389     if (static_cast<int>(cols.size()) < nColsNeeded)
0390     {
0391       continue;
0392     }
0393 
0394     try
0395     {
0396       const double q = std::stod(cols[iQ]);
0397       const double pt = std::stod(cols[iPt]);
0398       const double eta = std::stod(cols[iEta]);
0399       const double phi = std::stod(cols[iPhi]);
0400       const double kappa = std::stod(cols[iKappa]);
0401       qs.push_back(q);
0402       pts.push_back(pt);
0403       etas.push_back(eta);
0404       phis.push_back(phi);
0405       kappas.push_back(static_cast<float>(kappa));
0406     }
0407     catch (const std::exception&)
0408     {
0409       continue;  // malformed data line
0410     }
0411   }
0412 
0413   if (qs.empty())
0414   {
0415     std::cout << PHWHERE << " kappa lookup file '" << path << "' contained no data rows" << std::endl;
0416     return false;
0417   }
0418 
0419   m_ptEdges = uniqueSorted(pts);
0420   m_etaEdges = uniqueSorted(etas);
0421   m_phiEdges = uniqueSorted(phis);
0422 
0423   const size_t nPt = m_ptEdges.size();
0424   const size_t nEta = m_etaEdges.size();
0425   const size_t nPhi = m_phiEdges.size();
0426 
0427   if (nPt < 2 || nEta < 2 || nPhi < 2)
0428   {
0429     std::cout << PHWHERE << " kappa lookup grid too small to interpolate (nPt=" << nPt
0430                << " nEta=" << nEta << " nPhi=" << nPhi << ")" << std::endl;
0431     return false;
0432   }
0433 
0434   m_kappaGrid[0].assign(nPt * nEta * nPhi, 1.0F);
0435   m_kappaGrid[1].assign(nPt * nEta * nPhi, 1.0F);
0436 
0437   size_t nBad = 0;
0438   for (size_t r = 0; r < qs.size(); ++r)
0439   {
0440     const int qIdx = (qs[r] > 0) ? 1 : 0;
0441     const long ip = findIndex(m_ptEdges, pts[r]);
0442     const long ie = findIndex(m_etaEdges, etas[r]);
0443     const long iph = findIndex(m_phiEdges, phis[r]);
0444     if (ip < 0 || ie < 0 || iph < 0)
0445     {
0446       ++nBad;
0447       continue;
0448     }
0449     const size_t idx = (static_cast<size_t>(ip) * nEta + static_cast<size_t>(ie)) * nPhi + static_cast<size_t>(iph);
0450     m_kappaGrid[qIdx][idx] = kappas[r];
0451   }
0452 
0453   if (nBad > 0)
0454   {
0455     std::cout << PHWHERE << " warning: " << nBad << " rows in '" << path
0456                << "' could not be placed on the (pT,eta,phi) grid" << std::endl;
0457   }
0458 
0459   std::cout << "NNScaleMap: loaded kappa lookup grid " << nPt << "(pT) x " << nEta
0460              << "(eta) x " << nPhi << "(phi), " << qs.size() << " rows from " << path << std::endl;
0461 
0462   return true;
0463 }
0464 
0465 float NNScaleMap::GetKappa(int charge, float pt, float eta, float phi) const
0466 {
0467   if (m_ptEdges.empty())
0468   {
0469     return 1.0F;
0470   }
0471 
0472   const int qIdx = (charge > 0) ? 1 : 0;
0473 
0474   const double ptC = std::clamp<double>(pt, m_ptEdges.front(), m_ptEdges.back());
0475   const double etaC = std::clamp<double>(eta, m_etaEdges.front(), m_etaEdges.back());
0476   const double phiC = std::clamp<double>(phi, m_phiEdges.front(), m_phiEdges.back());
0477 
0478   double fpt = 0.0;
0479   double feta = 0.0;
0480   double fphi = 0.0;
0481   const size_t ipt = bracketIndex(m_ptEdges, ptC, fpt);
0482   const size_t ieta = bracketIndex(m_etaEdges, etaC, feta);
0483   const size_t iphi = bracketIndex(m_phiEdges, phiC, fphi);
0484 
0485   const size_t nEta = m_etaEdges.size();
0486   const size_t nPhi = m_phiEdges.size();
0487   const std::vector<float>& grid = m_kappaGrid[qIdx];
0488 
0489   auto at = [&](size_t ip, size_t ie, size_t iph) -> float
0490   {
0491     return grid[(ip * nEta + ie) * nPhi + iph];
0492   };
0493 
0494   const float c000 = at(ipt, ieta, iphi);
0495   const float c001 = at(ipt, ieta, iphi + 1);
0496   const float c010 = at(ipt, ieta + 1, iphi);
0497   const float c011 = at(ipt, ieta + 1, iphi + 1);
0498   const float c100 = at(ipt + 1, ieta, iphi);
0499   const float c101 = at(ipt + 1, ieta, iphi + 1);
0500   const float c110 = at(ipt + 1, ieta + 1, iphi);
0501   const float c111 = at(ipt + 1, ieta + 1, iphi + 1);
0502 
0503   const float c00 = static_cast<float>(c000 * (1.0 - fphi) + c001 * fphi);
0504   const float c01 = static_cast<float>(c010 * (1.0 - fphi) + c011 * fphi);
0505   const float c10 = static_cast<float>(c100 * (1.0 - fphi) + c101 * fphi);
0506   const float c11 = static_cast<float>(c110 * (1.0 - fphi) + c111 * fphi);
0507 
0508   const float c0 = static_cast<float>(c00 * (1.0 - feta) + c01 * feta);
0509   const float c1 = static_cast<float>(c10 * (1.0 - feta) + c11 * feta);
0510 
0511   return static_cast<float>(c0 * (1.0 - fpt) + c1 * fpt);
0512 }