Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-05 08:18:18

0001 #include "PHG4TpcPadPlaneReadout.h"
0002 
0003 #include <fun4all/Fun4AllReturnCodes.h>
0004 #include <g4detectors/PHG4CellDefs.h>  // for genkey, keytype
0005 #include <g4detectors/PHG4TpcCylinderGeom.h>
0006 #include <g4detectors/PHG4TpcCylinderGeomContainer.h>
0007 
0008 #include <g4main/PHG4Hit.h>  // for PHG4Hit
0009 #include <g4main/PHG4HitContainer.h>
0010 
0011 #include <phool/PHRandomSeed.h>
0012 #include <phool/getClass.h>
0013 
0014 // Move to new storage containers
0015 #include <trackbase/TpcDefs.h>
0016 #include <trackbase/TrkrDefs.h>  // for hitkey, hitse...
0017 #include <trackbase/TrkrHit.h>   // for TrkrHit
0018 #include <trackbase/TrkrHitSet.h>
0019 #include <trackbase/TrkrHitSetContainer.h>
0020 #include <trackbase/TrkrHitv2.h>  // for TrkrHit
0021 
0022 #include <g4tracking/TrkrTruthTrack.h>
0023 #include <g4tracking/TrkrTruthTrackContainer.h>
0024 #include <g4tracking/TrkrTruthTrackContainerv1.h>
0025 #include <g4tracking/TrkrTruthTrackv1.h>
0026 
0027 #include <phool/phool.h>  // for PHWHERE
0028 
0029 #include <TFile.h>
0030 #include <TH2.h>
0031 #include <TF1.h>
0032 #include <TSystem.h>
0033 
0034 #include <gsl/gsl_randist.h>
0035 #include <gsl/gsl_rng.h>  // for gsl_rng_alloc
0036 
0037 #include <boost/format.hpp>
0038 
0039 #include <cmath>
0040 #include <cstdlib>  // for getenv
0041 #include <iostream>
0042 #include <map>      // for _Rb_tree_cons...
0043 #include <utility>  // for pair
0044 
0045 class PHCompositeNode;
0046 class TrkrHitTruthAssoc;
0047 
0048 namespace
0049 {
0050   //! convenient square function
0051   template <class T>
0052   inline constexpr T square(const T &x)
0053   {
0054     return x * x;
0055   }
0056 
0057   template <class T>
0058   inline T get_r(const T &x, const T &y)
0059   {
0060     return std::sqrt(square(x) + square(y));
0061   }
0062 
0063   //! return normalized gaussian centered on zero and of width sigma
0064   template <class T>
0065   inline T gaus(const T &x, const T &sigma)
0066   {
0067     return std::exp(-square(x / sigma) / 2) / (sigma * std::sqrt(2 * M_PI));
0068   }
0069 
0070   static constexpr unsigned int print_layer = 18;
0071 
0072 }  // namespace
0073 
0074 PHG4TpcPadPlaneReadout::PHG4TpcPadPlaneReadout(const std::string &name)
0075   : PHG4TpcPadPlane(name)
0076 {
0077   InitializeParameters();
0078   // if(m_flagToUseGain==1)
0079   ReadGain();
0080   RandomGenerator = gsl_rng_alloc(gsl_rng_mt19937);
0081   gsl_rng_set(RandomGenerator, PHRandomSeed());  // fixed seed is handled in this funtcion
0082 
0083 
0084   return;
0085 }
0086 
0087 PHG4TpcPadPlaneReadout::~PHG4TpcPadPlaneReadout()
0088 {
0089   gsl_rng_free(RandomGenerator);
0090   for (auto his : h_gain)
0091   {
0092     delete his;
0093   }
0094 }
0095 
0096 //_________________________________________________________
0097 int PHG4TpcPadPlaneReadout::InitRun(PHCompositeNode *topNode)
0098 {
0099   // base class
0100   const auto reply = PHG4TpcPadPlane::InitRun(topNode);
0101   if (reply != Fun4AllReturnCodes::EVENT_OK)
0102   {
0103     return reply;
0104   }
0105   const std::string seggeonodename = "CYLINDERCELLGEOM_SVTX";
0106   GeomContainer = findNode::getClass<PHG4TpcCylinderGeomContainer>(topNode, seggeonodename);
0107   assert(GeomContainer);
0108   if(m_use_module_gain_weights)
0109     {
0110       int side, region, sector;
0111       double weight;
0112       std::ifstream weights_file(m_tpc_module_gain_weights_file);
0113       if(!weights_file.is_open()) 
0114     {
0115       std::cout << ".In PHG4TpcPadPlaneReadout: Option to use module gain weights enabled, but weights file not found. Aborting." << std::endl;
0116       return Fun4AllReturnCodes::ABORTEVENT;
0117     }
0118 
0119       for(int iside =0; iside < 2; ++iside)
0120     {
0121       for(int isec = 0; isec < 12; ++isec)
0122         {
0123           for(int ir = 0; ir < 3; ++ir)
0124         {
0125           weights_file >> side >> region >> sector >> weight;
0126           m_module_gain_weight[side][region][sector] = weight;
0127           std::cout << " iside " << iside << " side " << side << " ir " << ir 
0128                 << " region " << region << " isec " << isec 
0129                 << " sector " << sector << " weight " << weight << std::endl;
0130         }
0131         }
0132     }
0133     }  
0134 
0135   if(m_useLangau)
0136     {
0137       int side, region, sector;
0138       double par0; double par1; double par2; double par3;
0139       std::ifstream pars_file(m_tpc_langau_pars_file);
0140       if(!pars_file.is_open()) 
0141     {
0142       std::cout << ".In PHG4TpcPadPlaneReadout: Option to use Langau parameters enabled, but parameter file not found. Aborting." << std::endl;
0143       return Fun4AllReturnCodes::ABORTEVENT;
0144     }
0145 
0146       for(int iside =0; iside < 2; ++iside)
0147     {
0148       for(int isec = 0; isec < 12; ++isec)
0149         {
0150           for(int ir = 0; ir < 3; ++ir)
0151         {
0152           pars_file >> side >> region >> sector >> par0 >> par1 >> par2 >> par3;
0153           flangau[side][region][sector] = new TF1((boost::format("flangau_%d_%d_%d") % side % region % sector).str().c_str(), [](double *x, double *par) 
0154           {
0155             Double_t invsq2pi = 0.3989422804014;
0156             Double_t mpshift  = -0.22278298;
0157             Double_t np = 100.0;
0158             Double_t sc =   5.0;
0159             Double_t xx;
0160             Double_t mpc;
0161             Double_t fland;
0162             Double_t sum = 0.0;
0163             Double_t xlow,xupp;
0164             Double_t step;
0165             Double_t i;
0166             mpc = par[1] - mpshift * par[0]; 
0167             xlow = x[0] - sc * par[3];
0168             xupp = x[0] + sc * par[3];
0169             step = (xupp-xlow) / np;
0170             for(i=1.0; i<=np/2; i++) 
0171             {
0172               xx = xlow + (i-.5) * step;
0173               fland = TMath::Landau(xx,mpc,par[0]) / par[0];
0174               sum += fland * TMath::Gaus(x[0],xx,par[3]);
0175               
0176               xx = xupp - (i-.5) * step;
0177               fland = TMath::Landau(xx,mpc,par[0]) / par[0];
0178               sum += fland * TMath::Gaus(x[0],xx,par[3]);
0179             }
0180       
0181             return (par[2] * step * sum * invsq2pi / par[3]);
0182           }, 0, 5000, 4);
0183 
0184 
0185           flangau[side][region][sector]->SetParameters(par0,par1,par2,par3);
0186           //std::cout << " iside " << iside << " side " << side << " ir " << ir 
0187           //        << " region " << region << " isec " << isec 
0188           //        << " sector " << sector << " weight " << weight << std::endl;
0189         }
0190         }
0191     }
0192     } 
0193 
0194   
0195 
0196   return Fun4AllReturnCodes::EVENT_OK;
0197 }
0198 
0199 //_________________________________________________________
0200 double PHG4TpcPadPlaneReadout::getSingleEGEMAmplification()
0201 {
0202   // Jin H.: For the GEM gain in sPHENIX TPC,
0203   //         Bob pointed out the PHENIX HBD measured it as the Polya function with theta parameter = 0.8.
0204   //         Just talked with Tom too, he suggest us to start the TPC modeling with simpler exponential function
0205   //         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.
0206   //         Summing over ~30 initial electrons, the distribution is pushed towards more Gauss like.
0207   // Bob A.: I like Tom's suggestion to use the exponential distribution as a first approximation
0208   //         for the single electron gain distribution -
0209   //         and yes, the parameter you're looking for is of course the slope, which is the inverse gain.
0210   double nelec = gsl_ran_exponential(RandomGenerator, averageGEMGain);
0211   if (m_usePolya)
0212   { 
0213     double y;
0214     double xmax = 5000;
0215     double ymax = 0.376;
0216     while (true) 
0217     {
0218       nelec = gsl_ran_flat(RandomGenerator, 0, xmax);
0219       y = gsl_rng_uniform(RandomGenerator) * ymax;
0220       if (y <= pow((1 + polyaTheta) * (nelec / averageGEMGain), polyaTheta) * exp(-(1 + polyaTheta) * (nelec / averageGEMGain)))
0221       {
0222         break;
0223       }
0224     }
0225   }
0226   // Put gain reading here
0227 
0228   return nelec;
0229 }
0230 
0231 //_________________________________________________________
0232 double PHG4TpcPadPlaneReadout::getSingleEGEMAmplification(double weight)
0233 {
0234   // Jin H.: For the GEM gain in sPHENIX TPC,
0235   //         Bob pointed out the PHENIX HBD measured it as the Polya function with theta parameter = 0.8.
0236   //         Just talked with Tom too, he suggest us to start the TPC modeling with simpler exponential function
0237   //         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.
0238   //         Summing over ~30 initial electrons, the distribution is pushed towards more Gauss like.
0239   // Bob A.: I like Tom's suggestion to use the exponential distribution as a first approximation
0240   //         for the single electron gain distribution -
0241   //         and yes, the parameter you're looking for is of course the slope, which is the inverse gain.
0242   double q_bar = averageGEMGain * weight;
0243   double nelec = gsl_ran_exponential(RandomGenerator, q_bar);
0244   if (m_usePolya)
0245   {
0246     double y;
0247     double xmax = 5000;
0248     double ymax = 0.376;
0249     while (true) 
0250     {
0251       nelec = gsl_ran_flat(RandomGenerator, 0, xmax);
0252       y = gsl_rng_uniform(RandomGenerator) * ymax;
0253       if (y <= pow((1 + polyaTheta) * (nelec / q_bar), polyaTheta) * exp(-(1 + polyaTheta) * (nelec / q_bar))) 
0254       {
0255         break;
0256       }
0257     }
0258   }
0259   // Put gain reading here
0260 
0261   return nelec;
0262 }
0263 
0264 //_________________________________________________________
0265 double PHG4TpcPadPlaneReadout::getSingleEGEMAmplification(TF1 *f)
0266 {
0267   double nelec = f->GetRandom(0,5000);
0268   // Put gain reading here
0269 
0270   return nelec;
0271 }
0272 
0273 
0274 
0275 void PHG4TpcPadPlaneReadout::MapToPadPlane(
0276     TpcClusterBuilder &tpc_truth_clusterer,
0277     TrkrHitSetContainer *single_hitsetcontainer,
0278     TrkrHitSetContainer *hitsetcontainer,
0279     TrkrHitTruthAssoc * /*hittruthassoc*/,
0280     const double x_gem, const double y_gem, const double t_gem, const unsigned int side,
0281     PHG4HitContainer::ConstIterator hiter, TNtuple * /*ntpad*/, TNtuple * /*nthit*/)
0282 {
0283   // One electron per call of this method
0284   // The x_gem and y_gem values have already been randomized within the transverse drift diffusion width
0285   // 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
0286 
0287   double phi = atan2(y_gem, x_gem);
0288   if (phi > +M_PI)
0289   {
0290     phi -= 2 * M_PI;
0291   }
0292   if (phi < -M_PI)
0293   {
0294     phi += 2 * M_PI;
0295   }
0296 
0297   double rad_gem = get_r(x_gem, y_gem);
0298 
0299   // Moving electrons from dead area to a closest pad
0300   for (int iregion = 0; iregion < 3; ++iregion)
0301   {
0302     double daR = 0;
0303     if (iregion == 0 || iregion == 2)
0304     {
0305       daR = 1.0;  // 1.0cm edge to collect electrons from
0306     }
0307     else
0308     {
0309       daR = MinRadius[iregion] - MaxRadius[iregion - 1];
0310     }
0311     if (rad_gem <= MinRadius[iregion] && rad_gem >= MinRadius[iregion] - daR)
0312     {
0313       if (rad_gem <= MinRadius[iregion] - daR / 2)
0314       {
0315         rad_gem = MinRadius[iregion] - (1.1 * daR);
0316       }
0317       else
0318       {
0319         rad_gem = MinRadius[iregion] + 0.1 * daR;
0320       }
0321     }
0322   }
0323 
0324    
0325   unsigned int layernum = 0;
0326   /* TpcClusterBuilder pass_data {}; */
0327 
0328   // Find which readout layer this electron ends up in
0329 
0330   PHG4TpcCylinderGeomContainer::ConstRange layerrange = GeomContainer->get_begin_end();
0331   for (PHG4TpcCylinderGeomContainer::ConstIterator layeriter = layerrange.first;
0332        layeriter != layerrange.second;
0333        ++layeriter)
0334   {
0335     double rad_low = layeriter->second->get_radius() - layeriter->second->get_thickness() / 2.0;
0336     double rad_high = layeriter->second->get_radius() + layeriter->second->get_thickness() / 2.0;
0337 
0338     if (rad_gem > rad_low && rad_gem < rad_high)
0339     {
0340       // capture the layer where this electron hits the gem stack
0341       LayerGeom = layeriter->second;
0342 
0343       layernum = LayerGeom->get_layer();
0344       /* pass_data.layerGeom = LayerGeom; */
0345       /* pass_data.layer = layernum; */
0346       if (Verbosity() > 1000)
0347       {
0348         std::cout << " g4hit id " << hiter->first << " rad_gem " << rad_gem << " rad_low " << rad_low << " rad_high " << rad_high
0349                   << " layer  " << hiter->second->get_layer() << " want to change to " << layernum << std::endl;
0350       }
0351       hiter->second->set_layer(layernum);  // have to set here, since the stepping action knows nothing about layers
0352     }
0353   }
0354 
0355   if (layernum == 0)
0356   {
0357     return;
0358   }
0359 
0360   // store phi bins and tbins upfront to avoid repetitive checks on the phi methods
0361   const auto phibins = LayerGeom->get_phibins();
0362   /* pass_data.nphibins = phibins; */
0363 
0364   const auto tbins = LayerGeom->get_zbins();
0365 
0366   sector_min_Phi = LayerGeom->get_sector_min_phi();
0367   sector_max_Phi = LayerGeom->get_sector_max_phi();
0368   phi_bin_width = LayerGeom->get_phistep();
0369 
0370   phi = check_phi(side, phi, rad_gem);
0371 
0372   // Create the distribution function of charge on the pad plane around the electron position
0373 
0374   // The resolution due to pad readout includes the charge spread during GEM multiplication.
0375   // this now defaults to 400 microns during construction from Tom (see 8/11 email).
0376   // Use the setSigmaT(const double) method to update...
0377   // 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
0378 
0379   // amplify the single electron in the gem stack
0380   //===============================
0381 
0382   double nelec = getSingleEGEMAmplification();
0383   // Applying weight with respect to the rad_gem and phi after electrons are redistributed
0384   double phi_gain = phi;
0385   if (phi < 0)
0386   {
0387     phi_gain += 2 * M_PI;
0388   }
0389   double gain_weight = 1.0;
0390   if (m_flagToUseGain == 1)
0391   {
0392     gain_weight = h_gain[side]->GetBinContent(h_gain[side]->FindBin(rad_gem * 10, phi_gain));  // rad_gem in cm -> *10 to get mm
0393     nelec = nelec * gain_weight;
0394   }
0395 
0396   if(m_use_module_gain_weights)
0397     {
0398       double phistep = 30.0;
0399       int sector = 0;
0400 
0401       if( (phi_gain*180.0/M_PI) >=15 && (phi_gain*180.0 / M_PI) < 345)
0402     {
0403       sector = 1 + (int) ( (phi_gain*180.0/M_PI - 15) / phistep);
0404     } 
0405       else
0406     {
0407       sector = 0;
0408     }
0409 
0410       int this_region = -1;
0411       for (int iregion = 0; iregion < 3; ++iregion)
0412     {
0413       if (rad_gem < MaxRadius[iregion] && rad_gem > MinRadius[iregion])
0414         {
0415           this_region = iregion;
0416         }
0417     }
0418       if(this_region > -1) 
0419     {
0420       gain_weight = m_module_gain_weight[side][this_region][sector];
0421     }
0422       // regenerate nelec with the new distribution
0423       //    double original_nelec = nelec; 
0424       nelec = getSingleEGEMAmplification(gain_weight);
0425       //  std::cout << " side " << side << " this_region " << this_region 
0426       //    <<  " sector " << sector << " original nelec " 
0427       //    << original_nelec << " new nelec " << nelec << std::endl;
0428     }
0429 
0430   if(m_useLangau)
0431   {
0432     double phistep = 30.0;
0433     int sector = 0;
0434     
0435     if( (phi_gain*180.0/M_PI) >=15 && (phi_gain*180.0 / M_PI) < 345)
0436     {
0437       sector = 1 + (int) ( (phi_gain*180.0/M_PI - 15) / phistep);
0438     } 
0439     else
0440     {
0441       sector = 0;
0442     }
0443     
0444     int this_region = -1;
0445     for (int iregion = 0; iregion < 3; ++iregion)
0446     {
0447       if (rad_gem < MaxRadius[iregion] && rad_gem > MinRadius[iregion])
0448       {
0449     this_region = iregion;
0450       }
0451     }
0452     if(this_region > -1) 
0453     {
0454       nelec = getSingleEGEMAmplification(flangau[side][this_region][sector]);
0455     }
0456     else 
0457     {
0458       nelec = getSingleEGEMAmplification();
0459     }
0460   }
0461   
0462   // std::cout<<"PHG4TpcPadPlaneReadout::MapToPadPlane gain_weight = "<<gain_weight<<std::endl;
0463   /* pass_data.neff_electrons = nelec; */
0464 
0465   // Distribute the charge between the pads in phi
0466   //====================================
0467 
0468   if (Verbosity() > 200)
0469   {
0470     std::cout << "  populate phi bins for "
0471               << " layernum " << layernum
0472               << " phi " << phi
0473               << " sigmaT " << sigmaT
0474               //<< " zigzag_pads " << zigzag_pads
0475               << std::endl;
0476   }
0477 
0478   std::vector<int> pad_phibin;
0479   std::vector<double> pad_phibin_share;
0480 
0481   populate_zigzag_phibins(side, layernum, phi, sigmaT, pad_phibin, pad_phibin_share);
0482   /* if (pad_phibin.size() == 0) { */
0483   /* pass_data.neff_electrons = 0; */
0484   /* } else { */
0485   /* pass_data.fillPhiBins(pad_phibin); */
0486   /* } */
0487 
0488   // Normalize the shares so they add up to 1
0489   double norm1 = 0.0;
0490   for (unsigned int ipad = 0; ipad < pad_phibin.size(); ++ipad)
0491   {
0492     double pad_share = pad_phibin_share[ipad];
0493     norm1 += pad_share;
0494   }
0495   for (unsigned int iphi = 0; iphi < pad_phibin.size(); ++iphi)
0496   {
0497     pad_phibin_share[iphi] /= norm1;
0498   }
0499 
0500   // Distribute the charge between the pads in t
0501   //====================================
0502   if (Verbosity() > 100 && layernum == print_layer)
0503   {
0504     std::cout << "  populate t bins for layernum " << layernum
0505               << " with t_gem " << t_gem << " sigmaL[0] " << sigmaL[0] << " sigmaL[1] " << sigmaL[1] << std::endl;
0506   }
0507 
0508   std::vector<int> adc_tbin;
0509   std::vector<double> adc_tbin_share;
0510   populate_tbins(t_gem, sigmaL, adc_tbin, adc_tbin_share);
0511   /* if (adc_tbin.size() == 0)  { */
0512   /* pass_data.neff_electrons = 0; */
0513   /* } else { */
0514   /* pass_data.fillTimeBins(adc_tbin); */
0515   /* } */
0516 
0517   // Normalize the shares so that they add up to 1
0518   double tnorm = 0.0;
0519   for (unsigned int it = 0; it < adc_tbin.size(); ++it)
0520   {
0521     double bin_share = adc_tbin_share[it];
0522     tnorm += bin_share;
0523   }
0524   for (unsigned int it = 0; it < adc_tbin.size(); ++it)
0525   {
0526     adc_tbin_share[it] /= tnorm;
0527   }
0528 
0529   // Fill HitSetContainer
0530   //===============
0531   // These are used to do a quick clustering for checking
0532   double phi_integral = 0.0;
0533   double t_integral = 0.0;
0534   double weight = 0.0;
0535 
0536   for (unsigned int ipad = 0; ipad < pad_phibin.size(); ++ipad)
0537   {
0538     int pad_num = pad_phibin[ipad];
0539     double pad_share = pad_phibin_share[ipad];
0540 
0541     for (unsigned int it = 0; it < adc_tbin.size(); ++it)
0542     {
0543       int tbin_num = adc_tbin[it];
0544       double adc_bin_share = adc_tbin_share[it];
0545 
0546       // Divide electrons from avalanche between bins
0547       float neffelectrons = nelec * (pad_share) * (adc_bin_share);
0548       if (neffelectrons < neffelectrons_threshold)
0549       {
0550         continue;  // skip signals that will be below the noise suppression threshold
0551       }
0552 
0553       if (tbin_num >= tbins)
0554       {
0555         std::cout << " Error making key: adc_tbin " << tbin_num << " ntbins " << tbins << std::endl;
0556       }
0557       if (pad_num >= phibins)
0558       {
0559         std::cout << " Error making key: pad_phibin " << pad_num << " nphibins " << phibins << std::endl;
0560       }
0561 
0562       // collect information to do simple clustering. Checks operation of PHG4CylinderCellTpcReco, and
0563       // is also useful for comparison with PHG4TpcClusterizer result when running single track events.
0564       // The only information written to the cell other than neffelectrons is tbin and pad number, so get those from geometry
0565       double tcenter = LayerGeom->get_zcenter(tbin_num);
0566       double phicenter = LayerGeom->get_phicenter(pad_num, side);
0567       phi_integral += phicenter * neffelectrons;
0568       t_integral += tcenter * neffelectrons;
0569       weight += neffelectrons;
0570       if (Verbosity() > 1 && layernum == print_layer)
0571       {
0572         std::cout << "   tbin_num " << tbin_num << " tcenter " << tcenter << " pad_num " << pad_num << " phicenter " << phicenter
0573                   << " neffelectrons " << neffelectrons << " neffelectrons_threshold " << neffelectrons_threshold << std::endl;
0574       }
0575 
0576       // new containers
0577       //============
0578       // We add the Tpc TrkrHitsets directly to the node using hitsetcontainer
0579       // We need to create the TrkrHitSet if not already made - each TrkrHitSet should correspond to a Tpc readout module
0580       // The hitset key includes the layer, sector, side
0581 
0582       // The side is an input parameter
0583 
0584       // get the Tpc readout sector - there are 12 sectors with how many pads each?
0585       unsigned int pads_per_sector = phibins / 12;
0586       unsigned int sector = pad_num / pads_per_sector;
0587       TrkrDefs::hitsetkey hitsetkey = TpcDefs::genHitSetKey(layernum, sector, side);
0588       // Use existing hitset or add new one if needed
0589       TrkrHitSetContainer::Iterator hitsetit = hitsetcontainer->findOrAddHitSet(hitsetkey);
0590       TrkrHitSetContainer::Iterator single_hitsetit = single_hitsetcontainer->findOrAddHitSet(hitsetkey);
0591 
0592       // generate the key for this hit, requires tbin and phibin
0593       TrkrDefs::hitkey hitkey = TpcDefs::genHitKey((unsigned int) pad_num, (unsigned int) tbin_num);
0594       // See if this hit already exists
0595       TrkrHit *hit = nullptr;
0596       hit = hitsetit->second->getHit(hitkey);
0597       if (!hit)
0598       {
0599         // create a new one
0600         hit = new TrkrHitv2();
0601         hitsetit->second->addHitSpecificKey(hitkey, hit);
0602       }
0603       // Either way, add the energy to it  -- adc values will be added at digitization
0604       hit->addEnergy(neffelectrons);
0605 
0606       tpc_truth_clusterer.addhitset(hitsetkey, hitkey, neffelectrons);
0607 
0608       // repeat for the single_hitsetcontainer
0609       // See if this hit already exists
0610       TrkrHit *single_hit = nullptr;
0611       single_hit = single_hitsetit->second->getHit(hitkey);
0612       if (!single_hit)
0613       {
0614         // create a new one
0615         single_hit = new TrkrHitv2();
0616         single_hitsetit->second->addHitSpecificKey(hitkey, single_hit);
0617       }
0618       // Either way, add the energy to it  -- adc values will be added at digitization
0619       single_hit->addEnergy(neffelectrons);
0620 
0621       /*
0622       if (Verbosity() > 0)
0623       {
0624         assert(nthit);
0625         nthit->Fill(layernum, pad_num, tbin_num, neffelectrons);
0626       }
0627       */
0628 
0629     }  // end of loop over adc T bins
0630   }    // end of loop over zigzag pads
0631   /* pass_data.phi_integral = phi_integral; */
0632   /* pass_data.time_integral = t_integral; */
0633 
0634   /*
0635   // Capture the input values at the gem stack and the quick clustering results, elecron-by-electron
0636   if (Verbosity() > 0)
0637   {
0638     assert(ntpad);
0639     ntpad->Fill(layernum, phi, phi_integral / weight, t_gem, t_integral / weight);
0640   }
0641   */
0642 
0643   if (Verbosity() > 100)
0644   {
0645     if (layernum == print_layer)
0646     {
0647       std::cout << " hit " << m_NHits << " quick centroid for this electron " << std::endl;
0648       std::cout << "      phi centroid = " << phi_integral / weight << " phi in " << phi << " phi diff " << phi_integral / weight - phi << std::endl;
0649       std::cout << "      t centroid = " << t_integral / weight << " t in " << t_gem << " t diff " << t_integral / weight - t_gem << std::endl;
0650       // For a single track event, this captures the distribution of single electron centroids on the pad plane for layer print_layer.
0651       // The centroid of that should match the cluster centroid found by PHG4TpcClusterizer for layer print_layer, if everything is working
0652       //   - matches to < .01 cm for a few cases that I checked
0653 
0654       /*
0655       assert(nthit);
0656       nthit->Fill(hit, layernum, phi, phi_integral / weight, t_gem, t_integral / weight, weight);
0657       */
0658     }
0659   }
0660 
0661   m_NHits++;
0662   /* return pass_data; */
0663 }
0664 double PHG4TpcPadPlaneReadout::check_phi(const unsigned int side, const double phi, const double radius)
0665 {
0666   double new_phi = phi;
0667   int p_region = -1;
0668   for (int iregion = 0; iregion < 3; ++iregion)
0669   {
0670     if (radius < MaxRadius[iregion] && radius > MinRadius[iregion])
0671     {
0672       p_region = iregion;
0673     }
0674   }
0675 
0676   if (p_region >= 0)
0677   {
0678     for (int s = 0; s < 12; s++)
0679     {
0680       double daPhi = 0;
0681       if (s == 0)
0682       {
0683         daPhi = fabs(sector_min_Phi[side][11] + 2 * M_PI - sector_max_Phi[side][s]);
0684       }
0685       else
0686       {
0687         daPhi = fabs(sector_min_Phi[side][s - 1] - sector_max_Phi[side][s]);
0688       }
0689       double min_phi = sector_max_Phi[side][s];
0690       double max_phi = sector_max_Phi[side][s] + daPhi;
0691       if (new_phi <= max_phi && new_phi >= min_phi)
0692       {
0693         if (fabs(max_phi - new_phi) > fabs(new_phi - min_phi))
0694         {
0695           new_phi = min_phi - phi_bin_width / 5; 
0696         }
0697         else
0698         {
0699           new_phi = max_phi + phi_bin_width / 5;
0700         }
0701       }
0702     }
0703     if (new_phi < sector_min_Phi[side][11] && new_phi >= -M_PI)
0704     {
0705       new_phi += 2 * M_PI;
0706     }
0707   }
0708 
0709   return new_phi;
0710 }
0711 
0712 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)
0713 {
0714   const double radius = LayerGeom->get_radius();
0715   const double phistepsize = LayerGeom->get_phistep();
0716   const auto phibins = LayerGeom->get_phibins();
0717 
0718   // make the charge distribution gaussian
0719   double rphi = phi * radius;
0720   if (Verbosity() > 100)
0721   {
0722     if (LayerGeom->get_layer() == print_layer)
0723     {
0724       std::cout << " populate_zigzag_phibins for layer " << layernum << " with radius " << radius << " phi " << phi
0725                 << " rphi " << rphi << " phistepsize " << phistepsize << std::endl;
0726       std::cout << " fcharge created: radius " << radius << " rphi " << rphi << " cloud_sig_rp " << cloud_sig_rp << std::endl;
0727     }
0728   }
0729 
0730   // Get the range of phi values that completely contains all pads  that touch the charge distribution - (nsigmas + 1/2 pad width) in each direction
0731   const double philim_low_calc = phi - (_nsigmas * cloud_sig_rp / radius) - phistepsize;
0732   const double philim_high_calc = phi + (_nsigmas * cloud_sig_rp / radius) + phistepsize;
0733 
0734   // Find the pad range that covers this phi range
0735   const double philim_low = check_phi(side, philim_low_calc, radius);
0736   const double philim_high = check_phi(side, philim_high_calc, radius);
0737 
0738   int phibin_low = LayerGeom->get_phibin(philim_high, side);
0739   int phibin_high = LayerGeom->get_phibin(philim_low, side);
0740   int npads = phibin_high - phibin_low;
0741 
0742   if (Verbosity() > 1000)
0743   {
0744     if (layernum == print_layer)
0745     {
0746       std::cout << "           zigzags: phi " << phi << " philim_low " << philim_low << " phibin_low " << phibin_low
0747                 << " philim_high " << philim_high << " phibin_high " << phibin_high << " npads " << npads << std::endl;
0748     }
0749   }
0750 
0751   if (npads < 0 || npads > 9)
0752   {
0753     npads = 9;  // can happen if phibin_high wraps around. If so, limit to 10 pads and fix below
0754   }
0755 
0756   // 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.
0757   const double pad_rphi = 2.0 * LayerGeom->get_phistep() * radius;
0758 
0759   // Make a TF1 for each pad in the phi range
0760   using PadParameterSet = std::array<double, 2>;
0761   std::array<PadParameterSet, 10> pad_parameters{};
0762   std::array<int, 10> pad_keep{};
0763 
0764   // Now make a loop that steps through the charge distribution and evaluates the response at that point on each pad
0765   std::array<double, 10> overlap = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
0766   double pads_phi[10] = {0., 0., 0., 0., 0., 0., 0., 0., 0., 0.};
0767   double sum_of_pads_phi = 0.;
0768   double sum_of_pads_absphi = 0.;
0769   for (int ipad = 0; ipad <= npads; ipad++)
0770   {
0771     int pad_now = phibin_low + ipad;
0772     if (pad_now >= phibins)
0773     {
0774       pad_now -= phibins;
0775     }
0776     pads_phi[ipad] = LayerGeom->get_phicenter(pad_now, side);
0777     sum_of_pads_phi += pads_phi[ipad];
0778     sum_of_pads_absphi += fabs(pads_phi[ipad]);
0779   }
0780 
0781   for (int ipad = 0; ipad <= npads; ipad++)
0782   {
0783     int pad_now = phibin_low + ipad;
0784     // if(phibin_low<0 && phibin_high<0) pad_now = phibin_high + ipad;
0785     //  check that we do not exceed the maximum number of pads, wrap if necessary
0786     if (pad_now >= phibins)
0787     {
0788       pad_now -= phibins;
0789     }
0790 
0791     pad_keep[ipad] = pad_now;
0792     double phi_now = pads_phi[ipad];
0793     const double rphi_pad_now = phi_now * radius;
0794     pad_parameters[ipad] = {{pad_rphi / 2.0, rphi_pad_now}};
0795 
0796     if (Verbosity() > 1000)
0797     {
0798       if (layernum == print_layer)
0799       {
0800         std::cout << " zigzags: make fpad for ipad " << ipad << " pad_now " << pad_now << " pad_rphi/2 " << pad_rphi / 2.0
0801                   << " rphi_pad_now " << rphi_pad_now << std::endl;
0802       }
0803     }
0804     //}
0805 
0806     // use analytic integral
0807     // for (int ipad = 0; ipad <= npads; ipad++)
0808     //{
0809     // const double pitch = pad_parameters[ipad][0];
0810     // const double x_loc = pad_parameters[ipad][1] - rphi;
0811     // const double sigma = cloud_sig_rp;
0812 
0813     const double pitch = pad_rphi / 2.0;     // eshulga
0814     double x_loc_tmp = rphi_pad_now - rphi;  // eshulga
0815     const double sigma = cloud_sig_rp;       // eshulga
0816 
0817     // Checking if the pads are on the same side of the TPC in phi
0818     if (fabs(sum_of_pads_phi) != sum_of_pads_absphi)
0819     {
0820       if (phi < -M_PI / 2 && phi_now > 0)
0821       {
0822         x_loc_tmp = (phi_now - 2 * M_PI) * radius - rphi;
0823       }
0824       if (phi > M_PI / 2 && phi_now < 0)
0825       {
0826         x_loc_tmp = (phi_now + 2 * M_PI) * radius - rphi;
0827       }
0828       if (phi < 0 && phi_now > 0)
0829       {
0830         x_loc_tmp = (phi_now + fabs(phi)) * radius;
0831       }
0832       if (phi > 0 && phi_now < 0)
0833       {
0834         x_loc_tmp = (2 * M_PI - phi_now + phi) * radius;
0835       }
0836     }
0837 
0838     const double x_loc = x_loc_tmp;
0839     // calculate fraction of the total charge on this strip
0840     /*
0841     this corresponds to integrating the charge distribution Gaussian function (centered on rphi and of width cloud_sig_rp),
0842     convoluted with a strip response function, which is triangular from -pitch to +pitch, with a maximum of 1. at stript center
0843     */
0844     overlap[ipad] =
0845         (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;
0846   }
0847 
0848   // now we have the overlap for each pad
0849   for (int ipad = 0; ipad <= npads; ipad++)
0850   {
0851     phibin_pad.push_back(pad_keep[ipad]);
0852     phibin_pad_share.push_back(overlap[ipad]);
0853   }
0854 
0855   return;
0856 }
0857 
0858 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)
0859 {
0860   int tbin = LayerGeom->get_zbin(t);
0861   if (tbin < 0 || tbin > LayerGeom->get_zbins())
0862   {
0863     if (Verbosity() > 0)
0864     {
0865       std::cout << " t bin " << tbin << " for time " << t << " is outside range of " << LayerGeom->get_zbins() << " so return" << std::endl;
0866     }
0867     return;
0868   }
0869 
0870   double tstepsize = LayerGeom->get_zstep();
0871   double tdisp = t - LayerGeom->get_zcenter(tbin);
0872 
0873   if (Verbosity() > 1000)
0874   {
0875     std::cout << "     input:  t " << t << " tbin " << tbin << " tstepsize " << tstepsize << " t center " << LayerGeom->get_zcenter(tbin) << " tdisp " << tdisp << std::endl;
0876   }
0877 
0878   // Because of diffusion, hits can be shared across the membrane, so we allow all t bins
0879   int min_cell_tbin = 0;
0880   int max_cell_tbin = NTBins - 1;
0881 
0882   double cloud_sig_tt_inv[2];
0883   cloud_sig_tt_inv[0] = 1. / cloud_sig_tt[0];
0884   cloud_sig_tt_inv[1] = 1. / cloud_sig_tt[1];
0885 
0886   int zsect = 0;
0887   if (t < 0)
0888   {
0889     zsect = -1;
0890   }
0891   else
0892   {
0893     zsect = 1;
0894   }
0895 
0896   int n_zz = int(3 * (cloud_sig_tt[0] + cloud_sig_tt[1]) / (2.0 * tstepsize) + 1);
0897   if (Verbosity() > 1000)
0898   {
0899     std::cout << " n_zz " << n_zz << " cloud_sigzz[0] " << cloud_sig_tt[0] << " cloud_sig_tt[1] " << cloud_sig_tt[1] << std::endl;
0900   }
0901   for (int it = -n_zz; it != n_zz + 1; ++it)
0902   {
0903     int cur_t_bin = tbin + it;
0904     if ((cur_t_bin < min_cell_tbin) || (cur_t_bin > max_cell_tbin))
0905     {
0906       continue;
0907     }
0908 
0909     if (Verbosity() > 1000)
0910     {
0911       std::cout << " it " << it << " cur_t_bin " << cur_t_bin << " min_cell_tbin " << min_cell_tbin << " max_cell_tbin " << max_cell_tbin << std::endl;
0912     }
0913 
0914     double t_integral = 0.0;
0915     if (it == 0)
0916     {
0917       // the crossover between lead and tail shaping occurs in this bin
0918       int index1 = -1;
0919       int index2 = -1;
0920       if (zsect == -1)
0921       {
0922         index1 = 0;
0923         index2 = 1;
0924       }
0925       else
0926       {
0927         index1 = 1;
0928         index2 = 0;
0929       }
0930 
0931       double tLim1 = 0.0;
0932       double tLim2 = 0.5 * M_SQRT2 * (-0.5 * tstepsize - tdisp) * cloud_sig_tt_inv[index1];
0933       // 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
0934       double t_integral1 = 0.5 * (erf(tLim1) - erf(tLim2));
0935 
0936       if (Verbosity() > 1000)
0937       {
0938         if (LayerGeom->get_layer() == print_layer)
0939         {
0940           std::cout << "   populate_tbins:  cur_t_bin " << cur_t_bin << "  center t " << LayerGeom->get_zcenter(cur_t_bin)
0941                     << " index1 " << index1 << "  tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral1 " << t_integral1 << std::endl;
0942         }
0943       }
0944 
0945       tLim2 = 0.0;
0946       tLim1 = 0.5 * M_SQRT2 * (0.5 * tstepsize - tdisp) * cloud_sig_tt_inv[index2];
0947       double t_integral2 = 0.5 * (erf(tLim1) - erf(tLim2));
0948 
0949       if (Verbosity() > 1000)
0950       {
0951         if (LayerGeom->get_layer() == print_layer)
0952         {
0953           std::cout << "   populate_tbins:  cur_t_bin " << cur_t_bin << "  center t " << LayerGeom->get_zcenter(cur_t_bin)
0954                     << " index2 " << index2 << "  tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral2 " << t_integral2 << std::endl;
0955         }
0956       }
0957 
0958       t_integral = t_integral1 + t_integral2;
0959     }
0960     else
0961     {
0962       // The non zero bins are entirely in the lead or tail region
0963       // lead or tail depends on which side of the membrane
0964       int index = 0;
0965       if (it < 0)
0966       {
0967         if (zsect == -1)
0968         {
0969           index = 0;
0970         }
0971         else
0972         {
0973           index = 1;
0974         }
0975       }
0976       else
0977       {
0978         if (zsect == -1)
0979         {
0980           index = 1;
0981         }
0982         else
0983         {
0984           index = 0;
0985         }
0986       }
0987       double tLim1 = 0.5 * M_SQRT2 * ((it + 0.5) * tstepsize - tdisp) * cloud_sig_tt_inv[index];
0988       double tLim2 = 0.5 * M_SQRT2 * ((it - 0.5) * tstepsize - tdisp) * cloud_sig_tt_inv[index];
0989       t_integral = 0.5 * (erf(tLim1) - erf(tLim2));
0990 
0991       if (Verbosity() > 1000)
0992       {
0993         if (LayerGeom->get_layer() == print_layer)
0994         {
0995           std::cout << "   populate_tbins:  t_bin " << cur_t_bin << "  center t " << LayerGeom->get_zcenter(cur_t_bin)
0996                     << " index " << index << "  tLim1 " << tLim1 << " tLim2 " << tLim2 << " t_integral " << t_integral << std::endl;
0997         }
0998       }
0999     }
1000 
1001     tbin_adc.push_back(cur_t_bin);
1002     tbin_adc_share.push_back(t_integral);
1003   }
1004 
1005   return;
1006 }
1007 
1008 void PHG4TpcPadPlaneReadout::UseGain(const int flagToUseGain)
1009 {
1010   m_flagToUseGain = flagToUseGain;
1011   if (m_flagToUseGain == 1 && Verbosity() > 0)
1012   {
1013     std::cout << "PHG4TpcPadPlaneReadout: UseGain: TRUE " << std::endl;
1014   }
1015 }
1016 
1017 void PHG4TpcPadPlaneReadout::ReadGain()
1018 {
1019   // Reading TPC Gain Maps from the file
1020   if (m_flagToUseGain == 1)
1021   {
1022     char *calibrationsroot = getenv("CALIBRATIONROOT");
1023     if (calibrationsroot != nullptr)
1024     {
1025       std::string gain_maps_filename = std::string(calibrationsroot) + std::string("/TPC/GainMaps/TPCGainMaps.root");
1026       TFile *fileGain = TFile::Open(gain_maps_filename.c_str(), "READ");
1027       h_gain[0] = (TH2 *) fileGain->Get("RadPhiPlot0")->Clone();
1028       h_gain[1] = (TH2 *) fileGain->Get("RadPhiPlot1")->Clone();
1029       h_gain[0]->SetDirectory(nullptr);
1030       h_gain[1]->SetDirectory(nullptr);
1031       fileGain->Close();
1032     }
1033   }
1034 }
1035 void PHG4TpcPadPlaneReadout::SetDefaultParameters()
1036 {
1037   set_default_double_param("tpc_minradius_inner", 31.105);  // 30.0);  // cm
1038   set_default_double_param("tpc_minradius_mid", 41.153);    // 40.0);
1039   set_default_double_param("tpc_minradius_outer", 58.367);  // 60.0);
1040 
1041   set_default_double_param("tpc_maxradius_inner", 40.249);  // 40.0);  // cm
1042   set_default_double_param("tpc_maxradius_mid", 57.475);    // 60.0);
1043   set_default_double_param("tpc_maxradius_outer", 75.911);  // 77.0);  // from Tom
1044 
1045   set_default_double_param("neffelectrons_threshold", 1.0);
1046   set_default_double_param("maxdriftlength", 105.5);     // cm
1047   set_default_double_param("tpc_adc_clock", 53.326184);  // ns, for 18.8 MHz clock
1048   set_default_double_param("gem_cloud_sigma", 0.04);     // cm = 400 microns
1049   set_default_double_param("sampa_shaping_lead", 32.0);  // ns, for 80 ns SAMPA
1050   set_default_double_param("sampa_shaping_tail", 48.0);  // ns, for 80 ns SAMPA
1051 
1052   set_default_double_param("tpc_sector_phi_inner", 0.5024);  // 2 * M_PI / 12 );//sector size in phi for R1 sector
1053   set_default_double_param("tpc_sector_phi_mid", 0.5087);    // 2 * M_PI / 12 );//sector size in phi for R2 sector
1054   set_default_double_param("tpc_sector_phi_outer", 0.5097);  // 2 * M_PI / 12 );//sector size in phi for R3 sector
1055 
1056   set_default_int_param("ntpc_phibins_inner", 1128);  // 94 * 12
1057   set_default_int_param("ntpc_phibins_mid", 1536);    // 128 * 12
1058   set_default_int_param("ntpc_phibins_outer", 2304);  // 192 * 12
1059 
1060   // GEM Gain
1061   /*
1062   hp (2020/09/04): gain changed from 2000 to 1400, to accomodate gas mixture change
1063   from Ne/CF4 90/10 to Ne/CF4 50/50, and keep the average charge per particle per pad constant
1064   */
1065   set_default_double_param("gem_amplification", 1400);
1066   set_default_double_param("polya_theta", 0.8);
1067   return;
1068 }
1069 
1070 void PHG4TpcPadPlaneReadout::UpdateInternalParameters()
1071 {
1072   neffelectrons_threshold = get_double_param("neffelectrons_threshold");
1073 
1074   MinRadius =
1075       {{get_double_param("tpc_minradius_inner"),
1076         get_double_param("tpc_minradius_mid"),
1077         get_double_param("tpc_minradius_outer")}};
1078 
1079   MaxRadius =
1080       {{get_double_param("tpc_maxradius_inner"),
1081         get_double_param("tpc_maxradius_mid"),
1082         get_double_param("tpc_maxradius_outer")}};
1083 
1084   sigmaT = get_double_param("gem_cloud_sigma");
1085   sigmaL = {{get_double_param("sampa_shaping_lead"),
1086              get_double_param("sampa_shaping_tail")}};
1087 
1088   const double tpc_adc_clock = get_double_param("tpc_adc_clock");
1089 
1090   const double MaxZ = get_double_param("maxdriftlength");
1091   const double TBinWidth = tpc_adc_clock;
1092   const double MaxT = extended_readout_time + 2.0 * MaxZ / drift_velocity;  // allows for extended time readout
1093   const double MinT = 0;
1094   NTBins = (int) ((MaxT - MinT) / TBinWidth) + 1;
1095 
1096 
1097   averageGEMGain = get_double_param("gem_amplification");
1098   polyaTheta = get_double_param("polya_theta");
1099 
1100 }