Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
PHG4TpcPadPlaneReadout.cc
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file PHG4TpcPadPlaneReadout.cc
2 
4 #include <g4detectors/PHG4CellDefs.h> // for genkey, keytype
7 
8 #include <g4main/PHG4Hit.h> // for PHG4Hit
10 
11 #include <phool/PHRandomSeed.h>
12 #include <phool/getClass.h>
13 
14 // Move to new storage containers
15 #include <trackbase/TpcDefs.h>
16 #include <trackbase/TrkrDefs.h> // for hitkey, hitse...
17 #include <trackbase/TrkrHit.h> // for TrkrHit
18 #include <trackbase/TrkrHitSet.h>
20 #include <trackbase/TrkrHitv2.h> // for TrkrHit
21 
26 
27 #include <phool/phool.h> // for PHWHERE
28 
29 #include <TSystem.h>
30 #include <TFile.h>
31 
32 #include <gsl/gsl_randist.h>
33 #include <gsl/gsl_rng.h> // for gsl_rng_alloc
34 
35 #include <cmath>
36 #include <iostream>
37 #include <map> // for _Rb_tree_cons...
38 #include <utility> // for pair
39 #include <cstdlib> // for getenv
40 
41 class PHCompositeNode;
42 class TrkrHitTruthAssoc;
43 
44 namespace
45 {
47  template <class T>
48  inline constexpr T square(const T &x)
49  {
50  return x * x;
51  }
52 
53  template <class T>
54  inline T get_r( const T& x, const T& y )
55  { return std::sqrt(square(x) + square(y)); }
56 
58  template <class T>
59  inline T gaus(const T &x, const T &sigma)
60  {
61  return std::exp(-square(x / sigma) / 2) / (sigma * std::sqrt(2 * M_PI));
62  }
63 
64  static constexpr unsigned int print_layer = 18;
65 
66 } // namespace
67 
69  : PHG4TpcPadPlane(name)
70 {
72  //if(m_flagToUseGain==1)
73  ReadGain();
74  RandomGenerator = gsl_rng_alloc(gsl_rng_mt19937);
75  gsl_rng_set(RandomGenerator, PHRandomSeed()); // fixed seed is handled in this funtcion
76  h_gain[0] = nullptr;
77  h_gain[1] = nullptr;
78 
79  return;
80 }
81 
83 {
84  gsl_rng_free(RandomGenerator);
85  delete h_gain[0];
86  delete h_gain[1];
87 }
88 
89 //_________________________________________________________
91 {
92  // base class
93  const auto reply = PHG4TpcPadPlane::InitRun( topNode );
94  if( reply != Fun4AllReturnCodes::EVENT_OK ) return reply;
95 
96  // load geo node
97  const std::string seggeonodename = "CYLINDERCELLGEOM_SVTX";
98  GeomContainer = findNode::getClass<PHG4TpcCylinderGeomContainer>(topNode, seggeonodename);
100 
102 }
103 
104 //_________________________________________________________
106 {
107  // Jin H.: For the GEM gain in sPHENIX TPC,
108  // Bob pointed out the PHENIX HBD measured it as the Polya function with theta parameter = 0.8.
109  // Just talked with Tom too, he suggest us to start the TPC modeling with simpler exponential function
110  // with lambda parameter of 1/2000, (i.e. Polya function with theta parameter = 0, q_bar = 2000). Please note, this gain variation need to be sampled for each initial electron individually.
111  // Summing over ~30 initial electrons, the distribution is pushed towards more Gauss like.
112  // Bob A.: I like Tom's suggestion to use the exponential distribution as a first approximation
113  // for the single electron gain distribution -
114  // and yes, the parameter you're looking for is of course the slope, which is the inverse gain.
115  double nelec = gsl_ran_exponential(RandomGenerator, averageGEMGain);
116  //Put gain reading here
117 
118  return nelec;
119 }
120 
121 
123  TpcClusterBuilder &tpc_truth_clusterer,
124  TrkrHitSetContainer *single_hitsetcontainer,
125  TrkrHitSetContainer *hitsetcontainer,
126  TrkrHitTruthAssoc * /*hittruthassoc*/,
127  const double x_gem, const double y_gem, const double t_gem, const unsigned int side,
128  PHG4HitContainer::ConstIterator hiter, TNtuple * /*ntpad*/, TNtuple * /*nthit*/)
129 {
130  // One electron per call of this method
131  // The x_gem and y_gem values have already been randomized within the transverse drift diffusion width
132  // The t_gem value already reflects the drift time of the primary electron from the production point, and is randomized within the longitudinal diffusion witdth
133 
134  double phi = atan2(y_gem, x_gem);
135  if (phi > +M_PI) phi -= 2 * M_PI;
136  if (phi < -M_PI) phi += 2 * M_PI;
137 
138  double rad_gem = get_r( x_gem, y_gem );
139 
140  // Moving electrons from dead area to a closest pad
141  for (int iregion = 0; iregion < 3; ++iregion)
142  {
143  double daR = 0;
144  if(iregion==0 || iregion==2){
145  daR=1.0;//1.0cm edge to collect electrons from
146  }else{
147  daR = MinRadius[iregion]-MaxRadius[iregion-1];
148  }
149  if ( rad_gem <= MinRadius[iregion] && rad_gem >= MinRadius[iregion]-daR){
150  if( rad_gem <= MinRadius[iregion]-daR/2){
151  rad_gem = MinRadius[iregion] - (1.1*daR) ;
152  }else{
153  rad_gem = MinRadius[iregion] + 0.1*daR;
154  }
155 
156  }
157 
158  }
159 
160  phi = check_phi(side, phi, rad_gem);
161  unsigned int layernum = 0;
162  /* TpcClusterBuilder pass_data {}; */
163 
164  // Find which readout layer this electron ends up in
165 
167  for (PHG4TpcCylinderGeomContainer::ConstIterator layeriter = layerrange.first;
168  layeriter != layerrange.second;
169  ++layeriter)
170  {
171  double rad_low = layeriter->second->get_radius() - layeriter->second->get_thickness() / 2.0;
172  double rad_high = layeriter->second->get_radius() + layeriter->second->get_thickness() / 2.0;
173 
174  if (rad_gem > rad_low && rad_gem < rad_high)
175  {
176  // capture the layer where this electron hits the gem stack
177  LayerGeom = layeriter->second;
178 
179  layernum = LayerGeom->get_layer();
180  /* pass_data.layerGeom = LayerGeom; */
181  /* pass_data.layer = layernum; */
182  if (Verbosity() > 1000)
183  std::cout << " g4hit id " << hiter->first << " rad_gem " << rad_gem << " rad_low " << rad_low << " rad_high " << rad_high
184  << " layer " << hiter->second->get_layer() << " want to change to " << layernum << std::endl;
185  hiter->second->set_layer(layernum); // have to set here, since the stepping action knows nothing about layers
186  }
187 
188  }
189 
190  if (layernum == 0)
191  {
192  return;
193  }
194 
195  // store phi bins and tbins upfront to avoid repetitive checks on the phi methods
196  const auto phibins = LayerGeom->get_phibins();
197  /* pass_data.nphibins = phibins; */
198 
199  const auto tbins = LayerGeom->get_zbins();
200 
201  // Create the distribution function of charge on the pad plane around the electron position
202 
203  // The resolution due to pad readout includes the charge spread during GEM multiplication.
204  // this now defaults to 400 microns during construction from Tom (see 8/11 email).
205  // Use the setSigmaT(const double) method to update...
206  // We use a double gaussian to represent the smearing due to the SAMPA chip shaping time - default values of fShapingLead and fShapingTail are for 80 ns SAMPA
207 
208  // amplify the single electron in the gem stack
209  //===============================
210 
211  double nelec = getSingleEGEMAmplification();
212  // Applying weight with respect to the rad_gem and phi after electrons are redistributed
213  double phi_gain = phi;
214  if(phi<0)phi_gain += 2*M_PI;
215  double gain_weight = 1.0;
216  if(m_flagToUseGain==1) gain_weight = h_gain[side]->GetBinContent(h_gain[side]->FindBin(rad_gem*10,phi_gain));//rad_gem in cm -> *10 to get mm
217  nelec = nelec*gain_weight;
218  //std::cout<<"PHG4TpcPadPlaneReadout::MapToPadPlane gain_weight = "<<gain_weight<<std::endl;
219  /* pass_data.neff_electrons = nelec; */
220 
221  // Distribute the charge between the pads in phi
222  //====================================
223 
224  if (Verbosity() > 200)
225  std::cout << " populate phi bins for "
226  << " layernum " << layernum
227  << " phi " << phi
228  << " sigmaT " << sigmaT
229  //<< " zigzag_pads " << zigzag_pads
230  << std::endl;
231 
232  std::vector<int> pad_phibin;
233  std::vector<double> pad_phibin_share;
234 
235  populate_zigzag_phibins(side, layernum, phi, sigmaT, pad_phibin, pad_phibin_share);
236  /* if (pad_phibin.size() == 0) { */
237  /* pass_data.neff_electrons = 0; */
238  /* } else { */
239  /* pass_data.fillPhiBins(pad_phibin); */
240  /* } */
241 
242  // Normalize the shares so they add up to 1
243  double norm1 = 0.0;
244  for (unsigned int ipad = 0; ipad < pad_phibin.size(); ++ipad)
245  {
246  double pad_share = pad_phibin_share[ipad];
247  norm1 += pad_share;
248  }
249  for (unsigned int iphi = 0; iphi < pad_phibin.size(); ++iphi)
250  pad_phibin_share[iphi] /= norm1;
251 
252  // Distribute the charge between the pads in t
253  //====================================
254  if (Verbosity() > 100 && layernum == print_layer)
255  std::cout << " populate t bins for layernum " << layernum
256  << " with t_gem " << t_gem << " sigmaL[0] " << sigmaL[0] << " sigmaL[1] " << sigmaL[1] << std::endl;
257 
258  std::vector<int> adc_tbin;
259  std::vector<double> adc_tbin_share;
260  populate_tbins(t_gem, sigmaL, adc_tbin, adc_tbin_share);
261  /* if (adc_tbin.size() == 0) { */
262  /* pass_data.neff_electrons = 0; */
263  /* } else { */
264  /* pass_data.fillTimeBins(adc_tbin); */
265  /* } */
266 
267  // Normalize the shares so that they add up to 1
268  double tnorm = 0.0;
269  for (unsigned int it = 0; it < adc_tbin.size(); ++it)
270  {
271  double bin_share = adc_tbin_share[it];
272  tnorm += bin_share;
273  }
274  for (unsigned int it = 0; it < adc_tbin.size(); ++it)
275  adc_tbin_share[it] /= tnorm;
276 
277  // Fill HitSetContainer
278  //===============
279  // These are used to do a quick clustering for checking
280  double phi_integral = 0.0;
281  double t_integral = 0.0;
282  double weight = 0.0;
283 
284  for (unsigned int ipad = 0; ipad < pad_phibin.size(); ++ipad)
285  {
286  int pad_num = pad_phibin[ipad];
287  double pad_share = pad_phibin_share[ipad];
288 
289  for (unsigned int it = 0; it < adc_tbin.size(); ++it)
290  {
291  int tbin_num = adc_tbin[it];
292  double adc_bin_share = adc_tbin_share[it];
293 
294  // Divide electrons from avalanche between bins
295  float neffelectrons = nelec * (pad_share) * (adc_bin_share);
296  if (neffelectrons < neffelectrons_threshold) continue; // skip signals that will be below the noise suppression threshold
297 
298  if (tbin_num >= tbins) std::cout << " Error making key: adc_tbin " << tbin_num << " ntbins " << tbins << std::endl;
299  if (pad_num >= phibins) std::cout << " Error making key: pad_phibin " << pad_num << " nphibins " << phibins << std::endl;
300 
301  // collect information to do simple clustering. Checks operation of PHG4CylinderCellTpcReco, and
302  // is also useful for comparison with PHG4TpcClusterizer result when running single track events.
303  // The only information written to the cell other than neffelectrons is tbin and pad number, so get those from geometry
304  double tcenter = LayerGeom->get_zcenter(tbin_num);
305  double phicenter = LayerGeom->get_phicenter(pad_num);
306  phi_integral += phicenter * neffelectrons;
307  t_integral += tcenter * neffelectrons;
308  weight += neffelectrons;
309  if (Verbosity() > 1 && layernum == print_layer)
310  std::cout << " tbin_num " << tbin_num << " tcenter " << tcenter << " pad_num " << pad_num << " phicenter " << phicenter
311  << " neffelectrons " << neffelectrons << " neffelectrons_threshold " << neffelectrons_threshold << std::endl;
312 
313  // new containers
314  //============
315  // We add the Tpc TrkrHitsets directly to the node using hitsetcontainer
316  // We need to create the TrkrHitSet if not already made - each TrkrHitSet should correspond to a Tpc readout module
317  // The hitset key includes the layer, sector, side
318 
319  // The side is an input parameter
320 
321  // get the Tpc readout sector - there are 12 sectors with how many pads each?
322  unsigned int pads_per_sector = phibins / 12;
323  unsigned int sector = pad_num / pads_per_sector;
324  TrkrDefs::hitsetkey hitsetkey = TpcDefs::genHitSetKey(layernum, sector, side);
325  // Use existing hitset or add new one if needed
326  TrkrHitSetContainer::Iterator hitsetit = hitsetcontainer->findOrAddHitSet(hitsetkey);
327  TrkrHitSetContainer::Iterator single_hitsetit = single_hitsetcontainer->findOrAddHitSet(hitsetkey);
328 
329  // generate the key for this hit, requires tbin and phibin
330  TrkrDefs::hitkey hitkey = TpcDefs::genHitKey((unsigned int) pad_num, (unsigned int) tbin_num);
331  // See if this hit already exists
332  TrkrHit *hit = nullptr;
333  hit = hitsetit->second->getHit(hitkey);
334  if (!hit)
335  {
336  // create a new one
337  hit = new TrkrHitv2();
338  hitsetit->second->addHitSpecificKey(hitkey, hit);
339  }
340  // Either way, add the energy to it -- adc values will be added at digitization
341  hit->addEnergy(neffelectrons);
342 
343  tpc_truth_clusterer.addhitset(hitsetkey, hitkey, neffelectrons);
344 
345  // repeat for the single_hitsetcontainer
346  // See if this hit already exists
347  TrkrHit *single_hit = nullptr;
348  single_hit = single_hitsetit->second->getHit(hitkey);
349  if (!single_hit)
350  {
351  // create a new one
352  single_hit = new TrkrHitv2();
353  single_hitsetit->second->addHitSpecificKey(hitkey, single_hit);
354  }
355  // Either way, add the energy to it -- adc values will be added at digitization
356  single_hit->addEnergy(neffelectrons);
357 
358  /*
359  if (Verbosity() > 0)
360  {
361  assert(nthit);
362  nthit->Fill(layernum, pad_num, tbin_num, neffelectrons);
363  }
364  */
365 
366  } // end of loop over adc T bins
367  } // end of loop over zigzag pads
368  /* pass_data.phi_integral = phi_integral; */
369  /* pass_data.time_integral = t_integral; */
370 
371  /*
372  // Capture the input values at the gem stack and the quick clustering results, elecron-by-electron
373  if (Verbosity() > 0)
374  {
375  assert(ntpad);
376  ntpad->Fill(layernum, phi, phi_integral / weight, t_gem, t_integral / weight);
377  }
378  */
379 
380  if (Verbosity() > 100)
381  if (layernum == print_layer)
382  {
383  std::cout << " hit " << m_NHits << " quick centroid for this electron " << std::endl;
384  std::cout << " phi centroid = " << phi_integral / weight << " phi in " << phi << " phi diff " << phi_integral / weight - phi << std::endl;
385  std::cout << " t centroid = " << t_integral / weight << " t in " << t_gem << " t diff " << t_integral / weight - t_gem << std::endl;
386  // For a single track event, this captures the distribution of single electron centroids on the pad plane for layer print_layer.
387  // The centroid of that should match the cluster centroid found by PHG4TpcClusterizer for layer print_layer, if everything is working
388  // - matches to < .01 cm for a few cases that I checked
389 
390  /*
391  assert(nthit);
392  nthit->Fill(hit, layernum, phi, phi_integral / weight, t_gem, t_integral / weight, weight);
393  */
394  }
395 
396  m_NHits++;
397  /* return pass_data; */
398 }
399 double PHG4TpcPadPlaneReadout::check_phi(const unsigned int side, const double phi, const double radius)
400 {
401  double new_phi = phi;
402  int p_region=-1;
403  for (int iregion = 0; iregion < 3; ++iregion)
404  {
405  if (radius < MaxRadius[iregion] && radius > MinRadius[iregion]) p_region = iregion;
406  }
407 
408  if(p_region>0)
409  {
410  for(int s=0; s<12;s++)
411  {
412  double daPhi = 0;
413  if (s==0)
414  {
415  daPhi = fabs(sector_min_Phi_sectors[side][p_region][11] + 2*M_PI - sector_max_Phi_sectors[side][p_region][s]);
416  }else{
417  daPhi = fabs(sector_min_Phi_sectors[side][p_region][s-1] - sector_max_Phi_sectors[side][p_region][s]);
418  }
419  double min_phi = sector_max_Phi_sectors[side][p_region][s];
420  double max_phi = sector_max_Phi_sectors[side][p_region][s]+daPhi;
421  if (new_phi<=max_phi && new_phi>=min_phi)
422  {
423  if(fabs(max_phi - new_phi) > fabs(new_phi - min_phi))
424  {
425  new_phi = min_phi-PhiBinWidth[p_region]/5;//to be changed
426  }else{
427  new_phi = max_phi+PhiBinWidth[p_region]/5;
428  }
429  }
430  }
431  if(new_phi<sector_min_Phi_sectors[side][p_region][11] && new_phi>=-M_PI)
432  { new_phi += 2*M_PI; }
433  }
434 
435  return new_phi;
436 }
437 
438 void PHG4TpcPadPlaneReadout::populate_zigzag_phibins(const unsigned int side, const unsigned int layernum, const double phi, const double cloud_sig_rp, std::vector<int> &phibin_pad, std::vector<double> &phibin_pad_share)
439 {
440  const double radius = LayerGeom->get_radius();
441  const double phistepsize = LayerGeom->get_phistep();
442  const auto phibins = LayerGeom->get_phibins();
443 
444  // make the charge distribution gaussian
445  double rphi = phi * radius;
446  if (Verbosity() > 100)
447  if (LayerGeom->get_layer() == print_layer)
448  {
449  std::cout << " populate_zigzag_phibins for layer " << layernum << " with radius " << radius << " phi " << phi
450  << " rphi " << rphi << " phistepsize " << phistepsize << std::endl;
451  std::cout << " fcharge created: radius " << radius << " rphi " << rphi << " cloud_sig_rp " << cloud_sig_rp << std::endl;
452  }
453 
454  // Get the range of phi values that completely contains all pads that touch the charge distribution - (nsigmas + 1/2 pad width) in each direction
455  const double philim_low_calc = phi - (_nsigmas * cloud_sig_rp / radius) - phistepsize;
456  const double philim_high_calc = phi + (_nsigmas * cloud_sig_rp / radius) + phistepsize;
457 
458  // Find the pad range that covers this phi range
459  const double philim_low = check_phi(side, philim_low_calc, radius);
460  const double philim_high = check_phi(side, philim_high_calc, radius);
461 
462  int phibin_low = LayerGeom->get_phibin(philim_high);
463  int phibin_high = LayerGeom->get_phibin(philim_low);
464  int npads = phibin_high - phibin_low;
465 
466 
467  if (Verbosity() > 1000)
468  if (layernum == print_layer)
469  std::cout << " zigzags: phi " << phi << " philim_low " << philim_low << " phibin_low " << phibin_low
470  << " philim_high " << philim_high << " phibin_high " << phibin_high << " npads " << npads << std::endl;
471 
472  if (npads < 0 || npads > 9) npads = 9; // can happen if phibin_high wraps around. If so, limit to 10 pads and fix below
473 
474  // Calculate the maximum extent in r-phi of pads in this layer. Pads are assumed to touch the center of the next phi bin on both sides.
475  const double pad_rphi = 2.0 * LayerGeom->get_phistep() * radius;
476 
477  // Make a TF1 for each pad in the phi range
478  using PadParameterSet = std::array<double, 2>;
479  std::array<PadParameterSet, 10> pad_parameters;
480  std::array<int, 10> pad_keep;
481 
482 
483  // Now make a loop that steps through the charge distribution and evaluates the response at that point on each pad
484  std::array<double, 10> overlap = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
485  double pads_phi[10] = {0., 0., 0., 0., 0., 0., 0., 0., 0., 0.};
486  double sum_of_pads_phi = 0.;
487  double sum_of_pads_absphi = 0.;
488  for (int ipad = 0; ipad <= npads; ipad++)
489  {
490  int pad_now = phibin_low + ipad;
491  if (pad_now >= phibins) pad_now -= phibins;
492  pads_phi[ipad] = LayerGeom->get_phicenter(pad_now);
493  sum_of_pads_phi += pads_phi[ipad];
494  sum_of_pads_absphi += fabs(pads_phi[ipad]);
495  }
496 
497  for (int ipad = 0; ipad <= npads; ipad++)
498  {
499  int pad_now = phibin_low + ipad;
500  //if(phibin_low<0 && phibin_high<0) pad_now = phibin_high + ipad;
501  // check that we do not exceed the maximum number of pads, wrap if necessary
502  if (pad_now >= phibins) pad_now -= phibins;
503 
504  pad_keep[ipad] = pad_now;
505  double phi_now = pads_phi[ipad];
506  const double rphi_pad_now = phi_now * radius;
507  pad_parameters[ipad] = {{pad_rphi / 2.0, rphi_pad_now}};
508 
509  if (Verbosity() > 1000)
510  if (layernum == print_layer)
511  std::cout << " zigzags: make fpad for ipad " << ipad << " pad_now " << pad_now << " pad_rphi/2 " << pad_rphi / 2.0
512  << " rphi_pad_now " << rphi_pad_now << std::endl;
513  //}
514 
515  // use analytic integral
516  //for (int ipad = 0; ipad <= npads; ipad++)
517  //{
518  //const double pitch = pad_parameters[ipad][0];
519  //const double x_loc = pad_parameters[ipad][1] - rphi;
520  //const double sigma = cloud_sig_rp;
521 
522  const double pitch = pad_rphi / 2.0;//eshulga
523  double x_loc_tmp = rphi_pad_now - rphi;//eshulga
524  const double sigma = cloud_sig_rp;//eshulga
525 
526  // Checking if the pads are on the same side of the TPC in phi
527  if(fabs(sum_of_pads_phi)!= sum_of_pads_absphi){
528  if(phi<-M_PI/2 && phi_now>0){
529  x_loc_tmp = (phi_now - 2*M_PI) * radius - rphi;
530  }
531  if(phi>M_PI/2 && phi_now<0){
532  x_loc_tmp = (phi_now + 2*M_PI) * radius - rphi;
533  }
534  if(phi<0 && phi_now>0){
535  x_loc_tmp = (phi_now+fabs(phi)) * radius;
536  }
537  if(phi>0 && phi_now<0){
538  x_loc_tmp = (2*M_PI - phi_now + phi) * radius;
539  }
540  }
541 
542  const double x_loc = x_loc_tmp;
543  // calculate fraction of the total charge on this strip
544  /*
545  this corresponds to integrating the charge distribution Gaussian function (centered on rphi and of width cloud_sig_rp),
546  convoluted with a strip response function, which is triangular from -pitch to +pitch, with a maximum of 1. at stript center
547  */
548  overlap[ipad] =
549  (pitch - x_loc) * (std::erf(x_loc / (M_SQRT2 * sigma)) - std::erf((x_loc - pitch) / (M_SQRT2 * sigma))) / (pitch * 2) + (pitch + x_loc) * (std::erf((x_loc + pitch) / (M_SQRT2 * sigma)) - std::erf(x_loc / (M_SQRT2 * sigma))) / (pitch * 2) + (gaus(x_loc - pitch, sigma) - gaus(x_loc, sigma)) * square(sigma) / pitch + (gaus(x_loc + pitch, sigma) - gaus(x_loc, sigma)) * square(sigma) / pitch;
550 
551  }
552 
553  // now we have the overlap for each pad
554  for (int ipad = 0; ipad <= npads; ipad++)
555  {
556  phibin_pad.push_back(pad_keep[ipad]);
557  phibin_pad_share.push_back(overlap[ipad]);
558  }
559 
560  return;
561 }
562 
563 void PHG4TpcPadPlaneReadout::populate_tbins(const double t, const std::array<double, 2> &cloud_sig_tt, std::vector<int> &tbin_adc, std::vector<double> &tbin_adc_share)
564 {
565  int tbin = LayerGeom->get_zbin(t);
566  if (tbin < 0 || tbin > LayerGeom->get_zbins())
567  {
568  // std::cout << " t bin is outside range, return" << std::endl;
569  return;
570  }
571 
572  double tstepsize = LayerGeom->get_zstep();
573  double tdisp = t - LayerGeom->get_zcenter(tbin);
574 
575  if (Verbosity() > 1000)
576  std::cout << " input: t " << t << " tbin " << tbin << " tstepsize " << tstepsize << " t center " << LayerGeom->get_zcenter(tbin) << " tdisp " << tdisp << std::endl;
577 
578  // Because of diffusion, hits can be shared across the membrane, so we allow all t bins
579  int min_cell_tbin = 0;
580  int max_cell_tbin = NTBins - 1;
581 
582  double cloud_sig_tt_inv[2];
583  cloud_sig_tt_inv[0] = 1. / cloud_sig_tt[0];
584  cloud_sig_tt_inv[1] = 1. / cloud_sig_tt[1];
585 
586  int zsect = 0;
587  if (t < 0)
588  zsect = -1;
589  else
590  zsect = 1;
591 
592  int n_zz = int(3 * (cloud_sig_tt[0] + cloud_sig_tt[1]) / (2.0 * tstepsize) + 1);
593  if (Verbosity() > 1000) std::cout << " n_zz " << n_zz << " cloud_sigzz[0] " << cloud_sig_tt[0] << " cloud_sig_tt[1] " << cloud_sig_tt[1] << std::endl;
594  for (int it = -n_zz; it != n_zz + 1; ++it)
595  {
596  int cur_t_bin = tbin + it;
597  if ((cur_t_bin < min_cell_tbin) || (cur_t_bin > max_cell_tbin)) continue;
598 
599  if (Verbosity() > 1000)
600  std::cout << " it " << it << " cur_t_bin " << cur_t_bin << " min_cell_tbin " << min_cell_tbin << " max_cell_tbin " << max_cell_tbin << std::endl;
601 
602  double t_integral = 0.0;
603  if (it == 0)
604  {
605  // the crossover between lead and tail shaping occurs in this bin
606  int index1 = -1;
607  int index2 = -1;
608  if (zsect == -1)
609  {
610  index1 = 0;
611  index2 = 1;
612  }
613  else
614  {
615  index1 = 1;
616  index2 = 0;
617  }
618 
619  double tLim1 = 0.0;
620  double tLim2 = 0.5 * M_SQRT2 * (-0.5 * tstepsize - tdisp) * cloud_sig_tt_inv[index1];
621  // 1/2 * the erf is the integral probability from the argument Z value to zero, so this is the integral probability between the Z limits
622  double t_integral1 = 0.5 * (erf(tLim1) - erf(tLim2));
623 
624  if (Verbosity() > 1000)
625  if (LayerGeom->get_layer() == print_layer)
626  std::cout << " populate_tbins: cur_t_bin " << cur_t_bin << " center t " << LayerGeom->get_zcenter(cur_t_bin)
627  << " index1 " << index1 << " tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral1 " << t_integral1 << std::endl;
628 
629  tLim2 = 0.0;
630  tLim1 = 0.5 * M_SQRT2 * (0.5 * tstepsize - tdisp) * cloud_sig_tt_inv[index2];
631  double t_integral2 = 0.5 * (erf(tLim1) - erf(tLim2));
632 
633  if (Verbosity() > 1000)
634  if (LayerGeom->get_layer() == print_layer)
635  std::cout << " populate_tbins: cur_t_bin " << cur_t_bin << " center t " << LayerGeom->get_zcenter(cur_t_bin)
636  << " index2 " << index2 << " tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral2 " << t_integral2 << std::endl;
637 
638  t_integral = t_integral1 + t_integral2;
639  }
640  else
641  {
642  // The non zero bins are entirely in the lead or tail region
643  // lead or tail depends on which side of the membrane
644  int index = 0;
645  if (it < 0)
646  {
647  if (zsect == -1)
648  index = 0;
649  else
650  index = 1;
651  }
652  else
653  {
654  if (zsect == -1)
655  index = 1;
656  else
657  index = 0;
658  }
659  double tLim1 = 0.5 * M_SQRT2 * ((it + 0.5) * tstepsize - tdisp) * cloud_sig_tt_inv[index];
660  double tLim2 = 0.5 * M_SQRT2 * ((it - 0.5) * tstepsize - tdisp) * cloud_sig_tt_inv[index];
661  t_integral = 0.5 * (erf(tLim1) - erf(tLim2));
662 
663  if (Verbosity() > 1000)
664  if (LayerGeom->get_layer() == print_layer)
665  std::cout << " populate_tbins: t_bin " << cur_t_bin << " center t " << LayerGeom->get_zcenter(cur_t_bin)
666  << " index " << index << " tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral " << t_integral << std::endl;
667  }
668 
669  tbin_adc.push_back(cur_t_bin);
670  tbin_adc_share.push_back(t_integral);
671  }
672 
673  return;
674 }
675 
676 void PHG4TpcPadPlaneReadout::UseGain(const int flagToUseGain)
677 {
678  m_flagToUseGain = flagToUseGain;
679  if(m_flagToUseGain == 1 && Verbosity()>0) std::cout << "PHG4TpcPadPlaneReadout: UseGain: TRUE " << std::endl;
680 }
681 
683 {
684  // Reading TPC Gain Maps from the file
685  if(m_flagToUseGain==1){
686  char *calibrationsroot = getenv("CALIBRATIONROOT");
687  if (calibrationsroot != nullptr)
688  {
689  std::string gain_maps_filename = std::string(calibrationsroot)+std::string("/TPC/GainMaps/TPCGainMaps.root");
690  TFile *fileGain = TFile::Open(gain_maps_filename.c_str(), "READ");
691  h_gain[0] = (TH2F*)fileGain->Get("RadPhiPlot0")->Clone();
692  h_gain[1] = (TH2F*)fileGain->Get("RadPhiPlot1")->Clone();
693  h_gain[0]->SetDirectory(nullptr);
694  h_gain[1]->SetDirectory(nullptr);
695  fileGain->Close();
696  }
697  }
698 }
700 {
701 
702  set_default_double_param("tpc_minradius_inner", 31.105);//30.0); // cm
703  set_default_double_param("tpc_minradius_mid", 41.153);//40.0);
704  set_default_double_param("tpc_minradius_outer", 58.367);//60.0);
705 
706 
707  set_default_double_param("tpc_maxradius_inner", 40.249);//40.0); // cm
708  set_default_double_param("tpc_maxradius_mid", 57.475);//60.0);
709  set_default_double_param("tpc_maxradius_outer", 75.911);//77.0); // from Tom
710 
711 
712  set_default_double_param("neffelectrons_threshold", 1.0);
713  set_default_double_param("maxdriftlength", 105.5); // cm
714  set_default_double_param("tpc_adc_clock", 53.0); // ns, for 18.8 MHz clock
715  set_default_double_param("gem_cloud_sigma", 0.04); // cm = 400 microns
716  set_default_double_param("sampa_shaping_lead", 32.0); // ns, for 80 ns SAMPA
717  set_default_double_param("sampa_shaping_tail", 48.0); // ns, for 80 ns SAMPA
718 
719  set_default_double_param("tpc_sector_phi_inner", 0.5024);//2 * M_PI / 12 );//sector size in phi for R1 sector
720  set_default_double_param("tpc_sector_phi_mid", 0.5087);//2 * M_PI / 12 );//sector size in phi for R2 sector
721  set_default_double_param("tpc_sector_phi_outer", 0.5097);//2 * M_PI / 12 );//sector size in phi for R3 sector
722 
723  set_default_int_param("ntpc_phibins_inner", 1152);
724  set_default_int_param("ntpc_phibins_mid", 1536);
725  set_default_int_param("ntpc_phibins_outer", 2304);
726 
727  // GEM Gain
728  /*
729  hp (2020/09/04): gain changed from 2000 to 1400, to accomodate gas mixture change
730  from Ne/CF4 90/10 to Ne/CF4 50/50, and keep the average charge per particle per pad constant
731  */
732  set_default_double_param("gem_amplification", 1400);
733  return;
734 }
735 
737 {
738 
739  neffelectrons_threshold = get_double_param("neffelectrons_threshold");
740 
741  MinRadius =
742  {{
743  get_double_param("tpc_minradius_inner"),
744  get_double_param("tpc_minradius_mid"),
745  get_double_param("tpc_minradius_outer")
746  }};
747 
748  MaxRadius =
749  {{
750  get_double_param("tpc_maxradius_inner"),
751  get_double_param("tpc_maxradius_mid"),
752  get_double_param("tpc_maxradius_outer")
753  }};
754 
755  sigmaT = get_double_param("gem_cloud_sigma");
756  sigmaL = {{
757  get_double_param("sampa_shaping_lead"),
758  get_double_param("sampa_shaping_tail")
759  }};
760 
761  const double tpc_adc_clock = get_double_param("tpc_adc_clock");
762 
763  const double MaxZ = get_double_param("maxdriftlength");
764  const double TBinWidth = tpc_adc_clock;
765  const double MaxT = extended_readout_time + 2.0 * MaxZ / drift_velocity; // allows for extended time readout
766  const double MinT = 0;
767  NTBins = (int) ((MaxT - MinT) / TBinWidth) + 1;
768 
769  const std::array<double, 3> SectorPhi =
770  {{get_double_param("tpc_sector_phi_inner"),
771  get_double_param("tpc_sector_phi_mid"),
772  get_double_param("tpc_sector_phi_outer")}};
773 
774  const std::array<int,3> NPhiBins =
775  {{
776  get_int_param("ntpc_phibins_inner"),
777  get_int_param("ntpc_phibins_mid"),
778  get_int_param("ntpc_phibins_outer")
779  }};
780 
781  PhiBinWidth =
782  {{
783  SectorPhi[0] * 12 / (double) NPhiBins[0],
784  SectorPhi[1] * 12 / (double) NPhiBins[1],
785  SectorPhi[2] * 12 / (double) NPhiBins[2]
786  }};
787 
788  averageGEMGain = get_double_param("gem_amplification");
789 
790  for (int iregion = 0; iregion < 3; ++iregion)
791  {
792  for (int zside = 0; zside < 2;zside++)
793  {
794  sector_min_Phi_sectors[zside][iregion].clear();
795  sector_max_Phi_sectors[zside][iregion].clear();
796  for (int isector = 0; isector < NSectors; ++isector)
797  {
798  double sec_gap = (2*M_PI - SectorPhi[iregion]*12)/12;
799  double sec_max_phi = M_PI - SectorPhi[iregion]/2 - sec_gap - 2 * M_PI / 12 * isector;// * (isector+1) ;
800  double sec_min_phi = sec_max_phi - SectorPhi[iregion];
801  sector_min_Phi_sectors[zside][iregion].push_back(sec_min_phi);
802  sector_max_Phi_sectors[zside][iregion].push_back(sec_max_phi);
803 
804  }
805  }
806  }
807 }