Main Page | Modules | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

SimPmtUTM16.cxx

Go to the documentation of this file.
00001 #include "SimPmtUTM16.h"
00002 #include "SimPmtMaker.h"
00003 #include "SimPmtM16CrosstalkTable.h"
00004 #include "SimPmtM16Crosstalk.h"
00005 #include "MessageService/MsgService.h"
00006 #include "MessageService/MsgFormat.h"
00007 #include "Conventions/Munits.h"
00008 //#include "TF1.h"
00009 
00010 #include <cmath>
00011 using std::pow;
00012 using std::sqrt;
00013 
00014 #include <cassert>
00015 // Author: Mike Kordosky : kordosky@hep.utexas.edu
00016 //
00017 // Default sec values taken from original sim by J.Thomas & R.Saakyan
00018 // This is what is in gminos.  The parameters are:
00019 //
00020 //  double r[12]={2.4,2.4,2.4
00021 //            1.0,1.0,1.0,
00022 //            1.0,1.0,1.0,
00023 //            1.0,1.2, 2.4}; // in 100kohm units, for a total of 17.8
00024 //  double a = 0.1472;
00025 //  double b = 0.76;
00026 //  double hv = 755;
00027 //
00028 // dynode emmission coefficients are given by
00029 // se[i]=a*pow(hv*r[i]/rtot,b)
00030 //
00031 // These have been pre-calculated below:
00032 //
00033 
00034 const double SimPmtUTM16::gkDefaultSECValues[12] = {4.9407, 4.9407, 4.9407,
00035                                                     2.53997, 2.53997, 2.53997,
00036                                                     2.53997, 2.53997, 2.53997,
00037                                                     2.53997, 2.91747, 4.9407};
00038 
00039 // the gain of the tube is the product of the emission coefficients
00040 const double SimPmtUTM16::gkDefaultGain = SimPmtUTM16::gkDefaultSECValues[0]
00041 *SimPmtUTM16::gkDefaultSECValues[1]
00042 *SimPmtUTM16::gkDefaultSECValues[2]
00043 *SimPmtUTM16::gkDefaultSECValues[3]
00044 *SimPmtUTM16::gkDefaultSECValues[4]
00045 *SimPmtUTM16::gkDefaultSECValues[5]
00046 *SimPmtUTM16::gkDefaultSECValues[6]
00047 *SimPmtUTM16::gkDefaultSECValues[7]
00048 *SimPmtUTM16::gkDefaultSECValues[8]
00049 *SimPmtUTM16::gkDefaultSECValues[9]
00050 *SimPmtUTM16::gkDefaultSECValues[10]
00051 *SimPmtUTM16::gkDefaultSECValues[11];
00052 
00053 // global xtalk scaling parameters
00054 double SimPmtUTM16::gOpticalXTScale=1.75;
00055 double SimPmtUTM16::gOpticalXTOffset=0.0;
00056 double SimPmtUTM16::gElectricXTScale=0.75;
00057 double SimPmtUTM16::gElectricXTOffset=-0.001;
00058 
00059 // global timing parameters
00060 double SimPmtUTM16::gTransitTimeNS=8.8; // in ns
00061 double SimPmtUTM16::gTransitTimeJitterNS=0.180; // in ns
00062 
00063 // init crosstalk table to zero
00064 SimPmtM16CrosstalkTable* SimPmtUTM16::gCrosstalkTable=0;
00065 
00066 // Register this class with the factory.
00067 SimPmtMakerProxy<SimPmtUTM16> gSimPmtUTM16Proxy("SimPmtUTM16");
00068 
00069 CVSID("$Id: SimPmtUTM16.cxx,v 1.23 2005/05/13 10:57:19 tagg Exp $");
00070 
00071 ClassImp(SimPmtUTM16)
00072 
00073 SimPmtUTM16::SimPmtUTM16(PlexPixelSpotId tube, 
00074                          VldContext context,
00075                          TRandom* random)  :
00076   SimPmt(tube,context,random,16,8),
00077   fLastGainValidityId(-999)
00078 {
00079   
00080   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00081   //
00082   // This is a constructor
00083 
00084   // R1.17: Do this once at the start.
00085   fSECValuesBuilt = false;
00086 
00087   MSG("DetSim",Msg::kDebug) << "SimPmtUTM16 Constructor, Tube :" << fTube.AsString() << endl;
00088 
00089 }
00090 
00091 void SimPmtUTM16::Reset( const VldContext& context )
00092 {
00093   // Author: N. Tagg
00094   //
00095   // Resets the PMT for a new event at a new context.
00096   // Ensures the correct DB is used.
00097 
00098   // Reset the PMT data.
00099   SimPmt::Reset(context);
00100 
00101   // Pulls new xtalk data if available, or 
00102   // gets it for the first time if it doesn't yet exist.
00103   if(gCrosstalkTable) {
00104     gCrosstalkTable->Reset(context);
00105   } else {
00106     gCrosstalkTable = new SimPmtM16CrosstalkTable(context);
00107   }
00108 
00109   // R1.17 Reboot the model:
00110   if(fsRebuildGainMap || (!fSECValuesBuilt)) 
00111     fSECValuesBuilt = InitSECValues();
00112 
00113   if(!fSECValuesBuilt){
00114     MSG("DetSim", Msg::kFatal)<<"Could not initialize SEC values!"<<endl;
00115     assert(0); // make program bomb out... is this the right thing to do?    
00116   }
00117 }
00118 
00119 void SimPmtUTM16::SimulatePmt()
00120 {
00121   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00122 
00123   if(fsPmtDoOpticalCrosstalk) SimulateOpticalXtalk();
00124   else CopyPEtoPEXtalk();  // A null operation.
00125 
00126   if(fsPmtDoDarkNoise) SimulateDarkNoise();
00127   SimulateCharges(); // internally uses fElecticXtalk, fNonLinearity
00128   
00129 }
00130 
00131 
00132 void SimPmtUTM16::Print(Option_t* option) const
00133 {
00134   printf("  SimPmtUTM16::Print()  -- Tube: %s\n",fTube.AsString("t"));
00135   
00136   std::string opt = option;
00137   Bool_t spots = (opt.find("spot") != std::string::npos);;
00138 
00139   for(SimPmtBucketIterator it(*this); !it.End(); it.Next()) {
00140     int ibucket = it.BucketId();
00141     
00142   
00143     printf("  Time bucket %d:\n",ibucket);
00144 
00145     // output:
00146     // pixels:  16 15 14 13   fibres:  8 7 6
00147     //          12 11 10  9             5 4
00148     //           8  7  6  5            3 2 1
00149     //           4  3  2  1
00150   
00151     if(spots) { 
00152       printf("  Photoelectrons(Npe)\n");
00153       for(int row=0; row < 4; row++) {
00154         printf("  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f\n",
00155                GetPe(16-row*4,8,ibucket),  GetPe(16-row*4,7,ibucket),  GetPe(16-row*4,6,ibucket),
00156                GetPe(15-row*4,8,ibucket),  GetPe(15-row*4,7,ibucket),  GetPe(15-row*4,6,ibucket),
00157                GetPe(14-row*4,8,ibucket),  GetPe(14-row*4,7,ibucket),  GetPe(14-row*4,6,ibucket),
00158                GetPe(13-row*4,8,ibucket),  GetPe(13-row*4,7,ibucket),  GetPe(13-row*4,6,ibucket));
00159 
00160         printf("     %5.0f %5.0f        %5.0f %5.0f        %5.0f %5.0f        %5.0f %5.0f\n",
00161                GetPe(16-row*4,5,ibucket),  GetPe(16-row*4,4,ibucket),
00162                GetPe(15-row*4,5,ibucket),  GetPe(15-row*4,4,ibucket),
00163                GetPe(14-row*4,5,ibucket),  GetPe(14-row*4,4,ibucket),
00164                GetPe(13-row*4,5,ibucket),  GetPe(13-row*4,4,ibucket));
00165     
00166         printf("  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f  %5.0f %5.0f %5.0f\n",
00167                GetPe(16-row*4,3,ibucket),  GetPe(16-row*4,2,ibucket),  GetPe(16-row*4,1,ibucket),
00168                GetPe(15-row*4,3,ibucket),  GetPe(15-row*4,2,ibucket),  GetPe(15-row*4,1,ibucket),
00169                GetPe(14-row*4,3,ibucket),  GetPe(14-row*4,2,ibucket),  GetPe(14-row*4,1,ibucket),
00170                GetPe(13-row*4,3,ibucket),  GetPe(13-row*4,2,ibucket),  GetPe(13-row*4,1,ibucket));
00171            
00172         printf("\n");
00173       }
00174     }
00175   
00176   
00177     printf("\n        PEs                   PE with Xtalk         Charges (fC)\n");
00178     for(int row=0; row < 4; row++) {
00179       printf("    %4d %4d %4d %4d",
00180              (int)GetPe(16-row*4,0,ibucket), (int)GetPe(15-row*4,0,ibucket),
00181              (int)GetPe(14-row*4,0,ibucket), (int)GetPe(13-row*4,0,ibucket));
00182     
00183       printf("    %4d %4d %4d %4d",
00184              (int)GetPeXtalk(16-row*4,0,ibucket), (int)GetPeXtalk(15-row*4,0,ibucket),
00185              (int)GetPeXtalk(14-row*4,0,ibucket), (int)GetPeXtalk(13-row*4,0,ibucket));
00186 
00187       printf("    %6.0f %6.0f %6.0f %6.0f\n",
00188              GetCharge(16-row*4,ibucket)/Munits::fC, GetCharge(15-row*4,ibucket)/Munits::fC,
00189              GetCharge(14-row*4,ibucket)/Munits::fC, GetCharge(13-row*4,ibucket)/Munits::fC);
00190     }
00191   
00192   }
00193   
00194   printf("\n");
00195 }
00196 
00197 double SimPmtUTM16::FractionalWidthToSEC(double fw)
00198 {
00199   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00200   //
00201   // Given a fractional width, defined as:
00202   // width of 1pe peak / mean of 1pe peak
00203   // this routine calculates the secondary emission
00204   // coefficient at the first 3 stages.
00205   // The calculation implicitly assumes that:
00206   // (1) We are using the minos M16 base
00207   // (2) Stages with the same voltage drop have the
00208   //     same secondary emmisson coefficient.
00209   //
00210   // For justification of the latter point see:
00211   // "Photomultiplier Tube", Hamamatsu Photonics, ed H. Kume, 1994
00212   //      Equation 3.3
00213   //
00214   //
00215   // A comment from PmtSim.cxx:
00216   // Secondary emmission ratio is derived like this:
00217   //  return (gain*gain)/(width*width);
00218   //
00219   // The result given above is the first order approximation.
00220   // It is generally good to the ~10% level.
00221   // For m16s with the minos base, an approximation good to
00222   // approximately 1%:
00223   // (width/gain)^2 ~= 1/sec + 1/sec^2 + 1/sec^3
00224   // (See NuMI-L-661, equation 12) 
00225   //
00226 
00227   // solve cubic equation for 1/sec
00228 
00229   const double default_sec=-1.0;
00230 
00231   // precondition
00232   // This is really a check on what emerges from the db
00233   // This is a rather loose criterion.  Typically
00234   // width/gain ~ 1/2.
00235   if((fw<0.01) || (fw>3.0)){
00236     MAXMSG("DetSim", Msg::kWarning,20)
00237       <<"FractionalWidthToSEC was passed the fractional width: "<<fw
00238       <<" which is not a reasonable value.\n"
00239       <<"This routine will return "<<default_sec
00240       <<" as the secondary emmission coefficient."<<endl;
00241     
00242     return default_sec;
00243   }
00244   
00245   /*
00246   // find solution
00247   // formula taken from:
00248   // "Mathematical Handbook of Formulas and Tables", Spiegel(1992)
00249   static const double Q= -2.0/9.0;
00250   const double R = 7.0 + 27.0*fw*fw;
00251   // the discriminant D = Q^3 + R^2 >0 always:
00252   const double D= Q*Q*Q + R*R;
00253   const double S=pow(R+sqrt(D),1.0/3.0);
00254   const double T=pow(R-sqrt(D),1.0/3.0);
00255   // This yields 1 real root and 2 generally complex roots.
00256   // The real root is:
00257   const double secinv= S + T - 1.0/3.0;
00258   // The secondary emission coefficient is:
00259   */
00260 
00261   // taken from numerical methods
00262   //
00263   const double Q = -2.0/9.0;
00264   const double R = (-7.0 - 27.0*fw*fw)/54.0;
00265   
00266   double secinv=1.0/default_sec;
00267   if( (R*R)<(Q*Q*Q)) {
00268     // three real roots
00269     MSG("DetSim", Msg::kVerbose)<<"FractionalWidthToSEC("<<fw<<") has 3 real roots!\n"
00270                               <<"Will use first order value: "<<1.0/(fw*fw)<<endl;
00271     return 1.0/(fw*fw);
00272   }
00273   else{
00274     const double A = pow(fabs(R) + sqrt(R*R - Q*Q*Q),1.0/3.0);
00275     double B=0.0; if(A!=0.0) B=Q/A;
00276     secinv = (A+B) - 1.0/3.0;
00277     MSG("DetSim", Msg::kVerbose)<<"FractionalWidthToSEC("<<fw<<") has 1 real root="
00278                               <<1.0/secinv<<endl;  
00279   }
00280 
00281   const double sec=1.0/secinv;
00282   return sec;
00283   
00284 }
00285 
00286 void SimPmtUTM16::SimulateCharges()
00287 {
00288 
00289   MSG("DetSim", Msg::kVerbose)
00290     <<"Calling SimPmtUTM16::SimulateCharges()"<<endl;
00291   
00292   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00293   //
00294   // basic structure taken from SimPmt::SimulateCharges()
00295   // this proceedure simulates charge xtalk in parallel
00296   // with the dynode amplification process
00297   //
00298   fTotalCharge = 0;
00299   // Iterate over all time buckets.
00300   MSG("DetSim", Msg::kVerbose)<<"Iterating over time buckets now."<<endl;
00301 
00302   int cntr=1;
00303   for(SimPmtBucketIterator it(*this); !it.End(); it.Next()) {
00304     SimPmtTimeBucket& pmttb = it.Bucket();
00305     MSG("DetSim", Msg::kVerbose)<<"Working on time bucket: "<<cntr<<endl;
00306     cntr++;
00307     // zero charge in all pixels of this time bucket
00308     // original code did the equivalent
00309     // not sure if it is really needed
00310     for(int pix=1; pix<=fNPixels; pix++) pmttb.GetPixelBucket(pix).SetCharge(0);
00311     // Iterate over hit pixels in this time bucket.
00312     MSG("DetSim", Msg::kVerbose)<<"Iterating over hit pixels."<<endl;
00313     for(Int_t hitpix = 1; hitpix <= fNPixels; hitpix++) {
00314       MSG("DetSim", Msg::kVerbose)<<"Working on pixel: "<<hitpix<<endl;
00315       bool oksim=false;
00316       for(Int_t spot = 1; spot <= fNSpots; spot++) {
00317         const double hitpe = pmttb.GetPixelBucket(hitpix).GetPEXtalk(spot);
00318         MSG("DetSim", Msg::kVerbose)<<"Working on spot: "<<spot
00319                                   <<" got hitpe= "<<hitpe<<endl;
00320         if(hitpe<=0.0) continue;
00321         // these collections will hold the results of the simulation
00322         std::vector<double> qvec(fNPixels+1, 0.0); // charge
00323         std::vector<double> tvec(fNPixels+1, 0.0); // transit time
00324         MSG("DetSim", Msg::kVerbose)<<"Calling OneHitSimDynodeChain("
00325                                   <<hitpix<<", "<<spot<<", "<<hitpe
00326                                   <<", qvec, tvec)"<<endl;      
00327         oksim = OneHitSimDynodeChain(hitpix, spot, hitpe, qvec, tvec);
00328         // add the results to the pixel time buckets
00329         if(oksim){
00330           for(int outpix=1; outpix<=fNPixels; outpix++){
00331             const double generated_charge = qvec[outpix-1];
00332             MSG("DetSim", Msg::kVerbose)<<"Generated charge="<<generated_charge
00333                                       <<" on pixel "<<outpix
00334                                       <<endl;
00335             pmttb.GetPixelBucket(outpix).AddCharge(generated_charge);
00336             // if this is not the hit pixel, then any charge is from xtalk
00337             if(generated_charge>0 && outpix!=hitpix){
00338               // flip that xtalk bit
00339               pmttb.GetPixelBucket(outpix).
00340                 SetTruthBit(DigiSignal::kCrosstalk);
00341             }
00342           } 
00343           pmttb.AddTotalCharge(qvec[fNPixels]);
00344           fTotalCharge+=qvec[fNPixels];
00345           // what do I do with the transit time information?
00346         }
00347         else{
00348           MSG("DetSim", Msg::kError)<<"Error in OneHitSimDynodeChain.\nDid not place any charge into the time bucket.\nWill try to continue."<<endl;
00349         }
00350       } // spots
00351     } // pixels
00352    } // buckets.
00353 }
00354 
00355 bool SimPmtUTM16::OneHitSimDynodeChain(Int_t hp, Int_t hs, Float_t hpe,
00356                             std::vector<double>& qvec,
00357                             std::vector<double>& tvec)
00358 {
00359   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00360   //
00361   // Starting with pes on one pixel, this routine
00362   // simulates the secondary emission process at each dynode
00363   // stage.  At each stage after the first, prior to the calculation
00364   // of secondary electrons, this routine generates xtalk by leaking
00365   // some fraction of the charge in the hit pixel (at the current stage)
00366   // into the other pixels of the tube.  The leakage is done in a stochasitic
00367   // fashion with the average fraction of charge leaked taken from a database
00368   // table. After the simulation of charge leakage the number of secondary
00369   // electrons for each pixel is calculated by sampling a poisson distribution
00370   // with a mean given by:
00371   // (charge at current stage and pixel)
00372   // *(secondary emission coefficient at current stage and pixel)
00373   //
00374   // The charges created in this procedure are ADDED to what is already
00375   // in qvec.
00376 
00377   // NOTE:  Much of the rescaling work can probably be moved outside 
00378   // of this function so as to limit the number of times it must be done.
00379   // MAK 2003.05.12 -- now done
00380 
00381   // If the function was passed zero pes then quit.
00382   if(hpe<=0.0) return true;
00383 
00384   // Set some array indices
00385   const int hpit=hp-1;
00386   const int hsit=hs-1;
00387   MSG("DetSim", Msg::kVerbose)<<"Calculating gains."<<endl;
00388   // calculate the gains at each pixel
00389   double gains[16];
00390   for(int pxit=0; pxit<16; pxit++){
00391     gains[pxit]=1.0;
00392     for(int stage=0; stage<12; stage++){
00393       // for the hit pixel, we can use the exact gain 
00394       // since the spot is known
00395       if(pxit==hpit) gains[pxit]*=fSECValues[hpit][hsit][stage];
00396       // for xtalk pixels use an average gain
00397       else gains[pxit]*=fAverageSECValues[pxit][stage];
00398     }
00399   }
00400 
00401   // Now simulate dynode amplification process
00402   // This includes electric xtalk and pmt nonlinearity
00403   
00404   // an array to hold the charge on each pixel
00405   // gets updated as this routine proceeds
00406   unsigned int qarray[16]={0};
00407   
00408   // look up electric xtalk fractions
00409   // hold the fractions in xtfrac 
00410   MSG("DetSim", Msg::kVerbose)<<"Looking up xtalk fractions."<<endl;
00411   double xtfrac[16]={0.0};
00412   for(int pxit=0; pxit<16; pxit++){
00413     if(pxit==hpit) continue;
00414     const double got_xtfrac=RetrieveElectricXtalkFraction(hp,hs,pxit+1);
00415     if(got_xtfrac<0.0) xtfrac[pxit]=0.0;
00416     else xtfrac[pxit]=got_xtfrac;
00417   }
00418 
00419   // first stage: amplify the hit pes
00420   const unsigned int begin_charge=
00421     (unsigned int) ( fRandom->PoissonD(hpe*fSECValues[hpit][hsit][0]) );
00422   qarray[hpit]=begin_charge;
00423   MSG("DetSim", Msg::kVerbose)<<"Charge after stage 1"<<endl;
00424   PrintCharge(qarray);
00425   // loop through the last 11 stages
00426   // at each stage, leak some electrons for electric xtalk
00427   // then do dynode multiplication
00428   // NOTE: leakage prior to the first dynode looks like
00429   // what we normally call optical xtalk... that is why this
00430   // algorithm starts on the second stage.
00431   for(int stage=1; stage<12; stage++){ 
00432     // first leak charge for xtalk
00433     if(fsPmtDoChargeCrosstalk){
00434       for(int pxit=0; pxit<16; pxit++){
00435         if(pxit==hpit) continue; // pixels do not xtalk to themselves
00436         if(qarray[hpit]<=0.0) break; // can't work with nothing
00437         // create some xtalk charge
00438         double working_xt_fraction
00439           =(xtfrac[pxit]+gElectricXTOffset)*gElectricXTScale/11.0;
00440         // Scale it by user.
00441         working_xt_fraction *= fsPmtScaleChargeCrosstalk;
00442         qarray[pxit]+=(unsigned int)(fRandom->PoissonD(working_xt_fraction*qarray[hpit]));
00443       }
00444     }
00445     // amplify charges at this stage, including any charge from xtalk
00446     for(int pxit=0; pxit<16; pxit++){
00447       if(qarray[pxit]<=0) continue;// can't work with nothing
00448       double current_sec=0.0;
00449       if(pxit==hpit) current_sec=fSECValues[hpit][hsit][stage];
00450       else current_sec=fAverageSECValues[hpit][stage];
00451       // correct for non linearity on the last stage
00452       if(stage==11 && fsPmtDoNonlinearity){
00453         current_sec=
00454           LastStageChargeNonLinearity(qarray[pxit],gains[pxit],current_sec);
00455       }
00456       // simulate charge amplification at this stage
00457       qarray[pxit]=(unsigned int)(fRandom->PoissonD(qarray[pxit]*current_sec));
00458 
00459     }
00460     MSG("DetSim", Msg::kVerbose)<<"Charge after stage "<<(stage+1)<<endl;
00461     PrintCharge(qarray);
00462   }
00463 
00464 
00465   // dynode/anode charge fraction
00466   // Teststand data shows dynode/anode ~ 35%-50%
00467   const double dynode_fraction=0.425;
00468   // ADD created charges into ouput
00469   for(int pxit=0; pxit<16; pxit++){
00470     // sum charges for dynode output
00471     qvec[16]+=(qarray[pxit]/dynode_fraction)*Munits::e_SI;
00472     // convert charge from # of electrons to C
00473     qvec[pxit]+=qarray[pxit]*Munits::e_SI; // add charge in C
00474   }
00475 
00476   // The timing:
00477   // Hamamatsu says transit time 8.8 ns with a jitter of 180 ps
00478   // these numbers are for single pes.
00479   // The input charge drives the generation of charge in the tube, 
00480   // so simulate for it and apply the result to all pixels.
00481 
00482   // what is the common currency for times in DetSim?
00483   // I'm guessing seconds based on the above comment regarding C vs. fC.
00484   const double transit_time = 
00485     fRandom->Gaus(gTransitTimeNS, 
00486                   (gTransitTimeJitterNS)/sqrt(hpe))*Munits::ns;
00487   for(int pxit=0; pxit<17; pxit++){
00488     tvec[pxit]+=transit_time;
00489   }
00490 
00491   return true;
00492 }
00493 
00494 double SimPmtUTM16::RetrieveElectricXtalkFraction(Int_t hp, 
00495                                                   Int_t hs, 
00496                                                   Int_t xp) const
00497 {
00498   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00499   //
00500   // This routine should talk to the database and get the appropriate constant.
00501   // It can employ this object's fContext member and 
00502   // the arguments to this function.
00503   
00504   double result=0.0;
00505   if(gCrosstalkTable){
00506     const SimPmtM16Crosstalk* row = gCrosstalkTable->GetRow(hp,hs,xp);
00507     if(row){
00508       result = row->GetElecFrac();
00509       static const MsgFormat ffmt("%9.3e");
00510       MSG("DetSim", Msg::kVerbose)
00511         <<"RetrieveElectricXtalkFraction("<<hp<<", "<<hs<<", "<<xp<<") "
00512         <<"will return "<<ffmt(result)<<" ."<<endl;
00513     }
00514     else{
00515       MSG("DetSim", Msg::kError)
00516         <<"RetrieveElectricXtalkFraction "
00517         <<"could not find a db row for:\n"
00518         <<"hit_pixel: "<< hp
00519         <<", hit_spot: "<<hs
00520         <<", xtalk_pixel: "<<xp
00521         <<"."<<endl;
00522     }
00523   }
00524   else{
00525     MSG("DetSim", Msg::kFatal)
00526       <<"Somehow RetrieveElectricXtalkFraction was called with "
00527       "gCrosstalkTable==0!\nThis is a fatal error.";
00528       assert(0); // bomb out
00529   }
00530 
00531   return result; // a return value <= 0.0 will result in no xtalk
00532 }
00533 
00534 double SimPmtUTM16::RetrieveOpticalXtalkFraction(Int_t  hp, 
00535                                                  Int_t hs, 
00536                                                  Int_t xp) const
00537 {
00538   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00539   //
00540   // This routine should talk to the database and get the appropriate constant.
00541   // It can employ this object's fContext member and 
00542   // the arguments to this function.
00543 
00544   double result=0.0;
00545   if(gCrosstalkTable){
00546     const SimPmtM16Crosstalk* row = gCrosstalkTable->GetRow(hp,hs,xp);
00547     if(row){
00548       result = row->GetOptFrac();
00549       static const MsgFormat ffmt("%9.3e");
00550       MSG("DetSim", Msg::kVerbose)
00551         <<"RetrieveOpticalXtalkFraction("<<hp<<", "<<hs<<", "<<xp<<") "
00552         <<"will return "<<ffmt(result)<<" ."<<endl;
00553     }
00554     else{
00555       MSG("DetSim", Msg::kError)
00556         <<"RetrieveOpticalXtalkFraction "
00557         <<"could not find a db row for:\n"
00558         <<"hit_pixel: "<< hp
00559         <<", hit_spot: "<<hs
00560         <<", xtalk_pixel: "<<xp
00561         <<"."<<endl;
00562     }
00563   }
00564   else{
00565     MSG("DetSim", Msg::kFatal)
00566       <<"Somehow RetrieveOpticalXtalkFraction was called with "
00567       "gCrosstalkTable==0!\nThis is a fatal error.";
00568       assert(0); // bomb out
00569   }
00570 
00571   return result * fsPmtScaleOpticalCrosstalk; // a return value <= 0.0 will result in no xtalk
00572 }
00573 
00574 
00575 bool SimPmtUTM16::InitSECValues()
00576 {
00577   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00578   //
00579   // This routine fills the arrays fSECValues, fAverageSECValues
00580   // It should be called once when the object is made and again
00581   // whenever the gain and width information change for any pixel&spot
00582   // on this tube.
00583   
00584   MSG("DetSim",Msg::kDebug) << "InitSECValues() Tube " << fTube.AsString() << endl;
00585 
00586   static const double default_gain_last=gkDefaultSECValues[3]
00587     *gkDefaultSECValues[4]
00588     *gkDefaultSECValues[5]
00589     *gkDefaultSECValues[6]
00590     *gkDefaultSECValues[7]
00591     *gkDefaultSECValues[8]
00592     *gkDefaultSECValues[9]
00593     *gkDefaultSECValues[10]
00594     *gkDefaultSECValues[11];
00595 
00596   // rescale secs based on gains and widths taken from db
00597   // loop over pixels
00598   for(int pxit=0; pxit<16; pxit++){
00599     const int pixel=pxit+1;
00600     // loop over fiber spots on a pixel
00601     double ave_width=0.0;
00602     double ave_gain=0.0;
00603     for(int spit=0; spit<8; spit++){
00604       const int spot=spit+1;
00605       double pixel_gain, fractional_width;
00606       GetGainAndWidth(pixel,spot,pixel_gain,fractional_width);
00607 
00608       const double default_fractional_width=0.5;
00609 
00610       if(fractional_width<0.05 || fractional_width>5.0){
00611         // a crazy value for the spe width
00612         MAXMSG("DetSim", Msg::kError,50)
00613           <<"InitSECValues: GetGainAndWidth("<<pixel<<", "<<spot<<")"
00614           <<" returned fractional width " << fractional_width
00615           <<" which is outside the reasonable range!\n"
00616           <<" Will use the width "<< default_fractional_width
00617           <<" in further computations."<<endl;
00618         fractional_width=default_fractional_width;
00619       }
00620       const double sec = FractionalWidthToSEC(fractional_width);
00621       double width = fractional_width * pixel_gain;
00622       ave_width+=(width*width);
00623       ave_gain+=pixel_gain;
00624     // The following code sets the emmission coefficient of
00625     // the first 3 stages to sec and then rescales the 
00626     // default coefficients for the last 9 stages
00627     // so that the gain of the tube is equal to pixel_gain.
00628     // Since the width is largely determined by the emission
00629     // coefficients of the first 3 stages this procedure will result
00630     // in a simulation with the requested width and mean for the 1pe peak.
00631 
00632     // first 3 stages determine fractional width
00633       fSECValues[pxit][spit][0]=sec;
00634       fSECValues[pxit][spit][1]=sec;
00635       fSECValues[pxit][spit][2]=sec;
00636 
00637       const double pixel_gain_first=sec*sec*sec;
00638       // for other stages
00639       const double pixel_gain_last = pixel_gain/pixel_gain_first;
00640       const double rescale_factor = 
00641         pow((pixel_gain_last/default_gain_last),1.0/9.0);
00642       for(int stage=3; stage<12; stage++) {
00643         fSECValues[pxit][spit][stage] = 
00644           rescale_factor*gkDefaultSECValues[stage];
00645       }
00646     } // done with loop over spots
00647 
00648     // calculate average sec values
00649 
00650     // Now, ave_gain holds the sum of the gains for the 8 spots.
00651     // I divide by 8 to get the average.
00652     ave_gain/=8.0;
00653 
00654     // Now, ave_width holds the sum of the (fractional_width)^2 for
00655     // each spot. I then divide by 8 to average and take the square root.
00656     ave_width/=8.0;
00657     ave_width=sqrt(ave_width);
00658 
00659     // Then, the average secondary emission coeffiecient is computed from
00660     // ave_width, but needs to be converted back to a fractional width:
00661     double ave_sec=FractionalWidthToSEC(ave_width/ave_gain);
00662 
00663     // The first sec values of the first 3 stages are set to ave_sec.
00664     fAverageSECValues[pxit][0]=ave_sec;
00665     fAverageSECValues[pxit][1]=ave_sec;
00666     fAverageSECValues[pxit][2]=ave_sec;
00667 
00668     // The sec values of the last 9 stages are scaled to as to produce
00669     // an overall gain of ave_gain.
00670     const double ave_pixel_gain_first=ave_sec*ave_sec*ave_sec;
00671     const double ave_pixel_gain_last=ave_gain/ave_pixel_gain_first;
00672     // Figure out a rescaling factor.  The power of 1/9
00673     // is needed since we will end up taking this number to the 9th
00674     // power when computing the gain and/or simulating the dynode 
00675     // cascade.
00676     const double ave_rescale_factor = 
00677       pow((ave_pixel_gain_last/default_gain_last),1.0/9.0);
00678     for(int stage=3; stage<12; stage++) {
00679       fAverageSECValues[pxit][stage] = 
00680         ave_rescale_factor*gkDefaultSECValues[stage];
00681     }
00682     // done with rescaling business for this pixel
00683   }// done with loop over pixels
00684 
00685   // print out SEC values
00686   MSG("DetSim", Msg::kVerbose)<<"Printing SEC values: "<<endl;
00687   for(int pxit=0; pxit<16; pxit++){
00688     for(int spit=0; spit<8; spit++){
00689       MSG("DetSim", Msg::kVerbose)
00690         <<"Stage SECs for pixel "<<pxit+1<<", and spot "<<spit+1<<endl;
00691       for(int stit=0; stit<12; stit++){
00692       MSG("DetSim", Msg::kVerbose)
00693         <<"stage "<<stit+1<<" : "<<fSECValues[pxit][spit][stit]<<"   : (avg="
00694         <<fAverageSECValues[pxit][stit]<<")"<<endl;
00695       }
00696     }
00697   }
00698 
00699   return true;
00700 }
00701 
00702 double SimPmtUTM16::LastStageNonLinearity(double q, double gain, double lsec)
00703 {
00704   MSG("DetSim",Msg::kError) << "YOU ARE CALLING A DEPRECATED FUNCTION!"<<endl;
00705 
00706   //*** DEPRECATED FUNCTION ****   Mike Kordosky -- April 25, 2005
00707 
00708   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00709   //
00710   // This routine models pmt non linearity as being due to a change in
00711   // the secondary emission of the last dynode.  The data taken from
00712   // the M16 teststands shows the amount of charge measured vs. the number
00713   // of input PEs over the range of 0-300 input pes.  The light level
00714   // is determined by measuring the light level in the linear 
00715   // region of the tube (in practice ~30pes ) and then scaling by the 
00716   // known transmission values of the neutral density filters used 
00717   // in the stand.
00718   //
00719   // Qualitatively, for light levels > 50 pes a decrease in the amount of
00720   // charge per input pe is seen.  This decrease is treated as being due
00721   // to a change in the gain of the pmt.  The data is then processed to 
00722   // show the fractional change in gain vs. the number of input pes.
00723   // That data is then parameterized as:
00724   // (a) Flat for pe<=50
00725   // (b) 3rd order polynomial for 50<pe<=300
00726   // (c) For pe>300 the value at pe=300 is used
00727 
00728   // This routine uses the parameterization above to model the change
00729   // in gain by a deviation of the secondary emission coefficient of the last
00730   // dynode stage from its nominal value.  The parametrization is defined
00731   // in the member function M16NonLinearity.
00732 
00733   // The input parameters are:
00734   // q = charge input to the last stage
00735   // gain = nominal gain of this pmt
00736   // lsec = nominal sec of the last stage
00737   
00738   double result=lsec;
00739   const double approx_npe=q/(gain/lsec);
00740   if(approx_npe>50.0 && approx_npe<=300.0){
00741     const double frac_gain_change=M16NonLinearity(approx_npe);
00742     result = (1.0+frac_gain_change)*lsec;
00743   }
00744   else if(approx_npe>300.0){
00745     const double frac_gain_change=M16NonLinearity(300.0);
00746     result = (1.0+frac_gain_change)*lsec;
00747   }
00748 
00749   return result;
00750 }
00751 
00752 double SimPmtUTM16::M16NonLinearity(double pe)
00753 { 
00754   MSG("DetSim",Msg::kError) << "YOU ARE CALLING A DEPRECATED FUNCTION!"<<endl;
00755   
00756   //***DEPRECATED FUNCTION***  Mike Kordosky -- April 25, 2005
00757   //
00758   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00759   //
00760   // Returns the fractional change in gain
00761   // due to pmt nonlinearity.
00762   // Parameterization taken from m16 teststand data.
00763   // See SimPmtUTM16::LastStageNonLinearity
00764   // for more details.
00765   //
00766   // This function is to be applied for 50<pe<300.
00767   
00768   //  static const double p[4]={5.0E-2,
00769   //                        -1.4E-3,
00770   //                        5.6E-6,
00771   //                        7.5E-9};
00772 
00773   // M. Kordosky - March 22, 2005
00774   // sign error in 3rd degree term... I thought I fixed it long ago!!
00775   // thanks to Anatael for illuminating this problem
00776   //
00777   static const double p[4]={5.0E-2,
00778                             -1.4E-3,
00779                             5.6E-6,
00780                             -7.5E-9};
00781   
00782   return p[0] + p[1]*pe + p[2]*pe*pe + p[3]*pe*pe*pe;
00783 }
00784 
00785 
00786 double SimPmtUTM16::LastStageChargeNonLinearity(double q, 
00787                                                 double /* gain */, double lsec)
00788 {
00789 
00790   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00791   //
00792   // This routine models pmt non linearity as being due to a change in
00793   // the secondary emission of the last dynode.  The data taken from
00794   // the M16 teststands and CalDet LI shows the amount of charge 
00795   // measured vs. the number of input PEs over the range of 0-300 input pes.
00796   //
00797   // The light level is determined by measuring the light level in the linear 
00798   // region of the tube (in practice ~30pes ), assuming linearity there, 
00799   // and then scaling by the known transmission values of the 
00800   // neutral density filters used in the stand or the PIN measurments.
00801   //
00802   // Qualitatively, for light levels > 50 pes a decrease in the amount of
00803   // charge per input pe is seen.  This decrease is treated as being due
00804   // to a change in the gain of the pmt.  
00805   
00806   // This routine uses the parameterization above to model the change
00807   // in gain by a deviation of the secondary emission coefficient of the last
00808   // dynode stage from its nominal value.  The parametrization is defined
00809   // in the member function M16ChargeNonLinearity.
00810 
00811   // The input parameters are:
00812   // q = charge input to the last stage in units of # electrons
00813   // gain = nominal gain of this pmt (unitless)
00814   // lsec = nominal sec of the last stage (unitless)
00815   
00816   double result=lsec;
00817   const double provisional_charge=q*lsec;
00818   const double frac_gain_change = M16ChargeNonLinearity(provisional_charge);
00819   result = (1.0+frac_gain_change)*lsec;
00820   
00821   return result;
00822 }
00823 
00824 double SimPmtUTM16::M16ChargeNonLinearity(double nelectrons)
00825 { 
00826   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00827   //
00828   // Returns the fractional change in gain
00829   // due to pmt nonlinearity.
00830   //
00831   // Input: the charge, in # of electron units, at final stage
00832   //        typically this is a provisional charge found from the 
00833   //        result at the second to last stage multipled by the nominal
00834   //        gain of the last stage
00835   //
00836   // Parameterization taken from CalDet 2003 LI
00837   // thanks Anatael Cabrera
00838   //
00839   // See SimPmtUTM16::LastStageChargeNonLinearity
00840   // for more details.
00841   //
00842   
00843   // default fsVaGain = 0.375/Munits::fC
00844   // Munits::e_SI = 1.602176462e-19 C
00845   const double adc_per_electron = fsVaGain*Munits::e_SI;
00846 
00847   // calculate provisional adcs
00848   // need this because parameterization given in terms of VA ADCs
00849   // but we haven't digitized yet!
00850   const double provisional_adc = nelectrons*adc_per_electron;
00851   
00852   // calculate gain dip
00853   // gain_dip: change of gain at last stage owing to non linearity
00854   //           negative # means a loss in tube gain
00855   //           the gain of the last stage should be modeled as
00856   //           gain = nominal*(1+gain_dip)
00857   //
00858   static const double p[2]={0.0166, -4.51e-6};
00859   
00860   double gain_dip = p[0] + p[1]*provisional_adc;
00861 
00862   const double linear_adc = 3600;//value below which PMTs are linear
00863   const double high_adc = 60*300;//upper limit of the parameterization ~300 PEs
00864   if(provisional_adc<=linear_adc) { 
00865     MSG("DetSim",Msg::kDebug) << " Tube in linear region: "
00866                               <<" provisional adc = "<<provisional_adc
00867                               <<"  < linear_adc = "<<linear_adc<<"\n"
00868                               <<" gain_dip set to zero "<<endl;
00869     gain_dip=0.0;
00870   }
00871   else if(provisional_adc>high_adc){
00872     gain_dip = p[0] + high_adc*p[1];
00873     MSG("DetSim",Msg::kDebug) << " Tube outside of parameterized region: "
00874                               <<" provisional adc = "<<provisional_adc
00875                               <<"  < high_adc = "<<high_adc<<"\n"
00876                               <<" gain_dip set to value at"
00877                               <<high_adc<<" ADCs : "<<gain_dip<<endl;
00878     
00879   }
00880 
00881   MSG("DetSim",Msg::kDebug) << "[nelectrons, provisional adc, %gain_dip] : [ " 
00882                             << nelectrons<<" , "
00883                             << provisional_adc<<" , "
00884                             << gain_dip*100.0 <<" ]"<<endl;
00885 
00886 
00887   return gain_dip; 
00888 
00889 }
00890 
00891 
00892 Float_t SimPmtUTM16::GenDarkNoiseCharge( Int_t /* pixel */, Float_t /* timeWindow */ )
00893 {
00894   // MAK April 25, 2005 : Simulated elsewhere
00895   return 0; 
00896 }
00897 
00898 Float_t SimPmtUTM16::GenOpticalCrosstalk( Int_t hp, Int_t hs,
00899                                    Int_t xp, Float_t hpe ) 
00900 {
00901   // Author: Mike Kordosky : kordosky@hep.utexas.edu
00902   //
00903   // This routine generates optical xtalk.  The output of the
00904   // routine is the number of xtalk pes generated in the xtalk pixel.
00905   //
00906   // For the configuration described by:
00907   // hp = hit pixel
00908   // hs = hit spot
00909   // xp = xtalk pixel
00910   // the database is contacted to obtain the parameter:
00911   // k = <xtalk_charge/hit_charge>. 
00912   //
00913   // These k values are taken from pmt test stand data and have 
00914   // been tuned to CalDet beam data, via the use of the global 
00915   // scaling parameters gOpticalXTScale, gOpticalXTOffset.
00916   //
00917   // Optical xtalk is stochastic in nature. Most of the time no charge is
00918   // produced. When xtalk is seen the charge corresponds to ~1pe.  
00919   // The average charge in the xtalk pixel is given by:
00920   //       average_xtalk_charge = k*hpe
00921   // with  k = <xtalk_charge/hit_charge>
00922   //
00923   // The number of PEs from xtalk is then generated by sampling a Poisson
00924   // distribution with a mean = average_xtalk_charge.
00925 
00926   // Can't work with nothing.
00927   if(hpe<=0.0) return 0.0;
00928   
00929   // Get a k value
00930   double k = RetrieveOpticalXtalkFraction(hp, hs, xp);
00931   // check k before going on
00932   if(k<=0.0) return 0.0; 
00933   
00934   // rescale k
00935   k=(k+gOpticalXTOffset)*gOpticalXTScale;
00936   
00937   // check k before going on
00938   if(k<=0.0) return 0.0; 
00939   
00940   // generate xtalk pes
00941   Int_t xpe = fRandom->Poisson(k*hpe);
00942   if(xpe<0) xpe=0; // some error from fRandom?
00943   
00944   return xpe;
00945   
00946 }
00947 
00948 
00949 
00950 //void SimPmtUTM16::SimulateOpticalXtalk()
00951 //
00952 // Puts extra PE into pixels they don't belong.
00953 //
00954 // MAK 2003.05.15 -- original code put all xtalk on spot 1
00955 // fixed it to randomly generate across the tube face.
00956 //
00957 //
00958 // NJT 2003.07.15 -- Put the above mod into the SimPmt class,
00959 // remove this override.
00960 //
00961 
00962 void SimPmtUTM16::PrintCharge(unsigned int* qarray){
00963   static const MsgFormat ifmt("%10d");
00964   
00965   MSG("DetSim", Msg::kVerbose)
00966     <<"================================================================================\n"
00967     <<ifmt(qarray[3])<<" "<<ifmt(qarray[2])<<" "<<ifmt(qarray[1])<<" "<<ifmt(qarray[0])
00968     <<"\n"
00969     <<ifmt(qarray[7])<<" "<<ifmt(qarray[6])<<" "<<ifmt(qarray[5])<<" "<<ifmt(qarray[4])
00970     <<"\n"
00971     <<ifmt(qarray[11])<<" "<<ifmt(qarray[10])<<" "<<ifmt(qarray[9])<<" "<<ifmt(qarray[8])
00972     <<"\n"
00973     <<ifmt(qarray[15])<<" "<<ifmt(qarray[14])<<" "<<ifmt(qarray[13])<<" "<<ifmt(qarray[12])
00974     <<"\n"
00975     <<"================================================================================\n"
00976 
00977     <<endl;
00978 }
00979 
00980 
00981 
00982 
00983 
00984 
00985 
00986 
00987 
00988 
00989 
00990 
00991 
00992 
00993 
00994 
00995 
00996 

Generated on Fri Mar 28 15:39:54 2008 for loon by  doxygen 1.3.9.1