Warning, file /EICrecon/src/algorithms/pid/IrtCherenkovParticleID.cc was not indexed
or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001
0002
0003
0004 #include "IrtCherenkovParticleID.h"
0005
0006 #include <IRT/ChargedParticle.h>
0007 #include <IRT/CherenkovPID.h>
0008 #include <IRT/OpticalPhoton.h>
0009 #include <IRT/RadiatorHistory.h>
0010 #include <IRT/SinglePDF.h>
0011 #include <TString.h>
0012 #include <TVector3.h>
0013 #include <algorithms/logger.h>
0014 #include <edm4eic/CherenkovParticleIDHypothesis.h>
0015 #include <edm4eic/TrackPoint.h>
0016 #include <edm4hep/EDM4hepVersion.h>
0017 #include <edm4hep/MCParticleCollection.h>
0018 #include <edm4hep/SimTrackerHitCollection.h>
0019 #include <edm4hep/Vector2f.h>
0020 #include <edm4hep/Vector3d.h>
0021 #include <edm4hep/Vector3f.h>
0022 #include <fmt/core.h>
0023 #include <fmt/format.h>
0024 #include <podio/ObjectID.h>
0025 #include <podio/RelationRange.h>
0026 #include <algorithm>
0027 #include <cmath>
0028 #include <cstddef>
0029 #include <functional>
0030 #include <iterator>
0031 #include <memory>
0032 #include <set>
0033 #include <stdexcept>
0034 #include <utility>
0035 #include <vector>
0036
0037 #include "algorithms/pid/IrtCherenkovParticleIDConfig.h"
0038 #include "algorithms/pid/Tools.h"
0039
0040 namespace eicrecon {
0041
0042 void IrtCherenkovParticleID::init(CherenkovDetectorCollection* irt_det_coll) {
0043
0044 m_irt_det_coll = irt_det_coll;
0045
0046
0047 debug() << m_cfg << endmsg;
0048
0049
0050 auto print_cheat = [this](auto name, bool val, auto desc) {
0051 if (val) {
0052 warning("CHEAT MODE '{}' ENABLED: {}", name, desc);
0053 }
0054 };
0055 print_cheat("cheatPhotonVertex", m_cfg.cheatPhotonVertex,
0056 "use MC photon vertex, wavelength, refractive index");
0057 print_cheat("cheatTrueRadiator", m_cfg.cheatTrueRadiator, "use MC truth to obtain true radiator");
0058
0059
0060 const auto& detectors = m_irt_det_coll->GetDetectors();
0061 if (detectors.empty()) {
0062 throw std::runtime_error("No CherenkovDetectors found in input collection `irt_det_coll`");
0063 }
0064 if (detectors.size() > 1) {
0065 warning("IrtCherenkovParticleID currently only supports 1 CherenkovDetector at a time; "
0066 "taking the first");
0067 }
0068 auto this_detector = *detectors.begin();
0069 m_det_name = this_detector.first;
0070 m_irt_det = this_detector.second;
0071 debug("Initializing IrtCherenkovParticleID algorithm for CherenkovDetector '{}'", m_det_name);
0072
0073
0074 m_cell_mask = m_irt_det->GetReadoutCellMask();
0075 debug("readout cellMask = {:#X}", m_cell_mask);
0076
0077
0078 trace("Rebinning refractive index tables to have {} bins", m_cfg.numRIndexBins);
0079 for (auto [rad_name, irt_rad] : m_irt_det->Radiators()) {
0080
0081 std::lock_guard<std::mutex> lock(m_irt_det_mutex);
0082 auto ri_lookup_table_orig = irt_rad->m_ri_lookup_table;
0083 if (ri_lookup_table_orig.size() != m_cfg.numRIndexBins) {
0084 irt_rad->m_ri_lookup_table.clear();
0085 irt_rad->m_ri_lookup_table =
0086 Tools::ApplyFineBinning(ri_lookup_table_orig, m_cfg.numRIndexBins);
0087 }
0088 }
0089
0090
0091 debug("Obtain List of Radiators:");
0092 for (auto [rad_name, irt_rad] : m_irt_det->Radiators()) {
0093 if (rad_name != "Filter") {
0094 m_pid_radiators.insert({std::string(rad_name), irt_rad});
0095 debug("- {}", rad_name.Data());
0096 }
0097 }
0098
0099
0100 for (auto [rad_name, irt_rad] : m_pid_radiators) {
0101 std::lock_guard<std::mutex> lock(m_irt_det_mutex);
0102
0103 auto cfg_rad_it = m_cfg.radiators.find(rad_name);
0104 if (cfg_rad_it != m_cfg.radiators.end()) {
0105 auto cfg_rad = cfg_rad_it->second;
0106
0107 irt_rad->m_ID = Tools::GetRadiatorID(std::string(rad_name));
0108 irt_rad->m_AverageRefractiveIndex = cfg_rad.referenceRIndex;
0109 irt_rad->SetReferenceRefractiveIndex(cfg_rad.referenceRIndex);
0110 if (cfg_rad.attenuation > 0) {
0111 irt_rad->SetReferenceAttenuationLength(cfg_rad.attenuation);
0112 }
0113 if (cfg_rad.smearing > 0) {
0114 if (cfg_rad.smearingMode == "uniform") {
0115 irt_rad->SetUniformSmearing(cfg_rad.smearing);
0116 } else if (cfg_rad.smearingMode == "gaussian") {
0117 irt_rad->SetGaussianSmearing(cfg_rad.smearing);
0118 } else {
0119 error("Unknown smearing mode '{}' for {} radiator", cfg_rad.smearingMode, rad_name);
0120 }
0121 }
0122 } else {
0123 error("Cannot find radiator '{}' in IrtCherenkovParticleIDConfig instance", rad_name);
0124 }
0125 }
0126
0127
0128 debug("List of particles for PID:");
0129 for (auto pdg : m_cfg.pdgList) {
0130 auto mass = m_particleSvc.particle(pdg).mass;
0131 m_pdg_mass.insert({pdg, mass});
0132 debug(" {:>8} M={} GeV", pdg, mass);
0133 }
0134 }
0135
0136 void IrtCherenkovParticleID::process(const IrtCherenkovParticleID::Input& input,
0137 const IrtCherenkovParticleID::Output& output) const {
0138 const auto [in_aerogel_tracks, in_gas_tracks, in_merged_tracks, in_raw_hits, in_hit_assocs] =
0139 input;
0140 auto [out_aerogel_particleIDs, out_gas_particleIDs] = output;
0141
0142
0143 trace("{:=^70}", " call IrtCherenkovParticleID::AlgorithmProcess ");
0144 trace("number of raw sensor hits: {}", in_raw_hits->size());
0145 trace("number of raw sensor hit with associated photons: {}", in_hit_assocs->size());
0146
0147 std::map<std::string, const edm4eic::TrackSegmentCollection*> in_charged_particles{
0148 {"Aerogel", in_aerogel_tracks},
0149 {"Gas", in_gas_tracks},
0150 {"Merged", in_merged_tracks},
0151 };
0152
0153
0154 std::map<std::string, edm4eic::CherenkovParticleIDCollection*> out_cherenkov_pids{
0155 {"Aerogel", out_aerogel_particleIDs}, {"Gas", out_gas_particleIDs}};
0156
0157
0158 std::unordered_map<std::size_t, std::size_t> in_charged_particle_size_distribution;
0159 for (const auto& [rad_name, in_charged_particle] : in_charged_particles) {
0160 ++in_charged_particle_size_distribution[in_charged_particle->size()];
0161 }
0162 if (in_charged_particle_size_distribution.size() != 1) {
0163 std::vector<std::size_t> in_charged_particle_sizes;
0164 std::ranges::transform(
0165 in_charged_particles, std::back_inserter(in_charged_particle_sizes),
0166 [](const auto& in_charged_particle) { return in_charged_particle.second->size(); });
0167 error("radiators have differing numbers of TrackSegments {}",
0168 fmt::join(in_charged_particle_sizes, ", "));
0169 return;
0170 }
0171
0172
0173 trace("{:#<70}", "### CHARGED PARTICLES ");
0174 std::size_t num_charged_particles = in_charged_particle_size_distribution.begin()->first;
0175 for (std::size_t i_charged_particle = 0; i_charged_particle < num_charged_particles;
0176 i_charged_particle++) {
0177 trace("{:-<70}", fmt::format("--- charged particle #{} ", i_charged_particle));
0178
0179
0180 auto irt_particle = std::make_unique<ChargedParticle>();
0181
0182
0183
0184
0185 std::lock_guard<std::mutex> lock(m_irt_det_mutex);
0186 for (auto [rad_name, irt_rad] : m_pid_radiators) {
0187
0188
0189 auto charged_particle_list_it = in_charged_particles.find(rad_name);
0190 if (charged_particle_list_it == in_charged_particles.end()) {
0191 error("Cannot find radiator '{}' in `in_charged_particles`", rad_name);
0192 continue;
0193 }
0194 const auto* charged_particle_list = charged_particle_list_it->second;
0195 auto charged_particle = charged_particle_list->at(i_charged_particle);
0196
0197
0198 if (charged_particle.points_size() == 0) {
0199 trace("No propagated track points in radiator '{}'", rad_name);
0200 continue;
0201 }
0202 irt_rad->SetTrajectoryBinCount(charged_particle.points_size() - 1);
0203
0204
0205
0206
0207 auto* irt_rad_history = new RadiatorHistory();
0208 irt_particle->StartRadiatorHistory({irt_rad, irt_rad_history});
0209
0210
0211 irt_rad->ResetLocations();
0212 trace("TrackPoints in '{}' radiator:", rad_name);
0213 for (const auto& point : charged_particle.getPoints()) {
0214 TVector3 position = Tools::PodioVector3_to_TVector3(point.position);
0215 TVector3 momentum = Tools::PodioVector3_to_TVector3(point.momentum);
0216 irt_rad->AddLocation(position, momentum);
0217 trace(Tools::PrintTVector3(" point: x", position));
0218 trace(Tools::PrintTVector3(" p", momentum));
0219 }
0220
0221
0222 trace("{:#<70}", "### SENSOR HITS ");
0223 for (const auto& raw_hit : *in_raw_hits) {
0224
0225
0226
0227
0228 edm4hep::MCParticle mc_photon;
0229 bool mc_photon_found = false;
0230 if (m_cfg.cheatPhotonVertex || m_cfg.cheatTrueRadiator) {
0231 for (const auto& hit_assoc : *in_hit_assocs) {
0232 if (hit_assoc.getRawHit().isAvailable()) {
0233 if (hit_assoc.getRawHit().id() == raw_hit.id()) {
0234 #if EDM4HEP_BUILD_VERSION >= EDM4HEP_VERSION(0, 99, 0)
0235 mc_photon = hit_assoc.getSimHit().getParticle();
0236 #else
0237 mc_photon = hit_assoc.getSimHit().getMCParticle();
0238 #endif
0239 mc_photon_found = true;
0240 if (mc_photon.getPDG() != -22) {
0241 warning("non-opticalphoton hit: PDG = {}", mc_photon.getPDG());
0242 }
0243 break;
0244 }
0245 }
0246 }
0247 }
0248
0249
0250 if (m_cfg.cheatTrueRadiator && mc_photon_found) {
0251 auto vtx = Tools::PodioVector3_to_TVector3(mc_photon.getVertex());
0252 auto* mc_rad = m_irt_det->GuessRadiator(vtx, vtx);
0253 if (mc_rad != irt_rad) {
0254 continue;
0255 }
0256 trace(Tools::PrintTVector3(
0257 fmt::format("cheat: radiator '{}' determined from photon vertex", rad_name), vtx));
0258 }
0259
0260
0261
0262 auto cell_id = raw_hit.getCellID();
0263 uint64_t sensor_id = cell_id & m_cell_mask;
0264 TVector3 pixel_pos = m_irt_det->m_ReadoutIDToPosition(cell_id);
0265
0266
0267 if (level() <= algorithms::LogLevel::kTrace) {
0268 trace("cell_id={:#X} sensor_id={:#X}", cell_id, sensor_id);
0269 trace(Tools::PrintTVector3("pixel position", pixel_pos));
0270 if (mc_photon_found) {
0271 TVector3 mc_endpoint = Tools::PodioVector3_to_TVector3(mc_photon.getEndpoint());
0272 trace(Tools::PrintTVector3("photon endpoint", mc_endpoint));
0273 trace("{:>30} = {}", "dist( pixel, photon )", (pixel_pos - mc_endpoint).Mag());
0274 } else {
0275 trace(" no MC photon found; probably a noise hit");
0276 }
0277 }
0278
0279
0280 auto* irt_sensor = m_irt_det->m_PhotonDetectors[0];
0281 auto* irt_photon =
0282 new OpticalPhoton();
0283 irt_photon->SetVolumeCopy(sensor_id);
0284 irt_photon->SetDetectionPosition(pixel_pos);
0285 irt_photon->SetPhotonDetector(irt_sensor);
0286 irt_photon->SetDetected(true);
0287
0288
0289 if ((m_cfg.cheatPhotonVertex || m_cfg.cheatTrueRadiator) && mc_photon_found) {
0290 irt_photon->SetVertexPosition(Tools::PodioVector3_to_TVector3(mc_photon.getVertex()));
0291 irt_photon->SetVertexMomentum(Tools::PodioVector3_to_TVector3(mc_photon.getMomentum()));
0292 }
0293
0294
0295
0296 if (m_cfg.cheatPhotonVertex) {
0297 double ri = NAN;
0298 auto mom = 1e9 * irt_photon->GetVertexMomentum().Mag();
0299 auto ri_set = Tools::GetFinelyBinnedTableEntry(irt_rad->m_ri_lookup_table, mom, &ri);
0300 if (ri_set) {
0301 irt_photon->SetVertexRefractiveIndex(ri);
0302 trace("{:>30} = {}", "refractive index", ri);
0303 } else {
0304 warning("Tools::GetFinelyBinnedTableEntry failed to lookup refractive index for "
0305 "momentum {} eV",
0306 mom);
0307 }
0308 }
0309
0310
0311
0312
0313
0314 irt_rad_history->AddOpticalPhoton(irt_photon);
0315
0316
0317
0318
0319
0320 }
0321
0322 }
0323
0324
0325
0326
0327 trace("{:+^70}", " PARTICLE IDENTIFICATION ");
0328 CherenkovPID irt_pid;
0329 std::unordered_map<int, MassHypothesis*> pdg_to_hyp;
0330 for (auto [pdg, mass] : m_pdg_mass) {
0331 irt_pid.AddMassHypothesis(mass);
0332 pdg_to_hyp.insert({pdg, irt_pid.GetHypothesis(irt_pid.GetHypothesesCount() - 1)});
0333 }
0334
0335
0336 irt_particle->PIDReconstruction(irt_pid);
0337 trace("{:-^70}", " IRT RESULTS ");
0338
0339
0340 for (auto [rad_name, irt_rad] : m_pid_radiators) {
0341 trace("-> {} Radiator (ID={}):", rad_name, irt_rad->m_ID);
0342
0343
0344 unsigned npe = 0;
0345 double rindex_ave = 0.0;
0346 double energy_ave = 0.0;
0347 std::vector<std::pair<double, double>> phot_theta_phi;
0348
0349
0350 auto* irt_rad_history = irt_particle->FindRadiatorHistory(irt_rad);
0351 if (irt_rad_history == nullptr) {
0352 trace(" No radiator history; skip");
0353 continue;
0354 }
0355 trace(" Photoelectrons:");
0356 for (auto* irt_photon : irt_rad_history->Photons()) {
0357
0358
0359 bool photon_selected = false;
0360 for (auto irt_photon_sel : irt_photon->_m_Selected) {
0361 if (irt_photon_sel.second == irt_rad) {
0362 photon_selected = true;
0363 break;
0364 }
0365 }
0366 if (!photon_selected) {
0367 continue;
0368 }
0369
0370
0371 trace(
0372 Tools::PrintTVector3(fmt::format("- sensor_id={:#X}: hit", irt_photon->GetVolumeCopy()),
0373 irt_photon->GetDetectionPosition()));
0374 trace(Tools::PrintTVector3("photon vertex", irt_photon->GetVertexPosition()));
0375
0376
0377 auto phot_theta = irt_photon->_m_PDF[irt_rad].GetAverage();
0378 auto phot_phi = irt_photon->m_Phi[irt_rad];
0379
0380
0381 npe++;
0382 phot_theta_phi.emplace_back(phot_theta, phot_phi);
0383 if (m_cfg.cheatPhotonVertex) {
0384 rindex_ave += irt_photon->GetVertexRefractiveIndex();
0385 energy_ave += irt_photon->GetVertexMomentum().Mag();
0386 }
0387
0388 }
0389
0390
0391 if (npe > 0) {
0392 rindex_ave /= npe;
0393 energy_ave /= npe;
0394 }
0395
0396
0397 auto out_cherenkov_pid = out_cherenkov_pids.at(rad_name)->create();
0398 out_cherenkov_pid.setNpe(static_cast<decltype(edm4eic::CherenkovParticleIDData::npe)>(npe));
0399 out_cherenkov_pid.setRefractiveIndex(
0400 static_cast<decltype(edm4eic::CherenkovParticleIDData::refractiveIndex)>(rindex_ave));
0401 out_cherenkov_pid.setPhotonEnergy(
0402 static_cast<decltype(edm4eic::CherenkovParticleIDData::photonEnergy)>(energy_ave));
0403 for (auto [phot_theta, phot_phi] : phot_theta_phi) {
0404 out_cherenkov_pid.addToThetaPhiPhotons(
0405 edm4hep::Vector2f{static_cast<float>(phot_theta), static_cast<float>(phot_phi)});
0406 }
0407
0408
0409 for (auto [pdg, mass] : m_pdg_mass) {
0410
0411
0412 auto* irt_hypothesis = pdg_to_hyp.at(pdg);
0413 auto hyp_weight = irt_hypothesis->GetWeight(irt_rad);
0414 auto hyp_npe = irt_hypothesis->GetNpe(irt_rad);
0415
0416
0417 edm4eic::CherenkovParticleIDHypothesis out_hypothesis;
0418 out_hypothesis.PDG =
0419 static_cast<decltype(edm4eic::CherenkovParticleIDHypothesis::PDG)>(pdg);
0420 out_hypothesis.weight =
0421 static_cast<decltype(edm4eic::CherenkovParticleIDHypothesis::weight)>(hyp_weight);
0422 out_hypothesis.npe =
0423 static_cast<decltype(edm4eic::CherenkovParticleIDHypothesis::npe)>(hyp_npe);
0424
0425
0426 out_cherenkov_pid.addToHypotheses(out_hypothesis);
0427
0428 }
0429
0430
0431 auto PrintCherenkovEstimate = [this](edm4eic::CherenkovParticleID pid,
0432 bool printHypotheses = true, int indent = 2) {
0433 double thetaAve = 0;
0434 if (pid.getNpe() > 0) {
0435 for (const auto& [theta, phi] : pid.getThetaPhiPhotons()) {
0436 thetaAve += theta / pid.getNpe();
0437 }
0438 }
0439 trace("{:{}}Cherenkov Angle Estimate:", "", indent);
0440 trace("{:{}} {:>16}: {:>10}", "", indent, "NPE", pid.getNpe());
0441 trace("{:{}} {:>16}: {:>10.8} mrad", "", indent, "<theta>",
0442 thetaAve * 1e3);
0443 trace("{:{}} {:>16}: {:>10.8}", "", indent, "<rindex>", pid.getRefractiveIndex());
0444 trace("{:{}} {:>16}: {:>10.8} eV", "", indent, "<energy>",
0445 pid.getPhotonEnergy() * 1e9);
0446 if (printHypotheses) {
0447 trace("{:{}}Mass Hypotheses:", "", indent);
0448 trace("{}", Tools::HypothesisTableHead(indent + 2));
0449 for (const auto& hyp : pid.getHypotheses()) {
0450 trace("{}", Tools::HypothesisTableLine(hyp, indent + 2));
0451 }
0452 }
0453 };
0454 PrintCherenkovEstimate(out_cherenkov_pid);
0455
0456
0457 auto charged_particle_list_it = in_charged_particles.find("Merged");
0458 if (charged_particle_list_it != in_charged_particles.end()) {
0459 const auto* charged_particle_list = charged_particle_list_it->second;
0460 auto charged_particle = charged_particle_list->at(i_charged_particle);
0461 out_cherenkov_pid.setChargedParticle(charged_particle);
0462 } else {
0463 error("Cannot find radiator 'Merged' in `in_charged_particles`");
0464 }
0465
0466
0467 for (const auto& hit_assoc : *in_hit_assocs) {
0468 out_cherenkov_pid.addToRawHitAssociations(hit_assoc);
0469 }
0470
0471 }
0472
0473
0474
0475
0476
0477
0478 }
0479 }
0480
0481 }