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
0030
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
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 }
0099
0100 NNScaleMap::NNScaleMap(const std::string& name)
0101 : SubsysReco(name)
0102 {
0103 }
0104
0105 int NNScaleMap::Init(PHCompositeNode* )
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
0192
0193
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* )
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
0247
0248
0249
0250
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
0329
0330
0331
0332
0333
0334
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;
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 }