Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-12-17 09:22:07

0001 /*!
0002  * \file PHG4MicromegasHitReco.cc
0003  * \author Hugo Pereira Da Costa <hugo.pereira-da-costa@cea.fr>
0004  */
0005 
0006 #include "PHG4MicromegasHitReco.h"
0007 
0008 #include <fun4all/Fun4AllReturnCodes.h>
0009 #include <fun4all/SubsysReco.h>  // for SubsysReco
0010 
0011 #include <g4detectors/PHG4CylinderGeom.h>
0012 #include <g4detectors/PHG4CylinderGeomContainer.h>
0013 
0014 #include <g4main/PHG4Hit.h>
0015 #include <g4main/PHG4HitContainer.h>
0016 #include <g4main/PHG4Hitv1.h>
0017 
0018 #include <micromegas/CylinderGeomMicromegas.h>
0019 #include <micromegas/MicromegasDefs.h>
0020 
0021 #include <phool/PHCompositeNode.h>
0022 #include <phool/PHIODataNode.h>
0023 #include <phool/PHNode.h>
0024 #include <phool/PHNodeIterator.h>
0025 #include <phool/PHObject.h>  // for PHObject
0026 #include <phool/PHRandomSeed.h>
0027 #include <phool/getClass.h>
0028 #include <phool/phool.h>
0029 
0030 #include <phparameter/PHParameterInterface.h>  // for PHParameterInterface
0031 
0032 #include <trackbase/ActsGeometry.h>
0033 #include <trackbase/TrkrDefs.h>
0034 #include <trackbase/TrkrHitSet.h>
0035 #include <trackbase/TrkrHitSetContainerv1.h>
0036 #include <trackbase/TrkrHitTruthAssocv1.h>
0037 #include <trackbase/TrkrHitv2.h>
0038 
0039 #include <TVector2.h>
0040 #include <TVector3.h>
0041 
0042 #include <gsl/gsl_randist.h>
0043 #include <gsl/gsl_rng.h>  // for gsl_rng_alloc
0044 
0045 #include <cassert>
0046 #include <cmath>     // for atan2, sqrt, M_PI
0047 #include <cstdlib>   // for exit
0048 #include <iostream>  // for operator<<, basic...
0049 #include <map>       // for _Rb_tree_const_it...
0050 #include <numeric>
0051 
0052 namespace
0053 {
0054   //! convenient square function
0055   template <class T>
0056   constexpr T square(const T& x)
0057   {
0058     return x * x;
0059   }
0060 
0061   //! return normalized gaussian centered on zero and of width sigma
0062   template <class T>
0063   inline T gaus(const T& x, const T& sigma)
0064   {
0065     return std::exp(-square(x / sigma) / 2) / (sigma * std::sqrt(2 * M_PI));
0066   }
0067 
0068   //! bind angle to [-M_PI,+M_PI[. This is useful to avoid edge effects when making the difference between two angles
0069   template <class T>
0070   inline T bind_angle(const T& angle)
0071   {
0072     if (angle >= M_PI)
0073     {
0074       return angle - 2 * M_PI;
0075     }
0076     if (angle < -M_PI)
0077     {
0078       return angle + 2 * M_PI;
0079     }
0080 
0081     return angle;
0082   }
0083 
0084   // this corresponds to integrating a gaussian centered on zero and of width sigma from xloc - pitch/2 to xloc+pitch/2
0085   template <class T>
0086   inline T get_rectangular_fraction(const T& xloc, const T& sigma, const T& pitch)
0087   {
0088     return (std::erf((xloc + pitch / 2) / (M_SQRT2 * sigma)) - std::erf((xloc - pitch / 2) / (M_SQRT2 * sigma))) / 2;
0089   }
0090 
0091   /*
0092   this corresponds to integrating a gaussian centered on zero and of width sigma
0093   convoluted with a zig-zag strip response function, which is triangular from xloc-pitch to xloc+pitch, with a maximum of 1 at xloc
0094   */
0095   template <class T>
0096   inline T get_zigzag_fraction(const T& xloc, const T& sigma, const T& pitch)
0097   {
0098     return
0099         // rising edge
0100         (pitch - xloc) * (std::erf(xloc / (M_SQRT2 * sigma)) - std::erf((xloc - pitch) / (M_SQRT2 * sigma))) / (pitch * 2) + (gaus(xloc - pitch, sigma) - gaus(xloc, sigma)) * square(sigma) / pitch
0101 
0102         // descending edge
0103         + (pitch + xloc) * (std::erf((xloc + pitch) / (M_SQRT2 * sigma)) - std::erf(xloc / (M_SQRT2 * sigma))) / (pitch * 2) + (gaus(xloc + pitch, sigma) - gaus(xloc, sigma)) * square(sigma) / pitch;
0104   }
0105 
0106   // TVector3 streamer
0107   [[maybe_unused]] inline std::ostream& operator<<(std::ostream& out, const TVector3& position)
0108   {
0109     out << "(" << position.x() << ", " << position.y() << ", " << position.z() << ")";
0110     return out;
0111   }
0112 
0113 }  // namespace
0114 
0115 //___________________________________________________________________________
0116 PHG4MicromegasHitReco::PHG4MicromegasHitReco(const std::string& name)
0117   : SubsysReco(name)
0118   , PHParameterInterface(name)
0119 {
0120   // initialize rng
0121   const uint seed = PHRandomSeed();
0122   m_rng.reset(gsl_rng_alloc(gsl_rng_mt19937));
0123   gsl_rng_set(m_rng.get(), seed);
0124 
0125   InitializeParameters();
0126 }
0127 
0128 //___________________________________________________________________________
0129 int PHG4MicromegasHitReco::InitRun(PHCompositeNode* topNode)
0130 {
0131   UpdateParametersWithMacro();
0132 
0133   // load parameters
0134   m_tmin = get_double_param("micromegas_tmin");
0135   m_tmax = get_double_param("micromegas_tmax");
0136   m_electrons_per_gev = get_double_param("micromegas_electrons_per_gev");
0137   m_gain = get_double_param("micromegas_gain");
0138   m_cloud_sigma = get_double_param("micromegas_cloud_sigma");
0139   m_diffusion_trans = get_double_param("micromegas_diffusion_trans");
0140   m_added_smear_sigma_z = get_double_param("micromegas_added_smear_sigma_z");
0141   m_added_smear_sigma_rphi = get_double_param("micromegas_added_smear_sigma_rphi");
0142 
0143   // printout
0144   std::cout
0145       << "PHG4MicromegasHitReco::InitRun\n"
0146       << " m_tmin: " << m_tmin << "ns, m_tmax: " << m_tmax << "ns\n"
0147       << " m_electrons_per_gev: " << m_electrons_per_gev << "\n"
0148       << " m_gain: " << m_gain << "\n"
0149       << " m_cloud_sigma: " << m_cloud_sigma << "cm\n"
0150       << " m_diffusion_trans: " << m_diffusion_trans << "cm/sqrt(cm)\n"
0151       << " m_added_smear_sigma_z: " << m_added_smear_sigma_z << "cm\n"
0152       << " m_added_smear_sigma_rphi: " << m_added_smear_sigma_rphi << "cm\n"
0153       << std::endl;
0154 
0155   // get dst node
0156   PHNodeIterator iter(topNode);
0157   auto* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0158   if (!dstNode)
0159   {
0160     std::cout << "PHG4MicromegasHitReco::InitRun - DST Node missing, doing nothing." << std::endl;
0161     exit(1);
0162   }
0163 
0164   // create hitset container if needed
0165   auto* hitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0166   if (!hitsetcontainer)
0167   {
0168     std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITSET." << std::endl;
0169 
0170     // find or create TRKR node
0171     PHNodeIterator dstiter(dstNode);
0172     auto* trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
0173     if (!trkrnode)
0174     {
0175       trkrnode = new PHCompositeNode("TRKR");
0176       dstNode->addNode(trkrnode);
0177     }
0178 
0179     // create container and add to the tree
0180     hitsetcontainer = new TrkrHitSetContainerv1;
0181     auto* newNode = new PHIODataNode<PHObject>(hitsetcontainer, "TRKR_HITSET", "PHObject");
0182     trkrnode->addNode(newNode);
0183   }
0184 
0185   // create hit truth association if needed
0186   auto* hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
0187   if (!hittruthassoc)
0188   {
0189     std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITTRUTHASSOC." << std::endl;
0190 
0191     // find or create TRKR node
0192     PHNodeIterator dstiter(dstNode);
0193     auto* trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
0194     if (!trkrnode)
0195     {
0196       trkrnode = new PHCompositeNode("TRKR");
0197       dstNode->addNode(trkrnode);
0198     }
0199 
0200     hittruthassoc = new TrkrHitTruthAssocv1;
0201     auto* newNode = new PHIODataNode<PHObject>(hittruthassoc, "TRKR_HITTRUTHASSOC", "PHObject");
0202     trkrnode->addNode(newNode);
0203   }
0204 
0205   return Fun4AllReturnCodes::EVENT_OK;
0206 }
0207 
0208 //___________________________________________________________________________
0209 int PHG4MicromegasHitReco::process_event(PHCompositeNode* topNode)
0210 {
0211   // load relevant nodes
0212   // G4Hits
0213   auto* g4hitcontainer = findNode::getClass<PHG4HitContainer>(topNode, "G4HIT_MICROMEGAS");
0214   assert(g4hitcontainer);
0215 
0216   // acts geometry
0217   m_acts_geometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
0218   assert(m_acts_geometry);
0219 
0220   // geometry
0221   const auto geonodename = full_geonodename();
0222   auto* geonode = findNode::getClass<PHG4CylinderGeomContainer>(topNode, geonodename);
0223   assert(geonode);
0224 
0225   // Get the TrkrHitSetContainer node
0226   auto* trkrhitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0227   assert(trkrhitsetcontainer);
0228 
0229   // Get the TrkrHitTruthAssoc node
0230   auto* hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
0231   assert(hittruthassoc);
0232 
0233   // loop over layers in the g4hit container
0234   auto layer_range = g4hitcontainer->getLayers();
0235   for (auto layer_it = layer_range.first; layer_it != layer_range.second; ++layer_it)
0236   {
0237     // get layer
0238     const auto layer = *layer_it;
0239 
0240     // get relevant geometry
0241     auto* layergeom = dynamic_cast<CylinderGeomMicromegas*>(geonode->GetLayerGeom(layer));
0242     assert(layergeom);
0243 
0244     /*
0245      * get the position of the detector mesh in local coordinates
0246      * in local coordinate the mesh is a plane perpendicular to the z axis
0247      * Its position along z depends on the drift direction
0248      * it is used to calculate the drift distance of the primary electrons, and the
0249      * corresponding transverse diffusion
0250      */
0251     const auto mesh_local_z = layergeom->get_drift_direction() == MicromegasDefs::DriftDirection::OUTWARD ? layergeom->get_thickness() / 2 : -layergeom->get_thickness() / 2;
0252 
0253     //     /*
0254     //      * get the radius of the detector mesh. It depends on the drift direction
0255     //      * it is used to calculate the drift distance of the primary electrons, and the
0256     //      * corresponding transverse diffusion
0257     //      */
0258     //       const auto mesh_radius = layergeom->get_drift_direction() == MicromegasDefs::DriftDirection::OUTWARD ?
0259     //       (layergeom->get_radius() + layergeom->get_thickness()/2):
0260     //       (layergeom->get_radius() - layergeom->get_thickness()/2);
0261 
0262     // get hits
0263     const PHG4HitContainer::ConstRange g4hit_range = g4hitcontainer->getHits(layer);
0264 
0265     // loop over hits
0266     for (auto g4hit_it = g4hit_range.first; g4hit_it != g4hit_range.second; ++g4hit_it)
0267     {
0268       // get hit
0269       PHG4Hit* g4hit = g4hit_it->second;
0270 
0271       // check time window
0272       if (g4hit->get_t(0) > m_tmax)
0273       {
0274         continue;
0275       }
0276       if (g4hit->get_t(1) < m_tmin)
0277       {
0278         continue;
0279       }
0280 
0281       // get world coordinates
0282       TVector3 world_in(g4hit->get_x(0), g4hit->get_y(0), g4hit->get_z(0));
0283       TVector3 world_out(g4hit->get_x(1), g4hit->get_y(1), g4hit->get_z(1));
0284 
0285       // get tile id from g4hit
0286       const int tileid = g4hit->get_property_int(PHG4Hit::prop_index_i);
0287 
0288       // convert to local coordinate
0289       const auto local_in = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_in);
0290       const auto local_out = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_out);
0291 
0292       // number of primary elections
0293       const auto nprimary = get_primary_electrons(g4hit);
0294       if (!nprimary)
0295       {
0296         continue;
0297       }
0298 
0299       // create hitset
0300       const TrkrDefs::hitsetkey hitsetkey = MicromegasDefs::genHitSetKey(layer, layergeom->get_segmentation_type(), tileid);
0301       const auto hitset_it = trkrhitsetcontainer->findOrAddHitSet(hitsetkey);
0302 
0303       // keep track of all charges
0304       using charge_map_t = std::map<int, double>;
0305       charge_map_t total_charges;
0306 
0307       // loop over primaries
0308       for (uint ie = 0; ie < nprimary; ++ie)
0309       {
0310         // put the electron at a random position along the g4hit path
0311         // in local reference frame, drift occurs along the y axis, from local y to mesh_local_z
0312 
0313         const auto t = gsl_ran_flat(m_rng.get(), 0.0, 1.0);
0314         auto local = local_in * t + local_out * (1.0 - t);
0315 
0316         if (m_diffusion_trans > 0)
0317         {
0318           // add transeverse diffusion
0319           // first convert to polar coordinates
0320           const double z = local.z();
0321           const double drift_distance = std::abs(z - mesh_local_z);
0322           const double diffusion = gsl_ran_gaussian(m_rng.get(), m_diffusion_trans * std::sqrt(drift_distance));
0323           const double diffusion_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
0324 
0325           // diffusion occurs in x,z plane with a magnitude 'diffusion' and an angle 'diffusion angle'
0326           local += TVector3(diffusion * std::cos(diffusion_angle), diffusion * std::sin(diffusion_angle), 0);
0327         }
0328 
0329         const auto& added_smear_sigma = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? m_added_smear_sigma_rphi : m_added_smear_sigma_z;
0330 
0331         if (added_smear_sigma > 0)
0332         {
0333           // additional ad hoc smearing
0334           const double added_smear_trans = gsl_ran_gaussian(m_rng.get(), added_smear_sigma);
0335           const double added_smear_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
0336           local += TVector3(added_smear_trans * std::cos(added_smear_angle), added_smear_trans * std::sin(added_smear_angle), 0);
0337         }
0338 
0339         // distribute charge among adjacent strips
0340         const auto fractions = distribute_charge(layergeom, tileid, {local.x(), local.y()}, m_cloud_sigma);
0341 
0342         // make sure fractions adds up to unity
0343         if (Verbosity() > 10)
0344         {
0345           const auto sum = std::accumulate(fractions.begin(), fractions.end(), double(0),
0346                                            [](double value, const charge_pair_t& pair)
0347                                            { return value + pair.second; });
0348           std::cout << "PHG4MicromegasHitReco::process_event - sum: " << sum << std::endl;
0349         }
0350 
0351         // generate gain for this electron
0352         const auto gain = get_single_electron_amplification();
0353 
0354         // merge to total charges
0355         for (const auto& pair : fractions)
0356         {
0357           const int strip = pair.first;
0358           if (strip < 0 || strip >= (int) layergeom->get_strip_count(tileid, m_acts_geometry))
0359           {
0360             continue;
0361           }
0362 
0363           const auto it = total_charges.lower_bound(strip);
0364           if (it != total_charges.end() && it->first == strip)
0365           {
0366             it->second += pair.second * gain;
0367           }
0368           else
0369           {
0370             total_charges.insert(it, std::make_pair(strip, pair.second * gain));
0371           }
0372         }
0373       }
0374 
0375       // generate the key for this hit
0376       // loop over strips in list
0377       for (const auto pair : total_charges)
0378       {
0379         // get strip and bound check
0380         const int strip = pair.first;
0381 
0382         // get hit from hitset
0383         TrkrDefs::hitkey hitkey = MicromegasDefs::genHitKey(strip);
0384         auto* hit = hitset_it->second->getHit(hitkey);
0385         if (!hit)
0386         {
0387           // create hit and insert in hitset
0388           hit = new TrkrHitv2;
0389           hitset_it->second->addHitSpecificKey(hitkey, hit);
0390         }
0391 
0392         // add energy from g4hit
0393         hit->addEnergy(pair.second);
0394 
0395         // associate this hitset and hit to the geant4 hit key
0396         hittruthassoc->addAssoc(hitsetkey, hitkey, g4hit_it->first);
0397       }
0398     }
0399   }
0400 
0401   return Fun4AllReturnCodes::EVENT_OK;
0402 }
0403 
0404 //___________________________________________________________________________
0405 void PHG4MicromegasHitReco::SetDefaultParameters()
0406 {
0407   // default timing window (ns)
0408   /*
0409    * see https://indico.bnl.gov/event/8548/contributions/37753/attachments/28212/43343/2020_05_Proposal_sPhenixMonitoring_update_19052020.pptx slide 10
0410    * small negative time for tmin is set to catch out of time, same-bunch pileup events
0411    * similar value is used in PHG4InttReco
0412    */
0413   set_default_double_param("micromegas_tmin", -20);
0414   set_default_double_param("micromegas_tmax", 800);
0415 
0416   // gas data from
0417   // http://www.slac.stanford.edu/pubs/icfa/summer98/paper3/paper3.pdf
0418   // assume Ar/iC4H10 90/10, at 20C and 1atm
0419   // dedx (KeV/cm) for MIP
0420   static constexpr double Ar_dEdx = 2.44;
0421   static constexpr double iC4H10_dEdx = 5.93;
0422   static constexpr double mix_dEdx = 0.9 * Ar_dEdx + 0.1 * iC4H10_dEdx;
0423 
0424   // number of electrons per MIP (cm-1)
0425   static constexpr double Ar_ntot = 94;
0426   static constexpr double iC4H10_ntot = 195;
0427   static constexpr double mix_ntot = 0.9 * Ar_ntot + 0.1 * iC4H10_ntot;
0428 
0429   // number of electrons per gev
0430   static constexpr double mix_electrons_per_gev = 1e6 * mix_ntot / mix_dEdx;
0431   set_default_double_param("micromegas_electrons_per_gev", mix_electrons_per_gev);
0432 
0433   // gain
0434   set_default_double_param("micromegas_gain", 2000);
0435 
0436   // electron cloud sigma, after avalanche (cm)
0437   set_default_double_param("micromegas_cloud_sigma", 0.04);
0438 
0439   // transverse diffusion (cm/sqrt(cm))
0440   set_default_double_param("micromegas_diffusion_trans", 0.03);
0441 
0442   // additional smearing (cm)
0443   set_default_double_param("micromegas_added_smear_sigma_z", 0);
0444   set_default_double_param("micromegas_added_smear_sigma_rphi", 0);
0445 }
0446 
0447 //___________________________________________________________________________
0448 uint PHG4MicromegasHitReco::get_primary_electrons(PHG4Hit* g4hit) const
0449 {
0450   return gsl_ran_poisson(m_rng.get(), g4hit->get_eion() * m_electrons_per_gev);
0451 }
0452 
0453 //___________________________________________________________________________
0454 uint PHG4MicromegasHitReco::get_single_electron_amplification() const
0455 {
0456   /*
0457    * to handle gain fluctuations, an exponential distribution is used, similar to what used for the GEMS
0458    * (simulations/g4simulations/g4tpc/PHG4TpcPadPlaneReadout::getSingleEGEMAmplification)
0459    * One must get a different random number for each primary electron for this to be valid
0460    */
0461   return gsl_ran_exponential(m_rng.get(), m_gain);
0462 }
0463 
0464 //___________________________________________________________________________
0465 PHG4MicromegasHitReco::charge_list_t PHG4MicromegasHitReco::distribute_charge(
0466     CylinderGeomMicromegas* layergeom,
0467     uint tileid,
0468     const TVector2& local_coords,
0469     double sigma) const
0470 {
0471   // find tile and strip matching center position
0472   auto stripnum = layergeom->find_strip_from_local_coords(tileid, m_acts_geometry, local_coords);
0473 
0474   // check tile and strip
0475   if (stripnum < 0)
0476   {
0477     return charge_list_t();
0478   }
0479 
0480   // store pitch and radius
0481   const auto pitch = layergeom->get_pitch();
0482 
0483   // find relevant strip indices
0484   const auto strip_count = layergeom->get_strip_count(tileid, m_acts_geometry);
0485   const int nstrips = 5. * sigma / pitch + 1;
0486   const auto stripnum_min = std::clamp<int>(stripnum - nstrips, 0, strip_count);
0487   const auto stripnum_max = std::clamp<int>(stripnum + nstrips, 0, strip_count);
0488 
0489   // prepare charge list
0490   charge_list_t charge_list;
0491 
0492   // loop over strips
0493   for (int strip = stripnum_min; strip <= stripnum_max; ++strip)
0494   {
0495     // get strip center
0496     const auto strip_location = layergeom->get_local_coordinates(tileid, m_acts_geometry, strip);
0497 
0498     /*
0499      * find relevant strip coordinate with respect to location
0500      * in local coordinate, phi segmented view has strips along z and measures along x
0501      * in local coordinate, z segmented view has strips along phi and measures along y
0502      */
0503     const auto xloc = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? (strip_location.X() - local_coords.X()) : (strip_location.Y() - local_coords.Y());
0504 
0505     // decide of whether zigzag or straight strips are used depending on segmentation type
0506     /*
0507      * for the real detector SEGMENTATION_Z view has zigzag strip due to large pitch (2mm)
0508      * whereas SEGMENTATION_PHI has straight strips
0509      */
0510     const bool zigzag_strips = (layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_Z);
0511 
0512     // calculate charge fraction
0513     const auto fraction = zigzag_strips ? get_zigzag_fraction(xloc, sigma, pitch) : get_rectangular_fraction(xloc, sigma, pitch);
0514 
0515     // store
0516     charge_list.emplace_back(strip, fraction);
0517   }
0518 
0519   return charge_list;
0520 }