File indexing completed on 2026-08-31 08:22:18
0001 #include "MicromegasDriftQA.h"
0002
0003 #include <qautils/QAHistManagerDef.h>
0004
0005 #include <fun4all/Fun4AllHistoManager.h>
0006 #include <fun4all/Fun4AllReturnCodes.h>
0007
0008 #include <phool/PHCompositeNode.h>
0009 #include <phool/getClass.h>
0010 #include <phool/phool.h> // for PHWHERE
0011
0012 #include <g4detectors/PHG4CylinderGeomContainer.h>
0013 #include <micromegas/CylinderGeomMicromegas.h>
0014 #include <micromegas/MicromegasDefs.h>
0015 #include <trackbase/ActsGeometry.h>
0016 #include <trackbase/TrackFitUtils.h>
0017 #include <trackbase/TrkrCluster.h>
0018 #include <trackbase/TrkrClusterContainer.h>
0019 #include <trackbase/TrkrDefs.h>
0020 #include <trackbase_historic/SvtxTrack.h>
0021 #include <trackbase_historic/SvtxTrackMap.h>
0022
0023 #include <TDirectory.h>
0024 #include <TF1.h>
0025 #include <TF2.h>
0026 #include <TH1.h>
0027 #include <TH2.h>
0028 #include <TVector3.h>
0029
0030 #include <array>
0031 #include <cassert>
0032 #include <climits>
0033 #include <cmath>
0034 #include <format>
0035 #include <iostream>
0036 #include <string>
0037 #include <vector>
0038
0039 namespace
0040 {
0041 template <class T>
0042 class range_adaptor
0043 {
0044 public:
0045 explicit range_adaptor(const T& range)
0046 : m_range(range)
0047 {
0048 }
0049 const typename T::first_type& begin() { return m_range.first; }
0050 const typename T::second_type& end() { return m_range.second; }
0051
0052 private:
0053 T m_range;
0054 };
0055
0056 double normalize_angle(double phi)
0057 {
0058 while (phi < 0)
0059 {
0060 phi += 2 * M_PI;
0061 }
0062 while (phi >= 2 * M_PI)
0063 {
0064 phi -= 2 * M_PI;
0065 }
0066 return phi;
0067 }
0068
0069 bool phi_in_range(double phi, double min, double max)
0070 {
0071 phi = normalize_angle(phi);
0072 min = normalize_angle(min);
0073 max = normalize_angle(max);
0074 return (min < max) ? (phi >= min && phi <= max)
0075 : (phi >= min || phi <= max);
0076 }
0077
0078
0079
0080 bool helix_plane_intersection(
0081 double t_min,
0082 double t_max,
0083 double zmin,
0084 double zmax,
0085 double R,
0086 double X0,
0087 double Y0,
0088 double intersect_rz,
0089 double slope_rz,
0090 const TVector3& ptile,
0091 const TVector3& ntile,
0092 TVector3& intersect)
0093 {
0094
0095 const int max_iter = 10;
0096 const double tol = 1e-6;
0097
0098
0099 const double C = ntile.X() * (X0 - ptile.X()) + ntile.Y() * (Y0 - ptile.Y()) + ntile.Z() * (intersect_rz - ptile.Z());
0100
0101
0102 auto f = [&](double t)
0103 {
0104 const double xt = X0 + R * std::cos(t);
0105 const double yt = Y0 + R * std::sin(t);
0106 const double Rt = std::sqrt(xt * xt + yt * yt);
0107 return ntile.X() * R * std::cos(t) + ntile.Y() * R * std::sin(t) + ntile.Z() * slope_rz * Rt + C;
0108 };
0109
0110 auto df = [&](double t)
0111 {
0112 const double xt = X0 + R * std::cos(t);
0113 const double yt = Y0 + R * std::sin(t);
0114 const double Rt = std::sqrt(xt * xt + yt * yt);
0115 return -ntile.X() * R * std::sin(t) + ntile.Y() * R * std::cos(t) + ntile.Z() * R * slope_rz * (Y0 * std::cos(t) - X0 * std::sin(t)) / Rt;
0116 };
0117
0118 auto solve_from = [&](double t_seed, TVector3& result) -> bool
0119 {
0120 double t = t_seed;
0121 for (int i = 0; i < max_iter; ++i)
0122 {
0123 const double ft = f(t);
0124 const double dft = df(t);
0125 if (std::abs(dft) < 1e-8)
0126 {
0127 return false;
0128 }
0129 const double t_new = t - ft / dft;
0130
0131 const double x = X0 + R * std::cos(t_new);
0132 const double y = Y0 + R * std::sin(t_new);
0133 const double Rt_n = std::sqrt(x * x + y * y);
0134 const double z = slope_rz * Rt_n + intersect_rz;
0135 const double phi = std::atan2(y, x);
0136
0137 const TVector3 cand(x, y, z);
0138 const bool phi_ok = phi_in_range(phi, t_min - 1e-4, t_max + 1e-4);
0139 const bool z_ok = (z >= zmin - 1e-4 && z <= zmax + 1e-4);
0140 const bool proj_ok = (std::abs(ntile.Dot(cand - ptile)) <= 0.05);
0141
0142 if (std::abs(t_new - t) < tol && proj_ok && phi_ok && z_ok)
0143 {
0144 result = cand;
0145 return true;
0146 }
0147 t = t_new;
0148 }
0149 return false;
0150 };
0151
0152 auto wrap = [&](double t)
0153 {
0154 while (t > t_max)
0155 {
0156 t -= 2 * M_PI;
0157 }
0158 while (t < t_min)
0159 {
0160 t += 2 * M_PI;
0161 }
0162 return t;
0163 };
0164
0165
0166
0167 std::vector<double> t_seeds(3);
0168 const double t_center = 0.5 * (t_min + t_max);
0169 const double delta = 2.0 * M_PI / 3.0;
0170 for (int i = 0; i < 3; ++i)
0171 {
0172 t_seeds[i]=wrap(t_center + i * delta);
0173 }
0174
0175 for (const double t_seed : t_seeds)
0176 {
0177 if (solve_from(t_seed, intersect))
0178 {
0179 return true;
0180 }
0181 }
0182 return false;
0183 }
0184
0185
0186
0187 double fit_function_2d(double* x, double* par)
0188 {
0189 const int itile = static_cast<int>(std::floor(x[0]));
0190 const double z = x[1];
0191 if (itile < 0 || itile >= 8)
0192 {
0193 TF2::RejectPoint();
0194 return 0.;
0195 }
0196 return par[itile + 1] + par[0] * z;
0197 }
0198
0199
0200 const std::array<const char*, 8> k_tile_names =
0201 {"SCOZ", "SCIZ", "NCIZ", "NCOZ", "SEZ", "NEZ", "SWZ", "NWZ"};
0202
0203
0204 constexpr int k_nzbins = 220;
0205
0206
0207 constexpr double k_max_z = 110;
0208
0209
0210 constexpr double k_max_dz = 10;
0211
0212 }
0213
0214
0215 MicromegasDriftQA::MicromegasDriftQA(const std::string& name)
0216 : SubsysReco(name)
0217 {
0218 }
0219
0220
0221 int MicromegasDriftQA::InitRun(PHCompositeNode* topNode)
0222 {
0223 if (Verbosity())
0224 {
0225 std::cout << Name() << "::InitRun"
0226 << " drift_velocity=" << m_drift_velocity << " cm/ns"
0227 << " min_tpc_layer=" << m_min_tpc_layer
0228 << " max_tpc_layer=" << m_max_tpc_layer
0229 << std::endl;
0230 }
0231
0232 const auto res = load_nodes(topNode);
0233 if (res != Fun4AllReturnCodes::EVENT_OK)
0234 {
0235 return res;
0236 }
0237
0238 createHistos();
0239
0240
0241 auto* hm = QAHistManagerDef::getHistoManager();
0242 assert(hm);
0243
0244 for (int itile = 0; itile < 8; itile++)
0245 {
0246 h_ztrk_dz[itile] = dynamic_cast<TH2*>(hm->getHisto(std::format("{}ztrk_dz_{}", getHistoPrefix(), k_tile_names[itile])));
0247 }
0248 h_dz = dynamic_cast<TH1*>(hm->getHisto(std::format("{}dz", getHistoPrefix())));
0249 h_tile = dynamic_cast<TH1*>(hm->getHisto(std::format("{}tile", getHistoPrefix())));
0250 h_ylocal = dynamic_cast<TH1*>(hm->getHisto(std::format("{}ylocal", getHistoPrefix())));
0251 h_ntracks = dynamic_cast<TH1*>(hm->getHisto(std::format("{}ntracks", getHistoPrefix())));
0252 h_driftSummary = dynamic_cast<TH1*>(hm->getHisto(std::format("{}driftSummary", getHistoPrefix())));
0253
0254 return Fun4AllReturnCodes::EVENT_OK;
0255 }
0256
0257
0258 int MicromegasDriftQA::process_event(PHCompositeNode* topNode)
0259 {
0260 const auto res = load_nodes(topNode);
0261 if (res != Fun4AllReturnCodes::EVENT_OK)
0262 {
0263 return res;
0264 }
0265
0266 int nmatched = 0;
0267
0268 for (const auto& [track_id, track] : *m_track_map)
0269 {
0270
0271 const auto crossing = track->get_crossing();
0272 if (crossing == SHRT_MAX)
0273 {
0274 continue;
0275 }
0276
0277
0278 std::vector<Acts::Vector3> tpc_positions;
0279 for (const auto* seed : {track->get_silicon_seed(), track->get_tpc_seed()})
0280 {
0281 if (!seed)
0282 {
0283 continue;
0284 }
0285 for (auto it = seed->begin_cluster_keys(); it != seed->end_cluster_keys(); ++it)
0286 {
0287 const auto ckey = *it;
0288 if (TrkrDefs::getTrkrId(ckey) != TrkrDefs::tpcId)
0289 {
0290 continue;
0291 }
0292 const auto layer = TrkrDefs::getLayer(ckey);
0293 if (layer < m_min_tpc_layer || layer >= m_max_tpc_layer)
0294 {
0295 continue;
0296 }
0297 auto* cl = m_cluster_map->findCluster(ckey);
0298 if (cl)
0299 {
0300 tpc_positions.push_back(
0301 m_globalPositionWrapper.getGlobalPositionDistortionCorrected(ckey, cl, crossing));
0302 }
0303 }
0304 }
0305
0306
0307 if (tpc_positions.size() < 3)
0308 {
0309 continue;
0310 }
0311
0312
0313 const auto [slope_rz, intersect_rz] = TrackFitUtils::line_fit(tpc_positions);
0314 const auto [R, X0, Y0] = TrackFitUtils::circle_fit_by_taubin(tpc_positions);
0315
0316
0317 if (R < 40.0)
0318 {
0319 continue;
0320 }
0321
0322
0323 const auto mm_range = m_micromegas_geomcontainer->get_begin_end();
0324 for (const auto& [mm_layer, base_layergeom] : range_adaptor(mm_range))
0325 {
0326 const auto* layergeom = static_cast<const CylinderGeomMicromegas*>(base_layergeom);
0327 assert(layergeom);
0328
0329
0330 if (layergeom->get_segmentation_type() != MicromegasDefs::SegmentationType::SEGMENTATION_Z)
0331 {
0332 continue;
0333 }
0334
0335 const double layer_radius = layergeom->get_radius();
0336 auto [xplus, yplus, xminus, yminus] =
0337 TrackFitUtils::circle_circle_intersection(layer_radius, R, X0, Y0);
0338
0339 if (!std::isfinite(xplus))
0340 {
0341 continue;
0342 }
0343
0344
0345 const double last_phi = std::atan2(tpc_positions.back().y(), tpc_positions.back().x());
0346 const double phi_plus = std::atan2(yplus, xplus);
0347 const double phi_minus = std::atan2(yminus, xminus);
0348 const double phi = (std::abs(last_phi - phi_plus) < std::abs(last_phi - phi_minus)) ? phi_plus : phi_minus;
0349
0350 const double r_cyl = layer_radius;
0351 const double z_cyl = intersect_rz + slope_rz * r_cyl;
0352 const TVector3 world_cyl(r_cyl * std::cos(phi), r_cyl * std::sin(phi), z_cyl);
0353
0354 const int tileid = layergeom->find_tile_cylindrical(world_cyl);
0355 if (tileid < 0)
0356 {
0357 continue;
0358 }
0359
0360 const auto tile_center = layergeom->get_world_from_local_coords(tileid, m_tGeometry, {0, 0});
0361 const TVector3 ptile(tile_center.x(), tile_center.y(), tile_center.z());
0362
0363 const auto tile_norm = layergeom->get_world_from_local_vect(tileid, m_tGeometry, {0, 0, 1});
0364 const TVector3 ntile(tile_norm.x(), tile_norm.y(), tile_norm.z());
0365
0366 const auto phi_range = layergeom->get_phi_range(tileid, m_tGeometry);
0367 const double zmin = layergeom->get_zmin();
0368 const double zmax = layergeom->get_zmax();
0369
0370 TVector3 intersection;
0371 if (!helix_plane_intersection(phi_range.first, phi_range.second, zmin, zmax,
0372 R, X0, Y0, intersect_rz, slope_rz, ptile, ntile, intersection))
0373 {
0374 continue;
0375 }
0376
0377 const auto local_intersection = layergeom->get_local_from_world_coords(
0378 tileid, m_tGeometry, {intersection.x(), intersection.y(), intersection.z()});
0379 const double y_local = local_intersection.y();
0380
0381
0382 if (std::abs(y_local) > m_y_local_cut)
0383 {
0384 continue;
0385 }
0386
0387
0388 const auto hitsetkey = MicromegasDefs::genHitSetKey(mm_layer, MicromegasDefs::SegmentationType::SEGMENTATION_Z, tileid);
0389 const auto clusrange = m_cluster_map->getClusters(hitsetkey);
0390
0391 double dmin = -1;
0392 double z_cluster = 0;
0393 for (const auto& [ckey, cl] : range_adaptor(clusrange))
0394 {
0395 const double cl_y_local = cl->getLocalY();
0396 const double d = std::abs(y_local - cl_y_local);
0397 if (dmin < 0 || d < dmin)
0398 {
0399 dmin = d;
0400 const auto gpos = m_globalPositionWrapper.getGlobalPositionDistortionCorrected(ckey, cl, crossing);
0401 z_cluster = gpos.z();
0402 }
0403 }
0404
0405
0406 if (dmin < 0 || dmin > m_z_search_win)
0407 {
0408 continue;
0409 }
0410
0411
0412 const double z_track = intersection.z();
0413 const double dz = z_track - z_cluster;
0414
0415 h_ztrk_dz[tileid]->Fill(z_track, dz);
0416 h_dz->Fill(dz);
0417 h_tile->Fill(tileid);
0418 h_ylocal->Fill(y_local);
0419
0420 ++nmatched;
0421 break;
0422 }
0423 }
0424
0425 h_ntracks->Fill(nmatched);
0426
0427 return Fun4AllReturnCodes::EVENT_OK;
0428 }
0429
0430
0431 int MicromegasDriftQA::End(PHCompositeNode* )
0432 {
0433 if (!(h_ztrk_dz[0] && h_driftSummary))
0434 {
0435 std::cout << PHWHERE << " histograms not found, skipping drift velocity fit." << std::endl;
0436 return Fun4AllReturnCodes::EVENT_OK;
0437 }
0438
0439 int nEntries = 0;
0440 for (const auto* h : h_ztrk_dz)
0441 {
0442 nEntries += static_cast<int>(h->GetEntries());
0443 }
0444 if (Verbosity())
0445 {
0446 std::cout << Name() << "::End - fitting " << nEntries << " entries" << std::endl;
0447 }
0448
0449
0450 h_driftSummary->SetBinContent(3, m_drift_velocity);
0451
0452 if (nEntries < 8 * m_min_slice_entries)
0453 {
0454 std::cout << Name() << "::End - not enough entries (" << nEntries << "), skipping drift velocity fit." << std::endl;
0455 return Fun4AllReturnCodes::EVENT_OK;
0456 }
0457
0458
0459
0460 auto* h_fit = new TH2F("h_fit_micromegas", "", 8, 0, 8, k_nzbins, -k_max_z, k_max_z);
0461 h_fit->SetDirectory(nullptr);
0462
0463 for (int itile = 0; itile < 8; ++itile)
0464 {
0465 auto* h2d = h_ztrk_dz[itile];
0466
0467
0468 TObjArray slices;
0469 slices.SetOwner(kTRUE);
0470 h2d->FitSlicesY(nullptr, 0, -1, m_min_slice_entries, "QNR", &slices);
0471 auto* h_mean = dynamic_cast<TH1*>(slices.At(1));
0472 if (!h_mean)
0473 {
0474 continue;
0475 }
0476
0477 for (int iz = 1; iz <= h_mean->GetNbinsX(); ++iz)
0478 {
0479 const double entries = h2d->Integral(iz, iz, 1, h2d->GetNbinsY());
0480 if (entries > 0)
0481 {
0482 h_fit->SetBinContent(itile + 1, iz, h_mean->GetBinContent(iz));
0483 }
0484 }
0485 }
0486
0487
0488
0489
0490 auto* fit2d = new TF2("fit2d_micromegas", fit_function_2d, 0, 8, -k_max_z, k_max_z, 9);
0491 for (int i = 0; i < 9; ++i)
0492 {
0493 fit2d->SetParameter(i, 0.0);
0494 }
0495 h_fit->Fit(fit2d, "0RQ");
0496
0497 const double slope = fit2d->GetParameter(0);
0498 const double slope_err = fit2d->GetParError(0);
0499 const double new_drift = m_drift_velocity / (1.0 + slope);
0500 const double drift_err = m_drift_velocity / std::pow(1.0 + slope, 2) * slope_err;
0501
0502 std::cout << Name() << "::End"
0503 << " slope=" << slope
0504 << " input_drift=" << m_drift_velocity << " cm/ns"
0505 << " new_drift=" << new_drift << " cm/ns +/- " << drift_err << " cm/ns"
0506 << std::endl;
0507
0508
0509 h_driftSummary->SetBinContent(1, slope);
0510 h_driftSummary->SetBinContent(2, slope_err);
0511 h_driftSummary->SetBinContent(4, new_drift);
0512 h_driftSummary->SetBinContent(5, drift_err);
0513
0514 delete fit2d;
0515 delete h_fit;
0516
0517 return Fun4AllReturnCodes::EVENT_OK;
0518 }
0519
0520
0521 int MicromegasDriftQA::load_nodes(PHCompositeNode* topNode)
0522 {
0523 m_tGeometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
0524 if (!m_tGeometry)
0525 {
0526 std::cout << PHWHERE << " ActsGeometry node missing, abort." << std::endl;
0527 return Fun4AllReturnCodes::ABORTRUN;
0528 }
0529
0530 m_micromegas_geomcontainer = findNode::getClass<PHG4CylinderGeomContainer>(topNode, "CYLINDERGEOM_MICROMEGAS_FULL");
0531 if (!m_micromegas_geomcontainer)
0532 {
0533 std::cout << PHWHERE << " CYLINDERGEOM_MICROMEGAS_FULL node missing, abort." << std::endl;
0534 return Fun4AllReturnCodes::ABORTRUN;
0535 }
0536
0537 m_track_map = findNode::getClass<SvtxTrackMap>(topNode, m_trackmapname);
0538 if (!m_track_map)
0539 {
0540 std::cout << PHWHERE << " " << m_trackmapname << " node missing, abort." << std::endl;
0541 return Fun4AllReturnCodes::ABORTRUN;
0542 }
0543
0544 m_cluster_map = findNode::getClass<TrkrClusterContainer>(topNode, "TRKR_CLUSTER");
0545 if (!m_cluster_map)
0546 {
0547 std::cout << PHWHERE << " TRKR_CLUSTER node missing, abort." << std::endl;
0548 return Fun4AllReturnCodes::ABORTRUN;
0549 }
0550
0551 m_globalPositionWrapper.loadNodes(topNode);
0552
0553 return Fun4AllReturnCodes::EVENT_OK;
0554 }
0555
0556
0557 std::string MicromegasDriftQA::getHistoPrefix() const
0558 {
0559
0560 return std::string("h_") + Name() + std::string("_");
0561 }
0562
0563
0564 void MicromegasDriftQA::createHistos()
0565 {
0566
0567 auto* hm = QAHistManagerDef::getHistoManager();
0568 assert(hm);
0569
0570
0571 for (int itile = 0; itile < 8; itile++)
0572 {
0573 auto* h = new TH2F(std::format("{}ztrk_dz_{}", getHistoPrefix(), k_tile_names[itile]).c_str(),
0574 std::format("{};z_{{track}} (cm);#Deltaz (track#minuscluster) (cm)", k_tile_names[itile]).c_str(),
0575 k_nzbins, -k_max_z, k_max_z, 100, -k_max_dz, k_max_dz);
0576 hm->registerHisto(h);
0577 }
0578
0579 {
0580 auto* h = new TH1F(std::format("{}dz", getHistoPrefix()).c_str(),
0581 ";#Deltaz (track#minuscluster) (cm);track states", 100, -k_max_dz, k_max_dz);
0582 hm->registerHisto(h);
0583 }
0584
0585 {
0586 auto* h = new TH1F(std::format("{}tile", getHistoPrefix()).c_str(),
0587 ";tile;track states", 8, -0.5, 7.5);
0588 for (int itile = 0; itile < 8; itile++)
0589 {
0590 h->GetXaxis()->SetBinLabel(itile + 1, k_tile_names[itile]);
0591 }
0592 hm->registerHisto(h);
0593 }
0594
0595 {
0596 auto* h = new TH1F(std::format("{}ylocal", getHistoPrefix()).c_str(),
0597 ";y_{local} (cm);track states", 100, -30, 30);
0598 hm->registerHisto(h);
0599 }
0600
0601 {
0602 auto* h = new TH1F(std::format("{}ntracks", getHistoPrefix()).c_str(),
0603 ";matched track states per event;events", 20, -0.5, 19.5);
0604 hm->registerHisto(h);
0605 }
0606
0607 {
0608
0609 auto* h = new TH1F(std::format("{}driftSummary", getHistoPrefix()).c_str(),
0610 "drift velocity fit summary", 5, 0.5, 5.5);
0611 h->GetXaxis()->SetBinLabel(1, "slope");
0612 h->GetXaxis()->SetBinLabel(2, "slope_err");
0613 h->GetXaxis()->SetBinLabel(3, "v_{in} (cm/ns)");
0614 h->GetXaxis()->SetBinLabel(4, "v_{new} (cm/ns)");
0615 h->GetXaxis()->SetBinLabel(5, "v_{new} err (cm/ns)");
0616 hm->registerHisto(h);
0617 }
0618 }