Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-16 08:16:58

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 - 2025 Whitney Armstrong, Wouter Deconinck, Sylvester Joosten, Dmitry Romanov, Yann Bedfer
0003 
0004 /*
0005   Digitization specific to MPGDs.
0006   - What's special in MPGDs is their 2D-strip readout: i.e. simultaneous
0007    registering along two sets of coordinate strips.
0008   - The "process" method involves a combination of segmentation, simulation and
0009    digitization.
0010   - The segmentation done when producing "sim_hits", and stored as a pixel
0011    cellID, is overwritten by strip cellIDs, via "dd4hep::MultiSegmentation".
0012   - The simulation will eventually involve simulating the amplitude and timing
0013    correlation of the two coordinates, and the spreading of the charge on
0014    adjacent strips producing multi-hit clusters.
0015     The preliminary version is rudimentary: single-hit clusters, with identical
0016    timing and uncorrelated amplitudes.
0017   - The digitization follows the standard steps, only that it needs to be
0018    convoluted with the simulation.
0019   NOTA BENE: The file could be simplified, see issue #1702.
0020  */
0021 
0022 #include "MPGDTrackerDigi.h"
0023 
0024 #include <DD4hep/Alignments.h>
0025 #include <DD4hep/DetElement.h>
0026 #include <DD4hep/Handle.h>
0027 #include <DD4hep/IDDescriptor.h>
0028 #include <DD4hep/Objects.h>
0029 #include <DD4hep/Readout.h>
0030 #include <DD4hep/VolumeManager.h>
0031 #include <DD4hep/detail/SegmentationsInterna.h>
0032 #include <DDSegmentation/BitFieldCoder.h>
0033 #include <Evaluator/DD4hepUnits.h>
0034 #include <JANA/JException.h>
0035 #include <Math/GenVector/Cartesian3D.h>
0036 #include <Math/GenVector/DisplacementVector3D.h>
0037 #include <Parsers/Primitives.h>
0038 // Access "algorithms:GeoSvc"
0039 #include <algorithms/geo.h>
0040 #include <algorithms/logger.h>
0041 #include <edm4hep/EDM4hepVersion.h>
0042 #include <edm4hep/MCParticleCollection.h>
0043 #include <edm4hep/Vector3d.h>
0044 #include <edm4hep/Vector3f.h>
0045 #include <fmt/core.h>
0046 #include <algorithm>
0047 #include <cmath>
0048 #include <cstdint>
0049 #include <gsl/pointers>
0050 #include <initializer_list>
0051 #include <iterator>
0052 #include <random>
0053 #include <unordered_map>
0054 #include <utility>
0055 #include <vector>
0056 
0057 #include "algorithms/digi/MPGDTrackerDigiConfig.h"
0058 
0059 using namespace dd4hep;
0060 
0061 namespace eicrecon {
0062 
0063 void MPGDTrackerDigi::init() {
0064   // Access id decoder
0065   m_detector                            = algorithms::GeoSvc::instance().detector();
0066   const dd4hep::BitFieldCoder* m_id_dec = nullptr;
0067   if (m_cfg.readout.empty()) {
0068     throw JException("Readout is empty");
0069   }
0070   try {
0071     m_seg    = m_detector->readout(m_cfg.readout).segmentation();
0072     m_id_dec = m_detector->readout(m_cfg.readout).idSpec().decoder();
0073   } catch (...) {
0074     critical("Failed to load ID decoder for \"{}\" readout.", m_cfg.readout);
0075     throw JException("Failed to load ID decoder");
0076   }
0077   // Method "process" relies on a strict assumption on the IDDescriptor:
0078   // - Must have a "strip" field.
0079   // - That "strip" field includes bits 30|31.
0080   // Let's check.
0081   if (m_id_dec->get(((CellID)0x3) << 30, "strip") != 0x3) {
0082     critical(R"(Missing or invalid "strip" field in IDDescriptor for "{}"
0083         readout.)",
0084              m_cfg.readout);
0085     throw JException("Invalid IDDescriptor");
0086   }
0087   debug(R"(Find valid "strip" field in IDDescriptor for "{}" readout.)", m_cfg.readout);
0088 }
0089 
0090 void MPGDTrackerDigi::process(const MPGDTrackerDigi::Input& input,
0091                               const MPGDTrackerDigi::Output& output) const {
0092 
0093   // ********** SIMULATE THE 2D-strip READOUT of MPGDs.
0094   // - Overwrite and extend segmentation stored in "sim_hit", which is anyway
0095   //  expected to be along a single coordinate (this happens to allow one to
0096   //  reconstruct data w/ a segmentation differing from that used when
0097   //  generating the data).
0098   // - New segmentation is along two coordinates, described by two cellID's
0099   //  with each a distinctive "strip" field.
0100   //   N.B.: Assumptions on the IDDescriptor: the "strip" specification
0101   //  is fixed = cellID>>32&0x3.
0102   // - The simulation is simplistic: single-hit cluster per coordinate.
0103 
0104   const auto [headers, sim_hits] = input;
0105   auto [raw_hits, associations]  = output;
0106 
0107   // local random generator
0108   auto seed = m_uid.getUniqueID(*headers, name());
0109   std::default_random_engine generator(seed);
0110   std::normal_distribution<double> gaussian;
0111 
0112   // A map of unique cellIDs with temporary structure RawHit
0113   std::unordered_map<std::uint64_t, edm4eic::MutableRawTrackerHit> cell_hit_map;
0114   // Prepare for strip segmentation
0115   const Position dummy(0, 0, 0);
0116   const VolumeManager& volman = m_detector->volumeManager();
0117 
0118   using CellIDs = std::pair<CellID, CellID>;
0119   using Sim2IDs = std::vector<CellIDs>;
0120   Sim2IDs sim2IDs;
0121   for (const edm4hep::SimTrackerHit& sim_hit : *sim_hits) {
0122 
0123     // ***** TIME SMEARING
0124     // - Simplistic treatment.
0125     // - A more realistic one would have to distinguish a smearing common to
0126     //  both coordinates of the 2D-strip readout (due to the drifting of the
0127     //  leading primary electrons) from other smearing effects, specific to
0128     //  each coordinate.
0129     double time_smearing = gaussian(generator) * m_cfg.timeResolution;
0130     double result_time   = sim_hit.getTime() + time_smearing;
0131     auto hit_time_stamp  = (std::int32_t)(result_time * 1e3);
0132 
0133     // ***** SEGMENTATION
0134     // - The two cellID's are encoded via a "dd4hep::MultiSegmentation"
0135     //  discriminating on the strip field, w/ "strip" setting of 0x1 (
0136     //  called 'p') and 0x2 (called 'n').
0137     // - They are evaluated based on "sim_hit" Cartesian coordinates
0138     //  positions
0139     //   Given that all the segmentation classes foreseen for MPGDs (
0140     //  "CartesianGrid.." for Outer and EndCaps, "CylindricalGridPhiZ" for
0141     //  "CyMBaL") disregard the _global_ position argument to
0142     //  "dd4hep::Segmentation::cellID", we need the _local_ position and
0143     //  only that.
0144     const edm4hep::Vector3d& pos = sim_hit.getPosition();
0145     using dd4hep::mm;
0146     Position gpos(pos.x * mm, pos.y * mm, pos.z * mm);
0147     CellID vID = // Note: Only the bits corresponding to the volumeID will
0148         // be used. The rest, encoding the segmentation stored in "sim_hit",
0149         // being disregared.
0150         sim_hit.getCellID();
0151     DetElement local = volman.lookupDetElement(vID);
0152     const auto lpos  = local.nominal().worldToLocal(gpos);
0153     // p "strip"
0154     CellID stripBitp = ((CellID)0x1) << 30;
0155     CellID vIDp      = vID | stripBitp;
0156     CellID cIDp      = m_seg->cellID(lpos, dummy, vIDp);
0157     // n "strip"
0158     CellID stripBitn = ((CellID)0x2) << 30;
0159     CellID vIDn      = vID | stripBitn;
0160     CellID cIDn      = m_seg->cellID(lpos, dummy, vIDn);
0161 
0162     sim2IDs.emplace_back(cIDp, cIDn); // Remember cellIDs.
0163     // ***** DEBUGGING INFO
0164     if (level() >= algorithms::LogLevel::kDebug) {
0165       CellID hIDp = cIDp >> 32;
0166       CellID sIDp = cIDp >> 30 & 0x3;
0167       debug("--------------------");
0168       debug("Hit cellIDp  = 0x{:08x}, 0x{:08x} 0x{:02x}", hIDp, vIDp, sIDp);
0169       CellID hIDn = cIDn >> 32;
0170       CellID sIDn = cIDn >> 30 & 0x3;
0171       debug("Hit cellIDn  = 0x{:08x}, 0x{:08x} 0x{:02x}", hIDn, vIDn, sIDn);
0172       debug("   position  = ({:.2f}, {:.2f}, {:.2f})", sim_hit.getPosition().x,
0173             sim_hit.getPosition().y, sim_hit.getPosition().z);
0174       debug("   xy_radius = {:.2f}", std::hypot(sim_hit.getPosition().x, sim_hit.getPosition().y));
0175       debug("   momentum  = ({:.2f}, {:.2f}, {:.2f})", sim_hit.getMomentum().x,
0176             sim_hit.getMomentum().y, sim_hit.getMomentum().z);
0177       debug("   edep = {:.2f}", sim_hit.getEDep());
0178       debug("   time = {:.4f}[ns]", sim_hit.getTime());
0179 #if EDM4HEP_BUILD_VERSION >= EDM4HEP_VERSION(0, 99, 0)
0180       debug("   particle time = {}[ns]", sim_hit.getParticle().getTime());
0181 #else
0182       debug("   particle time = {}[ns]", sim_hit.getMCParticle().getTime());
0183 #endif
0184       debug("   time smearing: {:.4f}, resulting time = {:.4f} [ns]", time_smearing, result_time);
0185       debug("   hit_time_stamp: {} [~ps]", hit_time_stamp);
0186     }
0187 
0188     // ***** APPLY THRESHOLD
0189     if (sim_hit.getEDep() < m_cfg.threshold) {
0190       debug("  edep is below threshold of {:.2f} [keV]", m_cfg.threshold / keV);
0191       continue;
0192     }
0193 
0194     // ***** HIT ACCUMULATION
0195     for (CellID cID : {cIDp, cIDn}) {
0196       if (!cell_hit_map.contains(cID)) {
0197         // This cell doesn't have hits
0198         cell_hit_map[cID] = {
0199             cID, (std::int32_t)std::llround(sim_hit.getEDep() * 1e6),
0200             hit_time_stamp // ns->ps
0201         };
0202       } else {
0203         // There is previous values in the cell
0204         auto& hit = cell_hit_map[cID];
0205         debug("  Hit already exists in cell ID={}, prev. hit time: {}", cID, hit.getTimeStamp());
0206 
0207         // keep earliest time for hit
0208         hit.setTimeStamp(std::min(hit_time_stamp, hit.getTimeStamp()));
0209 
0210         // sum deposited energy
0211         auto charge = hit.getCharge();
0212         hit.setCharge(charge + (std::int32_t)std::llround(sim_hit.getEDep() * 1e6));
0213       }
0214     }
0215   }
0216 
0217   // ***** raw_hit INSTANTIATION AND raw<-sim_hit's ASSOCIATION
0218   for (auto item : cell_hit_map) {
0219     raw_hits->push_back(item.second);
0220     auto sim_it = sim2IDs.cbegin();
0221     for (const auto& sim_hit : *sim_hits) {
0222       CellIDs cIDs = *sim_it++;
0223       for (CellID cID : {cIDs.first, cIDs.second}) {
0224         if (item.first == cID) {
0225           // set association
0226           auto hitassoc = associations->create();
0227           hitassoc.setWeight(1.0);
0228           hitassoc.setRawHit(item.second);
0229           hitassoc.setSimHit(sim_hit);
0230         }
0231       }
0232     }
0233   }
0234 }
0235 
0236 } // namespace eicrecon