Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PHG4MicromegasHitReco.cc
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file PHG4MicromegasHitReco.cc
1 
7 
9 #include <fun4all/SubsysReco.h> // for SubsysReco
10 
13 
14 #include <g4main/PHG4Hit.h>
16 #include <g4main/PHG4Hitv1.h>
17 
20 
21 #include <phool/PHCompositeNode.h>
22 #include <phool/PHIODataNode.h>
23 #include <phool/PHNode.h>
24 #include <phool/PHNodeIterator.h>
25 #include <phool/PHObject.h> // for PHObject
26 #include <phool/PHRandomSeed.h>
27 #include <phool/getClass.h>
28 #include <phool/phool.h>
29 
30 #include <phparameter/PHParameterInterface.h> // for PHParameterInterface
31 
32 #include <trackbase/ActsGeometry.h>
33 #include <trackbase/TrkrDefs.h>
34 #include <trackbase/TrkrHitSet.h>
37 #include <trackbase/TrkrHitv2.h>
38 
39 #include <TVector2.h>
40 #include <TVector3.h>
41 
42 #include <gsl/gsl_randist.h>
43 #include <gsl/gsl_rng.h> // for gsl_rng_alloc
44 
45 #include <cassert>
46 #include <cmath> // for atan2, sqrt, M_PI
47 #include <cstdlib> // for exit
48 #include <iostream> // for operator<<, basic...
49 #include <map> // for _Rb_tree_const_it...
50 #include <numeric>
51 
52 namespace
53 {
55  template <class T>
56  inline constexpr T square(const T& x)
57  {
58  return x * x;
59  }
60 
62  template <class T>
63  inline T gaus(const T& x, const T& sigma)
64  {
65  return std::exp(-square(x / sigma) / 2) / (sigma * std::sqrt(2 * M_PI));
66  }
67 
69  template <class T>
70  inline T bind_angle(const T& angle)
71  {
72  if (angle >= M_PI)
73  {
74  return angle - 2 * M_PI;
75  }
76  else if (angle < -M_PI)
77  {
78  return angle + 2 * M_PI;
79  }
80  else
81  {
82  return angle;
83  }
84  }
85 
86  // this corresponds to integrating a gaussian centered on zero and of width sigma from xloc - pitch/2 to xloc+pitch/2
87  template <class T>
88  inline T get_rectangular_fraction(const T& xloc, const T& sigma, const T& pitch)
89  {
90  return (std::erf((xloc + pitch / 2) / (M_SQRT2 * sigma)) - std::erf((xloc - pitch / 2) / (M_SQRT2 * sigma))) / 2;
91  }
92 
93  /*
94  this corresponds to integrating a gaussian centered on zero and of width sigma
95  convoluted with a zig-zag strip response function, which is triangular from xloc-pitch to xloc+pitch, with a maximum of 1 at xloc
96  */
97  template <class T>
98  inline T get_zigzag_fraction(const T& xloc, const T& sigma, const T& pitch)
99  {
100  return
101  // rising edge
102  (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
103 
104  // descending edge
105  + (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;
106  }
107 
108  // TVector3 streamer
109  [[maybe_unused]] inline std::ostream& operator<<(std::ostream& out, const TVector3& position)
110  {
111  out << "(" << position.x() << ", " << position.y() << ", " << position.z() << ")";
112  return out;
113  }
114 
115 } // namespace
116 
117 //___________________________________________________________________________
119  : SubsysReco(name)
120  , PHParameterInterface(name)
121 {
122  // initialize rng
123  const uint seed = PHRandomSeed();
124  m_rng.reset(gsl_rng_alloc(gsl_rng_mt19937));
125  gsl_rng_set(m_rng.get(), seed);
126 
128 }
129 
130 //___________________________________________________________________________
132 {
134 
135  // load parameters
136  m_tmin = get_double_param("micromegas_tmin");
137  m_tmax = get_double_param("micromegas_tmax");
138  m_electrons_per_gev = get_double_param("micromegas_electrons_per_gev");
139  m_gain = get_double_param("micromegas_gain");
140  m_cloud_sigma = get_double_param("micromegas_cloud_sigma");
141  m_diffusion_trans = get_double_param("micromegas_diffusion_trans");
142  m_added_smear_sigma_z = get_double_param("micromegas_added_smear_sigma_z");
143  m_added_smear_sigma_rphi = get_double_param("micromegas_added_smear_sigma_rphi");
144 
145  // printout
146  std::cout
147  << "PHG4MicromegasHitReco::InitRun\n"
148  << " m_tmin: " << m_tmin << "ns, m_tmax: " << m_tmax << "ns\n"
149  << " m_electrons_per_gev: " << m_electrons_per_gev << "\n"
150  << " m_gain: " << m_gain << "\n"
151  << " m_cloud_sigma: " << m_cloud_sigma << "cm\n"
152  << " m_diffusion_trans: " << m_diffusion_trans << "cm/sqrt(cm)\n"
153  << " m_added_smear_sigma_z: " << m_added_smear_sigma_z << "cm\n"
154  << " m_added_smear_sigma_rphi: " << m_added_smear_sigma_rphi << "cm\n"
155  << std::endl;
156 
157  // get dst node
158  PHNodeIterator iter(topNode);
159  auto dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
160  if (!dstNode)
161  {
162  std::cout << "PHG4MicromegasHitReco::InitRun - DST Node missing, doing nothing." << std::endl;
163  exit(1);
164  }
165 
166  // create hitset container if needed
167  auto hitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
168  if (!hitsetcontainer)
169  {
170  std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITSET." << std::endl;
171 
172  // find or create TRKR node
173  PHNodeIterator dstiter(dstNode);
174  auto trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
175  if (!trkrnode)
176  {
177  trkrnode = new PHCompositeNode("TRKR");
178  dstNode->addNode(trkrnode);
179  }
180 
181  // create container and add to the tree
182  hitsetcontainer = new TrkrHitSetContainerv1;
183  auto newNode = new PHIODataNode<PHObject>(hitsetcontainer, "TRKR_HITSET", "PHObject");
184  trkrnode->addNode(newNode);
185  }
186 
187  // create hit truth association if needed
188  auto hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
189  if (!hittruthassoc)
190  {
191  std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITTRUTHASSOC." << std::endl;
192 
193  // find or create TRKR node
194  PHNodeIterator dstiter(dstNode);
195  auto trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
196  if (!trkrnode)
197  {
198  trkrnode = new PHCompositeNode("TRKR");
199  dstNode->addNode(trkrnode);
200  }
201 
202  hittruthassoc = new TrkrHitTruthAssocv1;
203  auto newNode = new PHIODataNode<PHObject>(hittruthassoc, "TRKR_HITTRUTHASSOC", "PHObject");
204  trkrnode->addNode(newNode);
205  }
206 
208 }
209 
210 //___________________________________________________________________________
212 {
213  // load relevant nodes
214  // G4Hits
215  auto g4hitcontainer = findNode::getClass<PHG4HitContainer>(topNode, "G4HIT_MICROMEGAS");
216  assert(g4hitcontainer);
217 
218  // acts geometry
219  m_acts_geometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
221 
222  // geometry
223  const auto geonodename = full_geonodename();
224  auto geonode = findNode::getClass<PHG4CylinderGeomContainer>(topNode, geonodename.c_str());
225  assert(geonode);
226 
227  // Get the TrkrHitSetContainer node
228  auto trkrhitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
229  assert(trkrhitsetcontainer);
230 
231  // Get the TrkrHitTruthAssoc node
232  auto hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
233  assert(hittruthassoc);
234 
235  // loop over layers in the g4hit container
236  auto layer_range = g4hitcontainer->getLayers();
237  for (auto layer_it = layer_range.first; layer_it != layer_range.second; ++layer_it)
238  {
239  // get layer
240  const auto layer = *layer_it;
241 
242  // get relevant geometry
243  auto layergeom = dynamic_cast<CylinderGeomMicromegas*>(geonode->GetLayerGeom(layer));
244  assert(layergeom);
245 
246  /*
247  * get the position of the detector mesh in local coordinates
248  * in local coordinate the mesh is a plane perpendicular to the z axis
249  * Its position along z depends on the drift direction
250  * it is used to calculate the drift distance of the primary electrons, and the
251  * corresponding transverse diffusion
252  */
253  const auto mesh_local_z = layergeom->get_drift_direction() == MicromegasDefs::DriftDirection::OUTWARD ? layergeom->get_thickness() / 2 : -layergeom->get_thickness() / 2;
254 
255  // /*
256  // * get the radius of the detector mesh. It depends on the drift direction
257  // * it is used to calculate the drift distance of the primary electrons, and the
258  // * corresponding transverse diffusion
259  // */
260  // const auto mesh_radius = layergeom->get_drift_direction() == MicromegasDefs::DriftDirection::OUTWARD ?
261  // (layergeom->get_radius() + layergeom->get_thickness()/2):
262  // (layergeom->get_radius() - layergeom->get_thickness()/2);
263 
264  // get hits
265  const PHG4HitContainer::ConstRange g4hit_range = g4hitcontainer->getHits(layer);
266 
267  // loop over hits
268  for (auto g4hit_it = g4hit_range.first; g4hit_it != g4hit_range.second; ++g4hit_it)
269  {
270  // get hit
271  PHG4Hit* g4hit = g4hit_it->second;
272 
273  // check time window
274  if (g4hit->get_t(0) > m_tmax)
275  {
276  continue;
277  }
278  if (g4hit->get_t(1) < m_tmin)
279  {
280  continue;
281  }
282 
283  // get world coordinates
284  TVector3 world_in(g4hit->get_x(0), g4hit->get_y(0), g4hit->get_z(0));
285  TVector3 world_out(g4hit->get_x(1), g4hit->get_y(1), g4hit->get_z(1));
286 
287  // get tile id from g4hit
288  const int tileid = g4hit->get_property_int(PHG4Hit::prop_index_i);
289 
290  // convert to local coordinate
291  const auto local_in = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_in);
292  const auto local_out = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_out);
293 
294  // number of primary elections
295  const auto nprimary = get_primary_electrons(g4hit);
296  if (!nprimary)
297  {
298  continue;
299  }
300 
301  // create hitset
302  const TrkrDefs::hitsetkey hitsetkey = MicromegasDefs::genHitSetKey(layer, layergeom->get_segmentation_type(), tileid);
303  const auto hitset_it = trkrhitsetcontainer->findOrAddHitSet(hitsetkey);
304 
305  // keep track of all charges
306  using charge_map_t = std::map<int, double>;
307  charge_map_t total_charges;
308 
309  // loop over primaries
310  for (uint ie = 0; ie < nprimary; ++ie)
311  {
312  // put the electron at a random position along the g4hit path
313  // in local reference frame, drift occurs along the y axis, from local y to mesh_local_z
314 
315  const auto t = gsl_ran_flat(m_rng.get(), 0.0, 1.0);
316  auto local = local_in * t + local_out * (1.0 - t);
317 
318  if (m_diffusion_trans > 0)
319  {
320  // add transeverse diffusion
321  // first convert to polar coordinates
322  const double z = local.z();
323  const double drift_distance = std::abs(z - mesh_local_z);
324  const double diffusion = gsl_ran_gaussian(m_rng.get(), m_diffusion_trans * std::sqrt(drift_distance));
325  const double diffusion_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
326 
327  // diffusion occurs in x,z plane with a magnitude 'diffusion' and an angle 'diffusion angle'
328  local += TVector3(diffusion * std::cos(diffusion_angle), diffusion * std::sin(diffusion_angle), 0);
329  }
330 
331  const auto& added_smear_sigma = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? m_added_smear_sigma_rphi : m_added_smear_sigma_z;
332 
333  if (added_smear_sigma > 0)
334  {
335  // additional ad hoc smearing
336  const double added_smear_trans = gsl_ran_gaussian(m_rng.get(), added_smear_sigma);
337  const double added_smear_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
338  local += TVector3(added_smear_trans * std::cos(added_smear_angle), added_smear_trans * std::sin(added_smear_angle), 0);
339  }
340 
341  // distribute charge among adjacent strips
342  const auto fractions = distribute_charge(layergeom, tileid, {local.x(), local.y()}, m_cloud_sigma);
343 
344  // make sure fractions adds up to unity
345  if (Verbosity() > 10)
346  {
347  const auto sum = std::accumulate(fractions.begin(), fractions.end(), double(0),
348  [](double value, const charge_pair_t& pair)
349  { return value + pair.second; });
350  std::cout << "PHG4MicromegasHitReco::process_event - sum: " << sum << std::endl;
351  }
352 
353  // generate gain for this electron
355 
356  // merge to total charges
357  for (const auto& pair : fractions)
358  {
359  const int strip = pair.first;
360  if (strip < 0 || strip >= (int) layergeom->get_strip_count(tileid, m_acts_geometry))
361  {
362  continue;
363  }
364 
365  const auto it = total_charges.lower_bound(strip);
366  if (it != total_charges.end() && it->first == strip)
367  {
368  it->second += pair.second * gain;
369  }
370  else
371  {
372  total_charges.insert(it, std::make_pair(strip, pair.second * gain));
373  }
374  }
375  }
376 
377  // generate the key for this hit
378  // loop over strips in list
379  for (const auto pair : total_charges)
380  {
381  // get strip and bound check
382  const int strip = pair.first;
383 
384  // get hit from hitset
386  auto hit = hitset_it->second->getHit(hitkey);
387  if (!hit)
388  {
389  // create hit and insert in hitset
390  hit = new TrkrHitv2;
391  hitset_it->second->addHitSpecificKey(hitkey, hit);
392  }
393 
394  // add energy from g4hit
395  hit->addEnergy(pair.second);
396 
397  // associate this hitset and hit to the geant4 hit key
398  hittruthassoc->addAssoc(hitsetkey, hitkey, g4hit_it->first);
399  }
400  }
401  }
402 
404 }
405 
406 //___________________________________________________________________________
408 {
409  // default timing window (ns)
410  /*
411  * see https://indico.bnl.gov/event/8548/contributions/37753/attachments/28212/43343/2020_05_Proposal_sPhenixMonitoring_update_19052020.pptx slide 10
412  * small negative time for tmin is set to catch out of time, same-bunch pileup events
413  * similar value is used in PHG4InttReco
414  */
415  set_default_double_param("micromegas_tmin", -20);
416  set_default_double_param("micromegas_tmax", 800);
417 
418  // gas data from
419  // http://www.slac.stanford.edu/pubs/icfa/summer98/paper3/paper3.pdf
420  // assume Ar/iC4H10 90/10, at 20C and 1atm
421  // dedx (KeV/cm) for MIP
422  static constexpr double Ar_dEdx = 2.44;
423  static constexpr double iC4H10_dEdx = 5.93;
424  static constexpr double mix_dEdx = 0.9 * Ar_dEdx + 0.1 * iC4H10_dEdx;
425 
426  // number of electrons per MIP (cm-1)
427  static constexpr double Ar_ntot = 94;
428  static constexpr double iC4H10_ntot = 195;
429  static constexpr double mix_ntot = 0.9 * Ar_ntot + 0.1 * iC4H10_ntot;
430 
431  // number of electrons per gev
432  static constexpr double mix_electrons_per_gev = 1e6 * mix_ntot / mix_dEdx;
433  set_default_double_param("micromegas_electrons_per_gev", mix_electrons_per_gev);
434 
435  // gain
436  set_default_double_param("micromegas_gain", 2000);
437 
438  // electron cloud sigma, after avalanche (cm)
439  set_default_double_param("micromegas_cloud_sigma", 0.04);
440 
441  // transverse diffusion (cm/sqrt(cm))
442  set_default_double_param("micromegas_diffusion_trans", 0.03);
443 
444  // additional smearing (cm)
445  set_default_double_param("micromegas_added_smear_sigma_z", 0);
446  set_default_double_param("micromegas_added_smear_sigma_rphi", 0);
447 }
448 
449 //___________________________________________________________________________
451 {
452  return gsl_ran_poisson(m_rng.get(), g4hit->get_eion() * m_electrons_per_gev);
453 }
454 
455 //___________________________________________________________________________
457 {
458  /*
459  * to handle gain fluctuations, an exponential distribution is used, similar to what used for the GEMS
460  * (simulations/g4simulations/g4tpc/PHG4TpcPadPlaneReadout::getSingleEGEMAmplification)
461  * One must get a different random number for each primary electron for this to be valid
462  */
463  return gsl_ran_exponential(m_rng.get(), m_gain);
464 }
465 
466 //___________________________________________________________________________
468  CylinderGeomMicromegas* layergeom,
469  uint tileid,
470  const TVector2& local_coords,
471  double sigma) const
472 {
473  // find tile and strip matching center position
474  auto stripnum = layergeom->find_strip_from_local_coords(tileid, m_acts_geometry, local_coords);
475 
476  // check tile and strip
477  if (stripnum < 0)
478  {
479  return charge_list_t();
480  }
481 
482  // store pitch and radius
483  const auto pitch = layergeom->get_pitch();
484 
485  // find relevant strip indices
486  const auto strip_count = layergeom->get_strip_count(tileid, m_acts_geometry);
487  const int nstrips = 5. * sigma / pitch + 1;
488  const auto stripnum_min = std::clamp<int>(stripnum - nstrips, 0, strip_count);
489  const auto stripnum_max = std::clamp<int>(stripnum + nstrips, 0, strip_count);
490 
491  // prepare charge list
492  charge_list_t charge_list;
493 
494  // loop over strips
495  for (int strip = stripnum_min; strip <= stripnum_max; ++strip)
496  {
497  // get strip center
498  const auto strip_location = layergeom->get_local_coordinates(tileid, m_acts_geometry, strip);
499 
500  /*
501  * find relevant strip coordinate with respect to location
502  * in local coordinate, phi segmented view has strips along z and measures along x
503  * in local coordinate, z segmented view has strips along phi and measures along y
504  */
505  const auto xloc = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? (strip_location.X() - local_coords.X()) : (strip_location.Y() - local_coords.Y());
506 
507  // decide of whether zigzag or straight strips are used depending on segmentation type
508  /*
509  * for the real detector SEGMENTATION_Z view has zigzag strip due to large pitch (2mm)
510  * whereas SEGMENTATION_PHI has straight strips
511  */
512  const bool zigzag_strips = (layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_Z);
513 
514  // calculate charge fraction
515  const auto fraction = zigzag_strips ? get_zigzag_fraction(xloc, sigma, pitch) : get_rectangular_fraction(xloc, sigma, pitch);
516 
517  // store
518  charge_list.push_back(std::make_pair(strip, fraction));
519  }
520 
521  return charge_list;
522 }