Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2026-04-06 08:12:10

0001 #include "QVecCalib.h"
0002 #include "EventPlaneData.h"
0003 
0004 // ====================================================================
0005 // sPHENIX Includes
0006 // ====================================================================
0007 #include <calobase/TowerInfoDefs.h>
0008 
0009 // -- Fun4All
0010 #include <fun4all/Fun4AllReturnCodes.h>
0011 #include <fun4all/Fun4AllServer.h>
0012 
0013 // -- Nodes
0014 #include <phool/PHCompositeNode.h>
0015 #include <phool/getClass.h>
0016 
0017 // -- sEPD
0018 #include <epd/EpdGeom.h>
0019 
0020 // -- Run
0021 #include <ffaobjects/RunHeader.h>
0022 
0023 // -- CDBTTree
0024 #include <cdbobjects/CDBTTree.h>
0025 
0026 #include <TFile.h>
0027 #include <TH1.h>
0028 #include <TH2.h>
0029 #include <TProfile.h>
0030 
0031 
0032 // ====================================================================
0033 // Standard C++ Includes
0034 // ====================================================================
0035 #include <format>
0036 #include <numbers>
0037 #include <filesystem>
0038 
0039 //____________________________________________________________________________..
0040 QVecCalib::QVecCalib(const std::string &name):
0041  SubsysReco(name)
0042 {
0043   //  std::cout << "QVecCalib::QVecCalib(const std::string &name) Calling ctor" << std::endl;
0044 }
0045 
0046 //____________________________________________________________________________..
0047 int QVecCalib::Init([[maybe_unused]] PHCompositeNode *topNode)
0048 {
0049   if (Verbosity() > 1)
0050   {
0051     std::cout << "QVecCalib::Init(PHCompositeNode *topNode) Initializing" << std::endl;
0052 
0053     Fun4AllServer *se = Fun4AllServer::instance();
0054     se->Print("NODETREE");
0055   }
0056 
0057   int ret = process_QA_hist();
0058   if (ret)
0059   {
0060     return ret;
0061   }
0062 
0063   init_hists();
0064 
0065   if (m_pass == Pass::ApplyRecentering || m_pass == Pass::ApplyFlattening)
0066   {
0067     ret = load_correction_data();
0068     if (ret)
0069     {
0070       return ret;
0071     }
0072   }
0073 
0074   prepare_hists();
0075 
0076   return Fun4AllReturnCodes::EVENT_OK;
0077 }
0078 
0079 void QVecCalib::prepare_hists()
0080 {
0081   if (m_pass == Pass::ComputeRecentering)
0082   {
0083     prepare_average_hists();
0084   }
0085   else if (m_pass == Pass::ApplyRecentering)
0086   {
0087     prepare_recenter_hists();
0088   }
0089   else if (m_pass == Pass::ApplyFlattening)
0090   {
0091     prepare_flattening_hists();
0092   }
0093 }
0094 
0095 int QVecCalib::process_QA_hist()
0096 {
0097   TH1::AddDirectory(kFALSE);
0098   auto* file = TFile::Open(m_input_hist.c_str());
0099 
0100   // Check if the file was opened successfully.
0101   if (!file || file->IsZombie())
0102   {
0103     std::cout << PHWHERE << "Error! Cannot not open file: " << m_input_hist << std::endl;
0104     return Fun4AllReturnCodes::ABORTRUN;
0105   }
0106 
0107   // Get sEPD Total Charge Bounds as function of centrality
0108   int ret = process_sEPD_event_thresholds(file);
0109   if (ret)
0110   {
0111     return ret;
0112   }
0113 
0114   // Get List of Bad Channels
0115   ret = process_bad_channels(file);
0116   if (ret)
0117   {
0118     return ret;
0119   }
0120 
0121   // cleanup
0122   file->Close();
0123   delete file;
0124 
0125   return Fun4AllReturnCodes::EVENT_OK;
0126 }
0127 
0128 int QVecCalib::process_sEPD_event_thresholds(TFile* file)
0129 {
0130   Fun4AllServer *se = Fun4AllServer::instance();
0131 
0132   std::string sepd_totalcharge_centrality = "h2SEPD_totalcharge_centrality";
0133 
0134   TH2 *hist {nullptr};
0135   file->GetObject(sepd_totalcharge_centrality.c_str(),hist);
0136 
0137   // Check if the hist is stored in the file
0138   if (hist == nullptr)
0139   {
0140     std::cout << PHWHERE << "Error! Cannot find hist: " << sepd_totalcharge_centrality << std::endl;
0141     return Fun4AllReturnCodes::ABORTRUN;
0142   }
0143 
0144   h2SEPD_Charge = static_cast<TH2*>(hist->Clone("h2SEPD_Charge"));
0145   h2SEPD_Chargev2 = static_cast<TH2*>(hist->Clone("h2SEPD_Chargev2"));
0146 
0147   se->registerHisto(h2SEPD_Charge);
0148   se->registerHisto(h2SEPD_Chargev2);
0149 
0150   auto* h2SEPD_Charge_py = h2SEPD_Charge->ProfileY("h2SEPD_Charge_py", 1, -1, "s");
0151 
0152   int binsx = h2SEPD_Charge->GetNbinsX();
0153   int binsy = h2SEPD_Charge->GetNbinsY();
0154   double ymin = h2SEPD_Charge->GetYaxis()->GetXmin();
0155   double ymax = h2SEPD_Charge->GetYaxis()->GetXmax();
0156 
0157   hSEPD_Charge_Min = new TProfile("hSEPD_Charge_Min", "; Centrality [%]; sEPD Total Charge", binsy, ymin, ymax);
0158   hSEPD_Charge_Max = new TProfile("hSEPD_Charge_Max", "; Centrality [%]; sEPD Total Charge", binsy, ymin, ymax);
0159 
0160   se->registerHisto(hSEPD_Charge_Min);
0161   se->registerHisto(hSEPD_Charge_Max);
0162 
0163   for (int y = 1; y <= binsy; ++y)
0164   {
0165     double cent = h2SEPD_Charge_py->GetBinCenter(y);
0166     double mean = h2SEPD_Charge_py->GetBinContent(y);
0167     double sigma = h2SEPD_Charge_py->GetBinError(y);
0168 
0169     if (sigma == 0)
0170     {
0171       continue;
0172     }
0173 
0174     double charge_low  = mean - m_sEPD_sigma_threshold * sigma;
0175     double charge_high = mean + m_sEPD_sigma_threshold * sigma;
0176 
0177     hSEPD_Charge_Min->Fill(cent, charge_low);
0178     hSEPD_Charge_Max->Fill(cent, charge_high);
0179 
0180     for (int x = 1; x <= binsx; ++x)
0181     {
0182       double charge = h2SEPD_Charge->GetXaxis()->GetBinCenter(x);
0183       double zscore = (charge - mean) / sigma;
0184 
0185       if (std::abs(zscore) > m_sEPD_sigma_threshold)
0186       {
0187         h2SEPD_Chargev2->SetBinContent(x, y, 0);
0188         h2SEPD_Chargev2->SetBinError(x, y, 0);
0189       }
0190     }
0191   }
0192 
0193   return Fun4AllReturnCodes::EVENT_OK;
0194 }
0195 
0196 int QVecCalib::process_bad_channels(TFile* file)
0197 {
0198   Fun4AllServer *se = Fun4AllServer::instance();
0199 
0200   std::string sepd_charge_hist = "hSEPD_Charge";
0201 
0202   TProfile *hSEPD_Charge{nullptr};
0203   file->GetObject(sepd_charge_hist.c_str(), hSEPD_Charge);
0204 
0205   // Check if the hist is stored in the file
0206   if (hSEPD_Charge == nullptr)
0207   {
0208     std::cout << PHWHERE << "Error! Cannot find hist: " << sepd_charge_hist << ", in file: " << file->GetName() << std::endl;
0209     return Fun4AllReturnCodes::ABORTRUN;
0210   }
0211 
0212   int rbins = 16;
0213   int bins_charge = 40;
0214 
0215   h2SEPD_South_Charge_rbin = new TH2F("h2SEPD_South_Charge_rbin",
0216                                       "sEPD South; r_{bin}; Avg Charge",
0217                                       rbins, -0.5, rbins - 0.5,
0218                                       bins_charge, 0, bins_charge);
0219 
0220   h2SEPD_North_Charge_rbin = new TH2F("h2SEPD_North_Charge_rbin",
0221                                       "sEPD North; r_{bin}; Avg Charge",
0222                                       rbins, -0.5, rbins - 0.5,
0223                                       bins_charge, 0, bins_charge);
0224 
0225   h2SEPD_South_Charge_rbinv2 = new TH2F("h2SEPD_South_Charge_rbinv2",
0226                                         "sEPD South; r_{bin}; Avg Charge",
0227                                         rbins, -0.5, rbins - 0.5,
0228                                         bins_charge, 0, bins_charge);
0229 
0230   h2SEPD_North_Charge_rbinv2 = new TH2F("h2SEPD_North_Charge_rbinv2",
0231                                         "sEPD North; r_{bin}; Avg Charge",
0232                                         rbins, -0.5, rbins - 0.5,
0233                                         bins_charge, 0, bins_charge);
0234 
0235   hSEPD_Bad_Channels = new TProfile("h_sEPD_Bad_Channels", "sEPD Bad Channels; Channel; Status", QVecShared::SEPD_CHANNELS, -0.5, QVecShared::SEPD_CHANNELS-0.5);
0236 
0237   se->registerHisto(h2SEPD_South_Charge_rbin);
0238   se->registerHisto(h2SEPD_North_Charge_rbin);
0239   se->registerHisto(h2SEPD_South_Charge_rbinv2);
0240   se->registerHisto(h2SEPD_North_Charge_rbinv2);
0241   se->registerHisto(hSEPD_Bad_Channels);
0242 
0243   for (int channel = 0; channel < QVecShared::SEPD_CHANNELS; ++channel)
0244   {
0245     unsigned int key = TowerInfoDefs::encode_epd(channel);
0246     int rbin = TowerInfoDefs::get_epd_rbin(key);
0247     unsigned int arm = TowerInfoDefs::get_epd_arm(key);
0248 
0249     double avg_charge = hSEPD_Charge->GetBinContent(channel + 1);
0250 
0251     auto* h2 = (arm == 0) ? h2SEPD_South_Charge_rbin : h2SEPD_North_Charge_rbin;
0252 
0253     h2->Fill(rbin, avg_charge);
0254   }
0255 
0256   auto* hSpx = h2SEPD_South_Charge_rbin->ProfileX("hSpx", 2, -1, "s");
0257   auto* hNpx = h2SEPD_North_Charge_rbin->ProfileX("hNpx", 2, -1, "s");
0258 
0259   int ctr_dead = 0;
0260   int ctr_hot = 0;
0261   int ctr_cold = 0;
0262 
0263   for (int channel = 0; channel < QVecShared::SEPD_CHANNELS; ++channel)
0264   {
0265     unsigned int key = TowerInfoDefs::encode_epd(channel);
0266     int rbin = TowerInfoDefs::get_epd_rbin(key);
0267     unsigned int arm = TowerInfoDefs::get_epd_arm(key);
0268 
0269     auto* h2 = (arm == 0) ? h2SEPD_South_Charge_rbinv2 : h2SEPD_North_Charge_rbinv2;
0270     auto* hprof = (arm == 0) ? hSpx : hNpx;
0271 
0272     double charge = hSEPD_Charge->GetBinContent(channel + 1);
0273     double mean_charge = hprof->GetBinContent(rbin + 1);
0274     double sigma = hprof->GetBinError(rbin + 1);
0275     double zscore = 0.0;
0276 
0277     if (sigma > 0)
0278     {
0279       zscore = (charge - mean_charge) / sigma;
0280     }
0281 
0282     if (charge < m_sEPD_min_avg_charge_threshold || std::abs(zscore) > m_sEPD_sigma_threshold)
0283     {
0284       m_bad_channels.insert(channel);
0285 
0286       std::string type;
0287       QVecShared::ChannelStatus status_fill;
0288 
0289       // dead channel
0290       if (charge == 0)
0291       {
0292         type = "Dead";
0293         status_fill = QVecShared::ChannelStatus::Dead;
0294         ++ctr_dead;
0295       }
0296       // hot channel
0297       else if (zscore > m_sEPD_sigma_threshold)
0298       {
0299         type = "Hot";
0300         status_fill = QVecShared::ChannelStatus::Hot;
0301         ++ctr_hot;
0302       }
0303       // cold channel
0304       else
0305       {
0306         type = "Cold";
0307         status_fill = QVecShared::ChannelStatus::Cold;
0308         ++ctr_cold;
0309       }
0310 
0311       hSEPD_Bad_Channels->Fill(channel, static_cast<int>(status_fill));
0312       std::cout << std::format("{:4} Channel: {:3d}, arm: {}, rbin: {:2d}, Mean: {:5.2f}, Charge: {:5.2f}, Z-Score: {:5.2f}",
0313                                 type, channel, arm, rbin, mean_charge, charge, zscore) << std::endl;
0314     }
0315     else
0316     {
0317       h2->Fill(rbin, charge);
0318     }
0319   }
0320 
0321   std::cout << "Total Bad Channels: " << m_bad_channels.size() << ", Dead: "
0322         << ctr_dead << ", Hot: " << ctr_hot << ", Cold: " << ctr_cold << std::endl;
0323 
0324   std::cout << "Finished processing Hot sEPD channels" << std::endl;
0325   return Fun4AllReturnCodes::EVENT_OK;
0326 }
0327 
0328 void QVecCalib::init_hists()
0329 {
0330   unsigned int bins_psi = 126;
0331   double psi_low = -std::numbers::pi;
0332   double psi_high = std::numbers::pi;
0333 
0334   Fun4AllServer *se = Fun4AllServer::instance();
0335 
0336   hCentrality = new TH1F("hCentrality", "|z| < 10 cm and MB; Centrality [%]; Events", m_cent_bins, m_cent_low, m_cent_high);
0337   se->registerHisto(hCentrality);
0338 
0339   std::string pass_suffix;
0340   if (m_pass == Pass::ApplyRecentering)
0341   {
0342     pass_suffix = "_corr";
0343   }
0344   else if (m_pass == Pass::ApplyFlattening)
0345   {
0346     pass_suffix = "_corr2";
0347   }
0348 
0349   // n = 2, 3, 4, etc.
0350   for (int n : m_harmonics)
0351   {
0352     std::string name_S = std::format("h2_sEPD_Psi_S_{}{}", n, pass_suffix);
0353     std::string name_N = std::format("h2_sEPD_Psi_N_{}{}", n, pass_suffix);
0354     std::string name_NS = std::format("h2_sEPD_Psi_NS_{}{}", n, pass_suffix);
0355 
0356     std::string title_S = std::format("sEPD South #Psi (Order {0}); Centrality [%]; {0}#Psi^{{S}}_{{{0}}}", n);
0357     std::string title_N = std::format("sEPD North #Psi (Order {0}); Centrality [%]; {0}#Psi^{{N}}_{{{0}}}", n);
0358     std::string title_NS = std::format("sEPD North South #Psi (Order {0}); Centrality [%]; {0}#Psi^{{NS}}_{{{0}}}", n);
0359 
0360     m_hists2D[name_S] = new TH2F(name_S.c_str(), title_S.c_str(), m_cent_bins, m_cent_low, m_cent_high, bins_psi, psi_low, psi_high);
0361     m_hists2D[name_N] = new TH2F(name_N.c_str(), title_N.c_str(), m_cent_bins, m_cent_low, m_cent_high, bins_psi, psi_low, psi_high);
0362     m_hists2D[name_NS] = new TH2F(name_NS.c_str(), title_NS.c_str(), m_cent_bins, m_cent_low, m_cent_high, bins_psi, psi_low, psi_high);
0363 
0364     // South, North
0365     for (auto det : m_subdetectors)
0366     {
0367       std::string det_str = (det == QVecShared::Subdetector::S) ? "S" : "N";
0368       std::string det_name = (det == QVecShared::Subdetector::S) ? "South" : "North";
0369 
0370       std::string q_avg_sq_cross_name;
0371       std::string q_avg_sq_cross_title = std::format("sEPD {0}; Centrality [%]; <Q_{{{1},x}} Q_{{{1},y}}>", det_name, n);
0372 
0373       if (m_pass == Pass::ApplyRecentering)
0374       {
0375         q_avg_sq_cross_name = QVecShared::get_hist_name(det_str, "xy", n);
0376       }
0377 
0378       if (m_pass == Pass::ApplyFlattening)
0379       {
0380         q_avg_sq_cross_name = QVecShared::get_hist_name(det_str, "xy", n, "_corr");
0381       }
0382 
0383       if (!q_avg_sq_cross_name.empty())
0384       {
0385         m_profiles[q_avg_sq_cross_name] = new TProfile(q_avg_sq_cross_name.c_str(), q_avg_sq_cross_title.c_str(),
0386                                                                      m_cent_bins, m_cent_low, m_cent_high);
0387       }
0388 
0389       for (auto comp : m_components)
0390       {
0391         std::string comp_str = (comp == QVecShared::QComponent::X) ? "x" : "y";
0392         std::string name = QVecShared::get_hist_name(det_str, comp_str, n, pass_suffix);
0393 
0394         auto add_profile = [&](const std::string& prof_name, std::string_view label_suffix = "")
0395         {
0396           std::string title = std::format("sEPD {}; Centrality [%]; <Q_{{{},{}}}{}>", det_name, n, comp_str, label_suffix);
0397           m_profiles[prof_name] = new TProfile(prof_name.c_str(), title.c_str(), m_cent_bins, m_cent_low, m_cent_high);
0398         };
0399 
0400         add_profile(name);
0401 
0402         // 2. Only generate strings and profiles for the current pass
0403         switch (m_pass)
0404         {
0405           case Pass::ComputeRecentering:
0406           {
0407             break;
0408           }
0409 
0410           case Pass::ApplyRecentering:
0411           {
0412             std::string name_sq = QVecShared::get_hist_name(det_str, comp_str+comp_str, n);
0413             add_profile(name_sq, "^{2}");
0414             break;
0415           }
0416 
0417           case Pass::ApplyFlattening:
0418           {
0419             std::string name_sq_corr = QVecShared::get_hist_name(det_str, comp_str+comp_str, n, "_corr");
0420             add_profile(name_sq_corr, "^{2}");
0421             break;
0422           }
0423         }
0424       }
0425     }
0426 
0427     // Init for Combined NS Histograms
0428     if (m_pass == Pass::ApplyRecentering || m_pass == Pass::ApplyFlattening)
0429     {
0430       std::string det_str = "NS";
0431 
0432       // Initialize 2nd Moment Profiles for NS (needed to compute flattening)
0433       for (const auto* comp : {"xx", "yy", "xy"})
0434       {
0435         std::string name = QVecShared::get_hist_name(det_str, comp, n);
0436         std::string title = std::format("sEPD NS; Centrality [%]; <Q_{{{},{}}}>", n, comp);
0437         m_profiles[name] = new TProfile(name.c_str(), title.c_str(), m_cent_bins, m_cent_low, m_cent_high);
0438       }
0439 
0440       // Initialize Validation Profiles (Flattened NS)
0441       if (m_pass == Pass::ApplyFlattening)
0442       {
0443         for (const auto* comp : {"xx", "yy", "xy"})
0444         {
0445             std::string name = QVecShared::get_hist_name(det_str, comp, n, "_corr");
0446             std::string title = std::format("sEPD NS Corrected; Centrality [%]; <Q_{{{},{}}}^{{2}}>", n, comp);
0447             m_profiles[name] = new TProfile(name.c_str(), title.c_str(), m_cent_bins, m_cent_low, m_cent_high);
0448         }
0449       }
0450     }
0451   }
0452 }
0453 
0454 std::array<std::array<double, 2>, 2> QVecCalib::calculate_flattening_matrix(double xx, double yy, double xy, int n, int cent_bin, const std::string& det_label)
0455 {
0456   double D_arg = (xx * yy) - (xy * xy);
0457   if (D_arg < 1e-12)
0458   {
0459     std::cout << "Warning: Near-zero determinant in bin " << cent_bin << ". Skipping matrix calc." << std::endl;
0460     return std::array<std::array<double, 2>, 2>{{{1, 0}, {0, 1}}};  // Return Identity
0461   }
0462   double D = std::sqrt(D_arg);
0463 
0464   double N_term = D * (xx + yy + (2 * D));
0465   if (N_term <= 0)
0466   {
0467     std::cout << "Invalid N-term (" << N_term << ") for n=" << n << ", cent=" << cent_bin
0468           << ", det=" << det_label << std::endl;
0469     exit(1);
0470   }
0471   double inv_sqrt_N = 1.0 / std::sqrt(N_term);
0472 
0473   std::array<std::array<double, 2>, 2> mat{};
0474   mat[0][0] = inv_sqrt_N * (yy + D);
0475   mat[0][1] = -inv_sqrt_N * xy;
0476   mat[1][0] = mat[0][1];
0477   mat[1][1] = inv_sqrt_N * (xx + D);
0478   return mat;
0479 }
0480 
0481 template <typename T>
0482 T* QVecCalib::load_and_clone(TFile* file, const std::string& name) {
0483   T *obj {nullptr};
0484   file->GetObject(name.c_str(),obj);
0485   if (!obj)
0486   {
0487     std::cout << "Could not find histogram " << name << " in file " << file->GetName() << std::endl;
0488     exit(1);
0489   }
0490   return static_cast<T*>(obj->Clone());
0491 }
0492 
0493 int QVecCalib::load_correction_data()
0494 {
0495   Fun4AllServer *se = Fun4AllServer::instance();
0496   auto* file = TFile::Open(m_input_Q_calib.c_str());
0497 
0498   if (!file || file->IsZombie())
0499   {
0500     std::cout << PHWHERE << "Error! Cannot open: " << m_input_Q_calib << std::endl;
0501     return Fun4AllReturnCodes::ABORTRUN;
0502   }
0503 
0504   for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
0505   {
0506     int n = m_harmonics[h_idx];
0507 
0508     // Helper to load and register histograms automatically
0509     auto load_reg = [&](const std::string& det, const std::string& var, const std::string& suffix = "")
0510     {
0511       std::string name = QVecShared::get_hist_name(det, var, n, suffix);
0512       m_profiles[name] = load_and_clone<TProfile>(file, name);
0513       se->registerHisto(m_profiles[name]);
0514       return name;
0515     };
0516 
0517     // Load standard Recentering averages for S and N
0518     std::string s_names[2][2];  // [det][comp]
0519     for (int d = 0; d < 2; ++d)
0520     {
0521       std::string det_str = (d == 0) ? "S" : "N";
0522       s_names[d][0] = load_reg(det_str, "x");
0523       s_names[d][1] = load_reg(det_str, "y");
0524     }
0525 
0526     // Load Flattening (2nd moment) data if needed
0527     if (m_pass == Pass::ApplyFlattening)
0528     {
0529       for (const auto& det_str : {"S", "N", "NS"})
0530       {
0531         for (const auto& var : {"xx", "yy", "xy"})
0532         {
0533           load_reg(det_str, var);
0534         }
0535       }
0536     }
0537 
0538     // Populate the CorrectionData matrix
0539     for (size_t cent_bin = 0; cent_bin < m_cent_bins; ++cent_bin)
0540     {
0541       int bin = static_cast<int>(cent_bin) + 1;
0542 
0543       // Populate Recentering (S, N)
0544       for (int d = 0; d < 2; ++d)
0545       {
0546         m_correction_data[cent_bin][h_idx][d].avg_Q = {m_profiles[s_names[d][0]]->GetBinContent(bin), m_profiles[s_names[d][1]]->GetBinContent(bin)};
0547       }
0548 
0549       if (m_pass == Pass::ApplyFlattening)
0550       {
0551         // Populate Flattening for S, N, and NS
0552         for (int d = 0; d < (int) QVecShared::Subdetector::Count; ++d)
0553         {
0554           std::string det_str;
0555           switch (d)
0556           {
0557           case 0:
0558             det_str = "S";
0559             break;
0560           case 1:
0561             det_str = "N";
0562             break;
0563           default:
0564             det_str = "NS";
0565             break;
0566           }
0567 
0568           double xx = m_profiles[QVecShared::get_hist_name(det_str, "xx", n)]->GetBinContent(bin);
0569           double yy = m_profiles[QVecShared::get_hist_name(det_str, "yy", n)]->GetBinContent(bin);
0570           double xy = m_profiles[QVecShared::get_hist_name(det_str, "xy", n)]->GetBinContent(bin);
0571           
0572           auto& data = m_correction_data[cent_bin][h_idx][d];
0573           data.X_matrix = calculate_flattening_matrix(xx, yy, xy, n, cent_bin, det_str);
0574           data.avg_Q_xx = xx;
0575           data.avg_Q_yy = yy;
0576           data.avg_Q_xy = xy;
0577         }
0578       }
0579     }
0580   }
0581 
0582   file->Close();
0583   delete file;
0584   return Fun4AllReturnCodes::EVENT_OK;
0585 }
0586 
0587 void QVecCalib::prepare_average_hists()
0588 {
0589   Fun4AllServer *se = Fun4AllServer::instance();
0590 
0591   for (int n : m_harmonics)
0592   {
0593     std::string S_x_avg_name = QVecShared::get_hist_name("S", "x", n);
0594     std::string S_y_avg_name = QVecShared::get_hist_name("S", "y", n);
0595     std::string N_x_avg_name = QVecShared::get_hist_name("N", "x", n);
0596     std::string N_y_avg_name = QVecShared::get_hist_name("N", "y", n);
0597 
0598     std::string psi_S_name = std::format("h2_sEPD_Psi_S_{}", n);
0599     std::string psi_N_name = std::format("h2_sEPD_Psi_N_{}", n);
0600     std::string psi_NS_name = std::format("h2_sEPD_Psi_NS_{}", n);
0601 
0602     AverageHists h;
0603 
0604     h.S_x_avg = m_profiles.at(S_x_avg_name);
0605     h.S_y_avg = m_profiles.at(S_y_avg_name);
0606     h.N_x_avg = m_profiles.at(N_x_avg_name);
0607     h.N_y_avg = m_profiles.at(N_y_avg_name);
0608 
0609     h.Psi_S = m_hists2D.at(psi_S_name);
0610     h.Psi_N = m_hists2D.at(psi_N_name);
0611     h.Psi_NS = m_hists2D.at(psi_NS_name);
0612 
0613     se->registerHisto(h.S_x_avg);
0614     se->registerHisto(h.S_y_avg);
0615     se->registerHisto(h.N_x_avg);
0616     se->registerHisto(h.N_y_avg);
0617 
0618     se->registerHisto(h.Psi_S);
0619     se->registerHisto(h.Psi_N);
0620     se->registerHisto(h.Psi_NS);
0621 
0622     m_average_hists.push_back(h);
0623   }
0624 }
0625 
0626 void QVecCalib::prepare_recenter_hists()
0627 {
0628   Fun4AllServer *se = Fun4AllServer::instance();
0629 
0630   for (int n : m_harmonics)
0631   {
0632     std::string S_x_corr_avg_name = QVecShared::get_hist_name("S", "x", n, "_corr");
0633     std::string S_y_corr_avg_name = QVecShared::get_hist_name("S", "y", n, "_corr");
0634     std::string N_x_corr_avg_name = QVecShared::get_hist_name("N", "x", n, "_corr");
0635     std::string N_y_corr_avg_name = QVecShared::get_hist_name("N", "y", n, "_corr");
0636 
0637     std::string S_xx_avg_name = QVecShared::get_hist_name("S", "xx", n);
0638     std::string S_yy_avg_name = QVecShared::get_hist_name("S", "yy", n);
0639     std::string S_xy_avg_name = QVecShared::get_hist_name("S", "xy", n);
0640     std::string N_xx_avg_name = QVecShared::get_hist_name("N", "xx", n);
0641     std::string N_yy_avg_name = QVecShared::get_hist_name("N", "yy", n);
0642     std::string N_xy_avg_name = QVecShared::get_hist_name("N", "xy", n);
0643 
0644     std::string NS_xx_avg_name = QVecShared::get_hist_name("NS", "xx", n);
0645     std::string NS_yy_avg_name = QVecShared::get_hist_name("NS", "yy", n);
0646     std::string NS_xy_avg_name = QVecShared::get_hist_name("NS", "xy", n);
0647 
0648     std::string psi_S_name = std::format("h2_sEPD_Psi_S_{}_corr", n);
0649     std::string psi_N_name = std::format("h2_sEPD_Psi_N_{}_corr", n);
0650     std::string psi_NS_name = std::format("h2_sEPD_Psi_NS_{}_corr", n);
0651 
0652     RecenterHists h;
0653 
0654     h.S_x_corr_avg = m_profiles.at(S_x_corr_avg_name);
0655     h.S_y_corr_avg = m_profiles.at(S_y_corr_avg_name);
0656     h.N_x_corr_avg = m_profiles.at(N_x_corr_avg_name);
0657     h.N_y_corr_avg = m_profiles.at(N_y_corr_avg_name);
0658 
0659     h.S_xx_avg = m_profiles.at(S_xx_avg_name);
0660     h.S_yy_avg = m_profiles.at(S_yy_avg_name);
0661     h.S_xy_avg = m_profiles.at(S_xy_avg_name);
0662 
0663     h.N_xx_avg = m_profiles.at(N_xx_avg_name);
0664     h.N_yy_avg = m_profiles.at(N_yy_avg_name);
0665     h.N_xy_avg = m_profiles.at(N_xy_avg_name);
0666 
0667     h.NS_xx_avg = m_profiles.at(NS_xx_avg_name);
0668     h.NS_yy_avg = m_profiles.at(NS_yy_avg_name);
0669     h.NS_xy_avg = m_profiles.at(NS_xy_avg_name);
0670 
0671     h.Psi_S_corr = m_hists2D.at(psi_S_name);
0672     h.Psi_N_corr = m_hists2D.at(psi_N_name);
0673     h.Psi_NS_corr = m_hists2D.at(psi_NS_name);
0674 
0675     se->registerHisto(h.S_x_corr_avg);
0676     se->registerHisto(h.S_y_corr_avg);
0677     se->registerHisto(h.N_x_corr_avg);
0678     se->registerHisto(h.N_y_corr_avg);
0679 
0680     se->registerHisto(h.S_xx_avg);
0681     se->registerHisto(h.S_yy_avg);
0682     se->registerHisto(h.S_xy_avg);
0683 
0684     se->registerHisto(h.N_xx_avg);
0685     se->registerHisto(h.N_yy_avg);
0686     se->registerHisto(h.N_xy_avg);
0687 
0688     se->registerHisto(h.NS_xx_avg);
0689     se->registerHisto(h.NS_yy_avg);
0690     se->registerHisto(h.NS_xy_avg);
0691 
0692     se->registerHisto(h.Psi_S_corr);
0693     se->registerHisto(h.Psi_N_corr);
0694     se->registerHisto(h.Psi_NS_corr);
0695 
0696     m_recenter_hists.push_back(h);
0697   }
0698 }
0699 
0700 void QVecCalib::prepare_flattening_hists()
0701 {
0702   Fun4AllServer *se = Fun4AllServer::instance();
0703 
0704   for (int n : m_harmonics)
0705   {
0706     std::string S_x_corr2_avg_name = QVecShared::get_hist_name("S", "x", n, "_corr2");
0707     std::string S_y_corr2_avg_name = QVecShared::get_hist_name("S", "y", n, "_corr2");
0708     std::string N_x_corr2_avg_name = QVecShared::get_hist_name("N", "x", n, "_corr2");
0709     std::string N_y_corr2_avg_name = QVecShared::get_hist_name("N", "y", n, "_corr2");
0710 
0711     std::string S_xx_corr_avg_name = QVecShared::get_hist_name("S", "xx", n, "_corr");
0712     std::string S_yy_corr_avg_name = QVecShared::get_hist_name("S", "yy", n, "_corr");
0713     std::string S_xy_corr_avg_name = QVecShared::get_hist_name("S", "xy", n, "_corr");
0714     std::string N_xx_corr_avg_name = QVecShared::get_hist_name("N", "xx", n, "_corr");
0715     std::string N_yy_corr_avg_name = QVecShared::get_hist_name("N", "yy", n, "_corr");
0716     std::string N_xy_corr_avg_name = QVecShared::get_hist_name("N", "xy", n, "_corr");
0717 
0718     std::string NS_xx_corr_avg_name = QVecShared::get_hist_name("NS", "xx", n, "_corr");
0719     std::string NS_yy_corr_avg_name = QVecShared::get_hist_name("NS", "yy", n, "_corr");
0720     std::string NS_xy_corr_avg_name = QVecShared::get_hist_name("NS", "xy", n, "_corr");
0721 
0722     std::string psi_S_name = std::format("h2_sEPD_Psi_S_{}_corr2", n);
0723     std::string psi_N_name = std::format("h2_sEPD_Psi_N_{}_corr2", n);
0724     std::string psi_NS_name = std::format("h2_sEPD_Psi_NS_{}_corr2", n);
0725 
0726     FlatteningHists h;
0727 
0728     h.S_x_corr2_avg = m_profiles.at(S_x_corr2_avg_name);
0729     h.S_y_corr2_avg = m_profiles.at(S_y_corr2_avg_name);
0730     h.N_x_corr2_avg = m_profiles.at(N_x_corr2_avg_name);
0731     h.N_y_corr2_avg = m_profiles.at(N_y_corr2_avg_name);
0732 
0733     h.S_xx_corr_avg = m_profiles.at(S_xx_corr_avg_name);
0734     h.S_yy_corr_avg = m_profiles.at(S_yy_corr_avg_name);
0735     h.S_xy_corr_avg = m_profiles.at(S_xy_corr_avg_name);
0736 
0737     h.N_xx_corr_avg = m_profiles.at(N_xx_corr_avg_name);
0738     h.N_yy_corr_avg = m_profiles.at(N_yy_corr_avg_name);
0739     h.N_xy_corr_avg = m_profiles.at(N_xy_corr_avg_name);
0740 
0741     h.NS_xx_corr_avg = m_profiles.at(NS_xx_corr_avg_name);
0742     h.NS_yy_corr_avg = m_profiles.at(NS_yy_corr_avg_name);
0743     h.NS_xy_corr_avg = m_profiles.at(NS_xy_corr_avg_name);
0744 
0745     h.Psi_S_corr2 = m_hists2D.at(psi_S_name);
0746     h.Psi_N_corr2 = m_hists2D.at(psi_N_name);
0747     h.Psi_NS_corr2 = m_hists2D.at(psi_NS_name);
0748 
0749     se->registerHisto(h.S_x_corr2_avg);
0750     se->registerHisto(h.S_y_corr2_avg);
0751     se->registerHisto(h.N_x_corr2_avg);
0752     se->registerHisto(h.N_y_corr2_avg);
0753 
0754     se->registerHisto(h.S_xx_corr_avg);
0755     se->registerHisto(h.S_yy_corr_avg);
0756     se->registerHisto(h.S_xy_corr_avg);
0757 
0758     se->registerHisto(h.N_xx_corr_avg);
0759     se->registerHisto(h.N_yy_corr_avg);
0760     se->registerHisto(h.N_xy_corr_avg);
0761 
0762     se->registerHisto(h.NS_xx_corr_avg);
0763     se->registerHisto(h.NS_yy_corr_avg);
0764     se->registerHisto(h.NS_xy_corr_avg);
0765 
0766     se->registerHisto(h.Psi_S_corr2);
0767     se->registerHisto(h.Psi_N_corr2);
0768     se->registerHisto(h.Psi_NS_corr2);
0769 
0770     m_flattening_hists.push_back(h);
0771   }
0772 }
0773 
0774 int QVecCalib::InitRun(PHCompositeNode *topNode)
0775 {
0776   RunHeader* run_header = findNode::getClass<RunHeader>(topNode, "RunHeader");
0777   if (!run_header)
0778   {
0779     std::cout << PHWHERE << "RunHeader Node missing." << std::endl;
0780     return Fun4AllReturnCodes::ABORTRUN;
0781   }
0782 
0783   m_runnumber = run_header->get_RunNumber();
0784 
0785   EpdGeom* epdgeom = findNode::getClass<EpdGeom>(topNode, "TOWERGEOM_EPD");
0786   if (!epdgeom)
0787   {
0788     std::cout << PHWHERE << "TOWERGEOM_EPD Node missing." << std::endl;
0789     return Fun4AllReturnCodes::ABORTRUN;
0790   }
0791 
0792   m_trig_cache.assign(m_harmonics.size(), std::vector<std::pair<double, double>>(QVecShared::SEPD_CHANNELS));
0793 
0794   for (int channel = 0; channel < QVecShared::SEPD_CHANNELS; ++channel)
0795   {
0796     unsigned int key = TowerInfoDefs::encode_epd(channel);
0797     double phi = epdgeom->get_phi(key);
0798 
0799     for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
0800     {
0801       int n = m_harmonics[h_idx];
0802       m_trig_cache[h_idx][channel] = {std::cos(n * phi), std::sin(n * phi)};
0803     }
0804   }
0805 
0806   std::cout << "QVecCalib::InitRun - Trigonometry cache initialized for "
0807             << QVecShared::SEPD_CHANNELS << " channels." << std::endl;
0808 
0809   return Fun4AllReturnCodes::EVENT_OK;
0810 }
0811 
0812 void QVecCalib::process_averages(double cent, const QVecShared::QVec& q_S, const QVecShared::QVec& q_N, const AverageHists& h)
0813 {
0814   double psi_S = std::atan2(q_S.y, q_S.x);
0815   double psi_N = std::atan2(q_N.y, q_N.x);
0816   double psi_NS = std::atan2(q_S.y + q_N.y, q_S.x + q_N.x);
0817 
0818   h.S_x_avg->Fill(cent, q_S.x);
0819   h.S_y_avg->Fill(cent, q_S.y);
0820   h.N_x_avg->Fill(cent, q_N.x);
0821   h.N_y_avg->Fill(cent, q_N.y);
0822 
0823   h.Psi_S->Fill(cent, psi_S);
0824   h.Psi_N->Fill(cent, psi_N);
0825   h.Psi_NS->Fill(cent, psi_NS);
0826 }
0827 
0828 void QVecCalib::process_recentering(double cent, size_t h_idx, const QVecShared::QVec& q_S, const QVecShared::QVec& q_N, const RecenterHists& h)
0829 {
0830   int cent_bin = hCentrality->FindBin(cent) - 1;
0831 
0832   const auto& S = m_correction_data[cent_bin][h_idx][(size_t) QVecShared::Subdetector::S];
0833   const auto& N = m_correction_data[cent_bin][h_idx][(size_t) QVecShared::Subdetector::N];
0834 
0835   double Q_S_x_avg = S.avg_Q.x;
0836   double Q_S_y_avg = S.avg_Q.y;
0837   double Q_N_x_avg = N.avg_Q.x;
0838   double Q_N_y_avg = N.avg_Q.y;
0839 
0840   QVecShared::QVec q_S_corr = {q_S.x - Q_S_x_avg, q_S.y - Q_S_y_avg};
0841   QVecShared::QVec q_N_corr = {q_N.x - Q_N_x_avg, q_N.y - Q_N_y_avg};
0842 
0843   //  Construct Combined Recentered Vector
0844   //  We use the sum of the individually recentered vectors
0845   QVecShared::QVec q_NS_corr = {q_S_corr.x + q_N_corr.x, q_S_corr.y + q_N_corr.y};
0846 
0847   double psi_S_corr = std::atan2(q_S_corr.y, q_S_corr.x);
0848   double psi_N_corr = std::atan2(q_N_corr.y, q_N_corr.x);
0849   double psi_NS_corr = std::atan2(q_NS_corr.y, q_NS_corr.x);
0850 
0851   h.S_x_corr_avg->Fill(cent, q_S_corr.x);
0852   h.S_y_corr_avg->Fill(cent, q_S_corr.y);
0853   h.N_x_corr_avg->Fill(cent, q_N_corr.x);
0854   h.N_y_corr_avg->Fill(cent, q_N_corr.y);
0855 
0856   h.S_xx_avg->Fill(cent, q_S_corr.x * q_S_corr.x);
0857   h.S_yy_avg->Fill(cent, q_S_corr.y * q_S_corr.y);
0858   h.S_xy_avg->Fill(cent, q_S_corr.x * q_S_corr.y);
0859   h.N_xx_avg->Fill(cent, q_N_corr.x * q_N_corr.x);
0860   h.N_yy_avg->Fill(cent, q_N_corr.y * q_N_corr.y);
0861   h.N_xy_avg->Fill(cent, q_N_corr.x * q_N_corr.y);
0862 
0863   h.NS_xx_avg->Fill(cent, q_NS_corr.x * q_NS_corr.x);
0864   h.NS_yy_avg->Fill(cent, q_NS_corr.y * q_NS_corr.y);
0865   h.NS_xy_avg->Fill(cent, q_NS_corr.x * q_NS_corr.y);
0866 
0867   h.Psi_S_corr->Fill(cent, psi_S_corr);
0868   h.Psi_N_corr->Fill(cent, psi_N_corr);
0869   h.Psi_NS_corr->Fill(cent, psi_NS_corr);
0870 }
0871 
0872 void QVecCalib::process_flattening(double cent, size_t h_idx, const QVecShared::QVec& q_S, const QVecShared::QVec& q_N, const FlatteningHists& h)
0873 {
0874   int cent_bin = hCentrality->FindBin(cent) - 1;
0875 
0876   const auto& S = m_correction_data[cent_bin][h_idx][(size_t) QVecShared::Subdetector::S];
0877   const auto& N = m_correction_data[cent_bin][h_idx][(size_t) QVecShared::Subdetector::N];
0878   const auto& NS = m_correction_data[cent_bin][h_idx][(size_t) QVecShared::Subdetector::NS];
0879 
0880   double Q_S_x_avg = S.avg_Q.x;
0881   double Q_S_y_avg = S.avg_Q.y;
0882   double Q_N_x_avg = N.avg_Q.x;
0883   double Q_N_y_avg = N.avg_Q.y;
0884 
0885   QVecShared::QVec q_S_corr = {q_S.x - Q_S_x_avg, q_S.y - Q_S_y_avg};
0886   QVecShared::QVec q_N_corr = {q_N.x - Q_N_x_avg, q_N.y - Q_N_y_avg};
0887 
0888   // Construct Combined Recentered Vector
0889   QVecShared::QVec q_NS_corr = {q_S_corr.x + q_N_corr.x, q_S_corr.y + q_N_corr.y};
0890 
0891   const auto& X_S = S.X_matrix;
0892   const auto& X_N = N.X_matrix;
0893   const auto& X_NS = NS.X_matrix;
0894 
0895   double Q_S_x_corr2 = X_S[0][0] * q_S_corr.x + X_S[0][1] * q_S_corr.y;
0896   double Q_S_y_corr2 = X_S[1][0] * q_S_corr.x + X_S[1][1] * q_S_corr.y;
0897   double Q_N_x_corr2 = X_N[0][0] * q_N_corr.x + X_N[0][1] * q_N_corr.y;
0898   double Q_N_y_corr2 = X_N[1][0] * q_N_corr.x + X_N[1][1] * q_N_corr.y;
0899 
0900   double Q_NS_x_corr2 = X_NS[0][0] * q_NS_corr.x + X_NS[0][1] * q_NS_corr.y;
0901   double Q_NS_y_corr2 = X_NS[1][0] * q_NS_corr.x + X_NS[1][1] * q_NS_corr.y;
0902 
0903   QVecShared::QVec q_S_corr2 = {Q_S_x_corr2, Q_S_y_corr2};
0904   QVecShared::QVec q_N_corr2 = {Q_N_x_corr2, Q_N_y_corr2};
0905   QVecShared::QVec q_NS_corr2 = {Q_NS_x_corr2, Q_NS_y_corr2};
0906 
0907   double psi_S = std::atan2(q_S_corr2.y, q_S_corr2.x);
0908   double psi_N = std::atan2(q_N_corr2.y, q_N_corr2.x);
0909   double psi_NS = std::atan2(q_NS_corr2.y, q_NS_corr2.x);
0910 
0911   h.S_x_corr2_avg->Fill(cent, q_S_corr2.x);
0912   h.S_y_corr2_avg->Fill(cent, q_S_corr2.y);
0913   h.N_x_corr2_avg->Fill(cent, q_N_corr2.x);
0914   h.N_y_corr2_avg->Fill(cent, q_N_corr2.y);
0915 
0916   h.S_xx_corr_avg->Fill(cent, q_S_corr2.x * q_S_corr2.x);
0917   h.S_yy_corr_avg->Fill(cent, q_S_corr2.y * q_S_corr2.y);
0918   h.S_xy_corr_avg->Fill(cent, q_S_corr2.x * q_S_corr2.y);
0919   h.N_xx_corr_avg->Fill(cent, q_N_corr2.x * q_N_corr2.x);
0920   h.N_yy_corr_avg->Fill(cent, q_N_corr2.y * q_N_corr2.y);
0921   h.N_xy_corr_avg->Fill(cent, q_N_corr2.x * q_N_corr2.y);
0922 
0923   h.NS_xx_corr_avg->Fill(cent, q_NS_corr2.x * q_NS_corr2.x);
0924   h.NS_yy_corr_avg->Fill(cent, q_NS_corr2.y * q_NS_corr2.y);
0925   h.NS_xy_corr_avg->Fill(cent, q_NS_corr2.x * q_NS_corr2.y);
0926 
0927   h.Psi_S_corr2->Fill(cent, psi_S);
0928   h.Psi_N_corr2->Fill(cent, psi_N);
0929   h.Psi_NS_corr2->Fill(cent, psi_NS);
0930 }
0931 
0932 bool QVecCalib::process_sEPD()
0933 {
0934   double sepd_total_charge_south = 0;
0935   double sepd_total_charge_north = 0;
0936 
0937   // Loop over all sEPD Channels
0938   for (int channel = 0; channel < QVecShared::SEPD_CHANNELS; ++channel)
0939   {
0940     double charge = m_evtdata->get_sepd_charge(channel);
0941 
0942     // Skip Bad Channels
0943     if (m_bad_channels.contains(channel) || charge <= 0)
0944     {
0945       continue;
0946     }
0947 
0948     unsigned int key = TowerInfoDefs::encode_epd(channel);
0949     unsigned int arm = TowerInfoDefs::get_epd_arm(key);
0950 
0951     // arm = 0: South
0952     // arm = 1: North
0953     if (arm == 0)
0954     {
0955       sepd_total_charge_south += charge;
0956     }
0957     else
0958     {
0959       sepd_total_charge_north += charge;
0960     }
0961 
0962     // Compute Raw Q vectors for each harmonic and respective arm
0963     for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
0964     {
0965       // Optimized lookup instead of std::cos/std::sin calls
0966       const auto& [cached_cos, cached_sin] = m_trig_cache[h_idx][channel];
0967 
0968       m_q_vectors[h_idx][arm].x += charge * cached_cos;
0969       m_q_vectors[h_idx][arm].y += charge * cached_sin;
0970     }
0971   }
0972 
0973   // Skip Events with Zero sEPD Total Charge in either arm
0974   if (sepd_total_charge_south == 0 || sepd_total_charge_north == 0)
0975   {
0976     return false;
0977   }
0978 
0979   // Normalize the Q-vectors by total charge
0980   for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
0981   {
0982     for (auto det : m_subdetectors)
0983     {
0984       size_t det_idx = (det == QVecShared::Subdetector::S) ? 0 : 1;
0985       double sepd_total_charge = (det_idx == 0) ? sepd_total_charge_south : sepd_total_charge_north;
0986       m_q_vectors[h_idx][det_idx].x /= sepd_total_charge;
0987       m_q_vectors[h_idx][det_idx].y /= sepd_total_charge;
0988     }
0989   }
0990 
0991   return true;
0992 }
0993 
0994 bool QVecCalib::process_event_check()
0995 {
0996   double cent = m_evtdata->get_event_centrality();
0997   int cent_bin = hSEPD_Charge_Min->FindBin(cent);
0998 
0999   double sepd_totalcharge = m_evtdata->get_sepd_totalcharge();
1000 
1001   double sepd_totalcharge_min = hSEPD_Charge_Min->GetBinContent(cent_bin);
1002   double sepd_totalcharge_max = hSEPD_Charge_Max->GetBinContent(cent_bin);
1003 
1004   return sepd_totalcharge > sepd_totalcharge_min && sepd_totalcharge < sepd_totalcharge_max;
1005 }
1006 
1007 //____________________________________________________________________________..
1008 int QVecCalib::process_event(PHCompositeNode *topNode)
1009 {
1010   m_evtdata = findNode::getClass<EventPlaneData>(topNode, "EventPlaneData");
1011   if (!m_evtdata)
1012   {
1013     std::cout << PHWHERE << "EventPlaneData Node missing." << std::endl;
1014     return Fun4AllReturnCodes::ABORTRUN;
1015   }
1016 
1017   int event_id = m_evtdata->get_event_id();
1018 
1019   if (Verbosity() && m_event % PROGRESS_REPORT_INTERVAL == 0)
1020   {
1021     std::cout << "Progress: " << m_event << ", Global: " << event_id << std::endl;
1022   }
1023   ++m_event;
1024 
1025   double cent = m_evtdata->get_event_centrality();
1026 
1027   bool isGood = process_event_check();
1028 
1029   // Skip Events with non correlation between centrality and sEPD
1030   if (!isGood)
1031   {
1032     ++m_event_counters.bad_centrality_sepd_correlation;
1033     return Fun4AllReturnCodes::ABORTEVENT;
1034   }
1035 
1036   isGood = process_sEPD();
1037 
1038   // Skip Events with Zero sEPD Total Charge in either arm
1039   if (!isGood)
1040   {
1041     ++m_event_counters.zero_sepd_total_charge;
1042     return Fun4AllReturnCodes::ABORTEVENT;
1043   }
1044 
1045   hCentrality->Fill(cent);
1046 
1047   for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
1048   {
1049     const auto& q_S = m_q_vectors[h_idx][0];  // 0 for South
1050     const auto& q_N = m_q_vectors[h_idx][1];  // 1 for North
1051 
1052     // --- First Pass: Derive 1st Order ---
1053     if (m_pass == Pass::ComputeRecentering)
1054     {
1055       process_averages(cent, q_S, q_N, m_average_hists[h_idx]);
1056     }
1057 
1058     // --- Second Pass: Apply 1st Order, Derive 2nd Order ---
1059     else if (m_pass == Pass::ApplyRecentering)
1060     {
1061       process_recentering(cent, h_idx, q_S, q_N, m_recenter_hists[h_idx]);
1062     }
1063 
1064     // --- Third Pass: Apply 2nd Order, Validate ---
1065     else if (m_pass == Pass::ApplyFlattening)
1066     {
1067       process_flattening(cent, h_idx, q_S, q_N, m_flattening_hists[h_idx]);
1068     }
1069   }
1070 
1071   return Fun4AllReturnCodes::EVENT_OK;
1072 }
1073 
1074 //____________________________________________________________________________..
1075 int QVecCalib::ResetEvent(PHCompositeNode * /*topNode*/)
1076 {
1077   m_q_vectors = {};
1078 
1079   return Fun4AllReturnCodes::EVENT_OK;
1080 }
1081 
1082 void QVecCalib::compute_averages(size_t cent_bin, int h_idx)
1083 {
1084   int n = m_harmonics[h_idx];
1085 
1086   std::string S_x_avg_name = QVecShared::get_hist_name("S", "x", n);
1087   std::string S_y_avg_name = QVecShared::get_hist_name("S", "y", n);
1088   std::string N_x_avg_name = QVecShared::get_hist_name("N", "x", n);
1089   std::string N_y_avg_name = QVecShared::get_hist_name("N", "y", n);
1090 
1091   int bin = cent_bin + 1;
1092 
1093   double Q_S_x_avg = m_profiles[S_x_avg_name]->GetBinContent(bin);
1094   double Q_S_y_avg = m_profiles[S_y_avg_name]->GetBinContent(bin);
1095   double Q_N_x_avg = m_profiles[N_x_avg_name]->GetBinContent(bin);
1096   double Q_N_y_avg = m_profiles[N_y_avg_name]->GetBinContent(bin);
1097 
1098   m_correction_data[cent_bin][h_idx][static_cast<size_t>(QVecShared::Subdetector::S)].avg_Q = {Q_S_x_avg, Q_S_y_avg};
1099   m_correction_data[cent_bin][h_idx][static_cast<size_t>(QVecShared::Subdetector::N)].avg_Q = {Q_N_x_avg, Q_N_y_avg};
1100 
1101   std::cout << std::format(
1102       "Centrality Bin: {}, "
1103       "Harmonic: {}, "
1104       "Q_S_x_avg: {:13.10f}, "
1105       "Q_S_y_avg: {:13.10f}, "
1106       "Q_N_x_avg: {:13.10f}, "
1107       "Q_N_y_avg: {:13.10f}",
1108       cent_bin,
1109       n,
1110       Q_S_x_avg,
1111       Q_S_y_avg,
1112       Q_N_x_avg,
1113       Q_N_y_avg) << std::endl;
1114 }
1115 
1116 void QVecCalib::compute_recentering(size_t cent_bin, int h_idx)
1117 {
1118   int n = m_harmonics[h_idx];
1119 
1120   std::string S_x_corr_avg_name = QVecShared::get_hist_name("S", "x", n, "_corr");
1121   std::string S_y_corr_avg_name = QVecShared::get_hist_name("S", "y", n, "_corr");
1122   std::string N_x_corr_avg_name = QVecShared::get_hist_name("N", "x", n, "_corr");
1123   std::string N_y_corr_avg_name = QVecShared::get_hist_name("N", "y", n, "_corr");
1124 
1125   int bin = cent_bin + 1;
1126 
1127   double Q_S_x_corr_avg = m_profiles[S_x_corr_avg_name]->GetBinContent(bin);
1128   double Q_S_y_corr_avg = m_profiles[S_y_corr_avg_name]->GetBinContent(bin);
1129   double Q_N_x_corr_avg = m_profiles[N_x_corr_avg_name]->GetBinContent(bin);
1130   double Q_N_y_corr_avg = m_profiles[N_y_corr_avg_name]->GetBinContent(bin);
1131 
1132   // -- Compute 2nd Order Correction --
1133   std::string S_xx_avg_name = QVecShared::get_hist_name("S", "xx", n);
1134   std::string S_yy_avg_name = QVecShared::get_hist_name("S", "yy", n);
1135   std::string S_xy_avg_name = QVecShared::get_hist_name("S", "xy", n);
1136   std::string N_xx_avg_name = QVecShared::get_hist_name("N", "xx", n);
1137   std::string N_yy_avg_name = QVecShared::get_hist_name("N", "yy", n);
1138   std::string N_xy_avg_name = QVecShared::get_hist_name("N", "xy", n);
1139 
1140   double Q_S_xx_avg = m_profiles[S_xx_avg_name]->GetBinContent(bin);
1141   double Q_S_yy_avg = m_profiles[S_yy_avg_name]->GetBinContent(bin);
1142   double Q_S_xy_avg = m_profiles[S_xy_avg_name]->GetBinContent(bin);
1143   double Q_N_xx_avg = m_profiles[N_xx_avg_name]->GetBinContent(bin);
1144   double Q_N_yy_avg = m_profiles[N_yy_avg_name]->GetBinContent(bin);
1145   double Q_N_xy_avg = m_profiles[N_xy_avg_name]->GetBinContent(bin);
1146 
1147   // -- Compute NS Matrix --
1148   std::string NS_xx_avg_name = QVecShared::get_hist_name("NS", "xx", n);
1149   std::string NS_yy_avg_name = QVecShared::get_hist_name("NS", "yy", n);
1150   std::string NS_xy_avg_name = QVecShared::get_hist_name("NS", "xy", n);
1151 
1152   double Q_NS_xx_avg = m_profiles[NS_xx_avg_name]->GetBinContent(bin);
1153   double Q_NS_yy_avg = m_profiles[NS_yy_avg_name]->GetBinContent(bin);
1154   double Q_NS_xy_avg = m_profiles[NS_xy_avg_name]->GetBinContent(bin);
1155 
1156   m_correction_data[cent_bin][h_idx][static_cast<size_t>(QVecShared::Subdetector::NS)].X_matrix = calculate_flattening_matrix(Q_NS_xx_avg, Q_NS_yy_avg, Q_NS_xy_avg, n, cent_bin, "NS");
1157 
1158   for (size_t det_idx = 0; det_idx < 2; ++det_idx)
1159   {
1160     double xx = (det_idx == 0) ? Q_S_xx_avg : Q_N_xx_avg;
1161     double yy = (det_idx == 0) ? Q_S_yy_avg : Q_N_yy_avg;
1162     double xy = (det_idx == 0) ? Q_S_xy_avg : Q_N_xy_avg;
1163 
1164     std::string label = (det_idx == 0) ? "S" : "N";
1165 
1166     m_correction_data[cent_bin][h_idx][det_idx].X_matrix = calculate_flattening_matrix(xx, yy, xy, n, cent_bin, label);
1167   }
1168 
1169   std::cout << std::format(
1170       "Centrality Bin: {}, "
1171       "Harmonic: {}, "
1172       "Q_S_x_corr_avg: {:13.10f}, "
1173       "Q_S_y_corr_avg: {:13.10f}, "
1174       "Q_N_x_corr_avg: {:13.10f}, "
1175       "Q_N_y_corr_avg: {:13.10f}, "
1176       "Q_S_xx_avg / Q_S_yy_avg: {:13.10f}, "
1177       "Q_N_xx_avg / Q_N_yy_avg: {:13.10f}, "
1178       "Q_NS_xx_avg / Q_NS_yy_avg: {:13.10f}, "
1179       "Q_S_xy_avg: {:13.10f}, "
1180       "Q_N_xy_avg: {:13.10f}, "
1181       "Q_NS_xy_avg: {:13.10f}",
1182       cent_bin,
1183       n,
1184       Q_S_x_corr_avg,
1185       Q_S_y_corr_avg,
1186       Q_N_x_corr_avg,
1187       Q_N_y_corr_avg,
1188       Q_S_xx_avg / Q_S_yy_avg,
1189       Q_N_xx_avg / Q_N_yy_avg,
1190       Q_NS_xx_avg / Q_NS_yy_avg,
1191       Q_S_xy_avg,
1192       Q_N_xy_avg,
1193       Q_NS_xy_avg) << std::endl;
1194 }
1195 
1196 void QVecCalib::print_flattening(size_t cent_bin, int n) const
1197 {
1198   std::string S_x_corr2_avg_name = QVecShared::get_hist_name("S", "x", n, "_corr2");
1199   std::string S_y_corr2_avg_name = QVecShared::get_hist_name("S", "y", n, "_corr2");
1200   std::string N_x_corr2_avg_name = QVecShared::get_hist_name("N", "x", n, "_corr2");
1201   std::string N_y_corr2_avg_name = QVecShared::get_hist_name("N", "y", n, "_corr2");
1202 
1203   std::string S_xx_corr_avg_name = QVecShared::get_hist_name("S", "xx", n, "_corr");
1204   std::string S_yy_corr_avg_name = QVecShared::get_hist_name("S", "yy", n, "_corr");
1205   std::string S_xy_corr_avg_name = QVecShared::get_hist_name("S", "xy", n, "_corr");
1206   std::string N_xx_corr_avg_name = QVecShared::get_hist_name("N", "xx", n, "_corr");
1207   std::string N_yy_corr_avg_name = QVecShared::get_hist_name("N", "yy", n, "_corr");
1208   std::string N_xy_corr_avg_name = QVecShared::get_hist_name("N", "xy", n, "_corr");
1209 
1210   std::string NS_xx_corr_avg_name = QVecShared::get_hist_name("NS", "xx", n, "_corr");
1211   std::string NS_yy_corr_avg_name = QVecShared::get_hist_name("NS", "yy", n, "_corr");
1212   std::string NS_xy_corr_avg_name = QVecShared::get_hist_name("NS", "xy", n, "_corr");
1213 
1214   int bin = cent_bin + 1;
1215 
1216   double Q_S_x_corr2_avg = m_profiles.at(S_x_corr2_avg_name)->GetBinContent(bin);
1217   double Q_S_y_corr2_avg = m_profiles.at(S_y_corr2_avg_name)->GetBinContent(bin);
1218   double Q_N_x_corr2_avg = m_profiles.at(N_x_corr2_avg_name)->GetBinContent(bin);
1219   double Q_N_y_corr2_avg = m_profiles.at(N_y_corr2_avg_name)->GetBinContent(bin);
1220 
1221   double Q_S_xx_corr_avg = m_profiles.at(S_xx_corr_avg_name)->GetBinContent(bin);
1222   double Q_S_yy_corr_avg = m_profiles.at(S_yy_corr_avg_name)->GetBinContent(bin);
1223   double Q_S_xy_corr_avg = m_profiles.at(S_xy_corr_avg_name)->GetBinContent(bin);
1224   double Q_N_xx_corr_avg = m_profiles.at(N_xx_corr_avg_name)->GetBinContent(bin);
1225   double Q_N_yy_corr_avg = m_profiles.at(N_yy_corr_avg_name)->GetBinContent(bin);
1226   double Q_N_xy_corr_avg = m_profiles.at(N_xy_corr_avg_name)->GetBinContent(bin);
1227 
1228   double Q_NS_xx_corr_avg = m_profiles.at(NS_xx_corr_avg_name)->GetBinContent(bin);
1229   double Q_NS_yy_corr_avg = m_profiles.at(NS_yy_corr_avg_name)->GetBinContent(bin);
1230   double Q_NS_xy_corr_avg = m_profiles.at(NS_xy_corr_avg_name)->GetBinContent(bin);
1231 
1232   std::cout << std::format(
1233       "Centrality Bin: {}, "
1234       "Harmonic: {}, "
1235       "Q_S_x_corr2_avg: {:13.10f}, "
1236       "Q_S_y_corr2_avg: {:13.10f}, "
1237       "Q_N_x_corr2_avg: {:13.10f}, "
1238       "Q_N_y_corr2_avg: {:13.10f}, "
1239       "Q_S_xx_corr_avg / Q_S_yy_corr_avg: {:13.10f}, "
1240       "Q_N_xx_corr_avg / Q_N_yy_corr_avg: {:13.10f}, "
1241       "Q_NS_xx_corr_avg / Q_NS_yy_corr_avg: {:13.10f}, "
1242       "Q_S_xy_corr_avg: {:13.10f}, "
1243       "Q_N_xy_corr_avg: {:13.10f}, "
1244       "Q_NS_xy_corr_avg: {:13.10f}",
1245       cent_bin,
1246       n,
1247       Q_S_x_corr2_avg,
1248       Q_S_y_corr2_avg,
1249       Q_N_x_corr2_avg,
1250       Q_N_y_corr2_avg,
1251       Q_S_xx_corr_avg / Q_S_yy_corr_avg,
1252       Q_N_xx_corr_avg / Q_N_yy_corr_avg,
1253       Q_NS_xx_corr_avg / Q_NS_yy_corr_avg,
1254       Q_S_xy_corr_avg,
1255       Q_N_xy_corr_avg,
1256       Q_NS_xy_corr_avg) << std::endl;
1257 }
1258 
1259 void QVecCalib::write_cdb()
1260 {
1261   std::error_code ec;
1262   if (std::filesystem::create_directories(m_cdb_output_dir, ec))
1263   {
1264     std::cout << "Success: Directory " << m_cdb_output_dir << " created" << std::endl;
1265   }
1266   else if (ec)
1267   {
1268     std::cout << "Failed to create directory " <<  m_cdb_output_dir << ": " << ec.message() << std::endl;
1269     exit(1);
1270   }
1271   else
1272   {
1273     std::cout << "Info: Directory " << m_cdb_output_dir << " already exists." << std::endl;
1274   }
1275 
1276   write_cdb_BadTowers();
1277   write_cdb_EventPlane();
1278 }
1279 
1280 void QVecCalib::write_cdb_BadTowers()
1281 {
1282   std::cout << "Writing Bad Towers CDB" << std::endl;
1283 
1284   std::string payload = "SEPD_HotMap";
1285   std::string fieldname_status = "status";
1286   std::string fieldname_sigma = "SEPD_sigma";
1287   std::string output_file = std::format("{}/{}-{}-{}.root", m_cdb_output_dir, payload, m_dst_tag, m_runnumber);
1288 
1289   CDBTTree cdbttree(output_file);
1290 
1291   for (int channel = 0; channel < QVecShared::SEPD_CHANNELS; ++channel)
1292   {
1293     unsigned int key = TowerInfoDefs::encode_epd(channel);
1294     int status = hSEPD_Bad_Channels->GetBinContent(channel+1);
1295 
1296     float sigma = 0;
1297 
1298     // Hot
1299     if (status == static_cast<int>(QVecShared::ChannelStatus::Hot))
1300     {
1301       sigma = SIGMA_HOT;
1302     }
1303 
1304     // Cold
1305     else if (status == static_cast<int>(QVecShared::ChannelStatus::Cold))
1306     {
1307       sigma = SIGMA_COLD;
1308     }
1309 
1310     cdbttree.SetIntValue(key, fieldname_status, status);
1311     cdbttree.SetFloatValue(key, fieldname_sigma, sigma);
1312   }
1313 
1314   std::cout << "Saving CDB: " << payload << " to " << output_file << std::endl;
1315 
1316   cdbttree.Commit();
1317   cdbttree.WriteCDBTTree();
1318 }
1319 
1320 void QVecCalib::write_cdb_EventPlane()
1321 {
1322   std::cout << "Writing Event Plane CDB" << std::endl;
1323 
1324   std::string payload = "SEPD_EventPlaneCalib";
1325   std::string output_file = std::format("{}/{}-{}-{}.root", m_cdb_output_dir, payload, m_dst_tag, m_runnumber);
1326 
1327   CDBTTree cdbttree(output_file);
1328 
1329   for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
1330   {
1331     int n = m_harmonics[h_idx];
1332 
1333     // Define lambdas to generate field names consistently
1334     auto field = [&](const std::string& det, const std::string& var)
1335     {
1336       return std::format("Q_{}_{}_{}_avg", det, var, n);
1337     };
1338 
1339     for (size_t cent_bin = 0; cent_bin < m_cent_bins; ++cent_bin)
1340     {
1341       int key = cent_bin;
1342 
1343       // Iterate through all subdetectors (S, N, NS) using the Enum Count
1344       for (size_t d = 0; d < static_cast<size_t>(QVecShared::Subdetector::Count); ++d)
1345       {
1346         auto det_enum = static_cast<QVecShared::Subdetector>(d);
1347 
1348         // Map enum to the string labels used in the CDB field names
1349         std::string det_label;
1350         switch (det_enum)
1351         {
1352         case QVecShared::Subdetector::S:
1353           det_label = "S";
1354           break;
1355         case QVecShared::Subdetector::N:
1356           det_label = "N";
1357           break;
1358         case QVecShared::Subdetector::NS:
1359           det_label = "NS";
1360           break;
1361         default:
1362           continue;
1363         }
1364 
1365         const auto& data = m_correction_data[cent_bin][h_idx][d];
1366         // 1st Order Moments (Recentering) - Skip for NS as it is a combined vector
1367         if (det_enum != QVecShared::Subdetector::NS)
1368         {
1369           cdbttree.SetDoubleValue(key, field(det_label, "x"), data.avg_Q.x);
1370           cdbttree.SetDoubleValue(key, field(det_label, "y"), data.avg_Q.y);
1371         }
1372 
1373         // 2nd Order Moments (Flattening) - Applicable to S, N, and NS
1374         cdbttree.SetDoubleValue(key, field(det_label, "xx"), data.avg_Q_xx);
1375         cdbttree.SetDoubleValue(key, field(det_label, "yy"), data.avg_Q_yy);
1376         cdbttree.SetDoubleValue(key, field(det_label, "xy"), data.avg_Q_xy);
1377       }
1378     }
1379   }
1380 
1381   std::cout << "Saving CDB: " << payload << " to " << output_file << std::endl;
1382 
1383   cdbttree.Commit();
1384   cdbttree.WriteCDBTTree();
1385 }
1386 
1387 //____________________________________________________________________________..
1388 int QVecCalib::End(PHCompositeNode * /*topNode*/)
1389 {
1390   std::cout << "QVecCalib::End(PHCompositeNode *topNode) This is the End..." << std::endl;
1391 
1392   std::cout << "\n--- Event Counter Summary ---" << std::endl;
1393   std::cout << "Bad Centrality/sEPD corr: " << m_event_counters.bad_centrality_sepd_correlation << std::endl;
1394   std::cout << "Zero sEPD Charge:         " << m_event_counters.zero_sepd_total_charge << std::endl;
1395   std::cout << "Total Events Seen:        " << m_event << std::endl;
1396   std::cout << "-----------------------------\n" << std::endl;
1397 
1398   for (size_t cent_bin = 0; cent_bin < m_cent_bins; ++cent_bin)
1399   {
1400     for (size_t h_idx = 0; h_idx < m_harmonics.size(); ++h_idx)
1401     {
1402       int n = m_harmonics[h_idx];
1403 
1404       if (m_pass == Pass::ComputeRecentering)
1405       {
1406         compute_averages(cent_bin, h_idx);
1407       }
1408 
1409       else if (m_pass == Pass::ApplyRecentering)
1410       {
1411         compute_recentering(cent_bin, h_idx);
1412       }
1413 
1414       else if (m_pass == Pass::ApplyFlattening)
1415       {
1416         print_flattening(cent_bin, n);
1417       }
1418     }
1419   }
1420 
1421   if (m_pass == Pass::ApplyFlattening)
1422   {
1423     write_cdb();
1424   }
1425 
1426   return Fun4AllReturnCodes::EVENT_OK;
1427 }