Back to home page

sPhenix code displayed by LXR

 
 

    


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

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