All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups
ScintillatorPpdDigi.cc
Go to the documentation of this file.
1 #include "ScintillatorPpdDigi.h"
2 #include <assert.h>
3 #include "CLHEP/Random/RandPoisson.h"
4 #include "CLHEP/Random/RandGauss.h"
5 #include "CLHEP/Random/RandBinomial.h"
6 #include <iostream>
7 using std::cout;
8 using std::endl;
9 
10 // this applies some extra digitisation to scintillator+PPD hits (PPD=SiPM, MPPC)
11 // - poisson fluctuates the number of photo-electrons according to #PEs/MIP
12 // - applies PPD saturation according to #pixels
13 // Daniel Jeans, Jan/Feb 2014.
14 // (split off from ILDCaloDigi Aug'14.)
15 
17  _pe_per_mip=-99;
18  _calib_mip=-99;
19  _npix=-99;
20  _misCalibNpix=0;
21  _pixSpread=0;
22  _elecNoise=0;
23 }
24 
26  cout << "--------------------------------" << endl;
27  cout << "ScintillatorPpdDigi parameters" << endl;
28  cout << " pe_per_mip = " << _pe_per_mip << endl;
29  cout << " calib_mip = " << _calib_mip << endl;
30  cout << " npix = " << _npix << endl;
31  cout << " misCalibNpix = " << _misCalibNpix << endl;
32  cout << " pixSpread = " << _pixSpread << endl;
33  cout << " elecDynRange = " << _elecMaxDynRange_MIP << endl;
34  cout << " elecNoise = " << _elecNoise << endl;
35  cout << "--------------------------------" << endl;
36  return;
37 }
38 
40 
41  float correctedEnergy(energy);
42 
43  if (_pe_per_mip<=0 || _calib_mip<=0 || _npix<=0) {
44  cout << "ERROR, crazy parameters for ScintillatorPpdDigi: PE/MIP=" << _pe_per_mip << ", MIP calib=" << _calib_mip << ", #pixels=" << _npix << endl;
45  cout << "you must specify at least the #PE/MIP, MIP calibration, and #pixels for realistic scintillator digitisation!!" << endl;
46  cout << "refusing to proceed!" << endl;
47  assert(0);
48  }
49 
50  // 1. convert energy to expected # photoelectrons (via MIPs)
51  float npe = _pe_per_mip*energy/_calib_mip;
52 
53  //oh: commented out Daniel's digitisation model. (npe -> poisson -> saturation -> stoykov smearing).
54  // Stoykov smearing used with Gaussian shape for lack of better model.
55  /*
56  // 2. smear according to poisson (PE statistics)
57  npe = CLHEP::RandPoisson::shoot( npe );
58 
59  if (_npix>0) {
60  // 3. apply average sipm saturation behaviour
61  npe = _npix*(1.0 - exp( -npe/_npix ) );
62 
63  // 4. apply intrinsic sipm fluctuations (see e.g arXiv:0706.0746 [physics.ins-det])
64  float alpha = npe/_npix; // frac of hit pixels
65  float width = sqrt( _npix*exp(-alpha)*( 1. - (1.+alpha)*exp(-alpha) ) );
66 
67  // make an integer correction
68  int dpix = int( floor( CLHEP::RandGauss::shoot(0, width) + 0.5 ) );
69 
70  npe += dpix;
71  }
72 */
73 
74  //AHCAL TB style digitisation: npe -> saturation -> binomial smearing
75  //shown to be mathematically equivalent to Daniel's model above, but slightly faster and automatically generates correct shape instead of Gaussian approximation
76 
77  if (_npix>0){
78  // apply average sipm saturation behaviour
79  npe = _npix*(1.0 - exp( -npe/_npix ) );
80 
81  //apply binomial smearing
82  float p = npe/_npix; // fraction of hit pixels on SiPM
83  npe = CLHEP::RandBinomial::shoot(_npix, p); //npe now quantised to integer pixels
84  }
85 
86 
87 
88  if (_pixSpread>0) {
89  // variations in pixel capacitance
90  npe *= CLHEP::RandGauss::shoot(1, _pixSpread/sqrt(npe) );
91  }
92 
93  if ( _elecMaxDynRange_MIP > 0 ) {
94  // limited dynamic range of readout electronics
95  // Daniel moved this here, before the unfolding of saturation (September 2015)
96  npe = std::min ( npe, _elecMaxDynRange_MIP*_pe_per_mip );
97  }
98 
99  if (_elecNoise>0) {
100  // add electronics noise
101  npe += CLHEP::RandGauss::shoot(0, _elecNoise*_pe_per_mip);
102  }
103 
104  if (_npix>0) {
105  // 4. unfold the saturation
106  // - miscalibration of npix
107  float smearedNpix = _misCalibNpix>0 ? _npix*CLHEP::RandGauss::shoot( 1.0, _misCalibNpix ) : _npix;
108 
109  //oh: commented out daniel's implmentation of dealing with hits>smearedNpix. using linearisation of saturation-reconstruction for high amplitude hits instead.
110  /*
111  // - this is to deal with case when #pe is larger than #pixels (would mean taking log of negative number)
112  float epsilon=1; // any small number...
113  if ( npe>smearedNpix-epsilon ) npe=smearedNpix-epsilon;
114  // - unfold saturation
115  npe = -smearedNpix * std::log ( 1. - ( npe / smearedNpix ) );
116  */
117 
118  const float r = 0.95; //this is the fraction of SiPM pixels fired above which a linear continuation of the saturation-reconstruction function is used. 0.95 of nPixel corresponds to a energy correction of factor ~3.
119 
120  if (npe < r*smearedNpix){ //current hit below linearisation threshold, reconstruct energy normally:
121  npe = -smearedNpix * std::log ( 1. - ( npe / smearedNpix ) );
122  } else { //current hit is aove linearisation threshold, reconstruct using linear continuation function:
123  npe = 1/(1-r)*(npe-r*smearedNpix)-smearedNpix*std::log(1-r);
124  }
125  }
126 
127  // convert back to energy
128  correctedEnergy = _calib_mip*npe/_pe_per_mip;
129 
130  return correctedEnergy;
131 }
132 
float getDigitisedEnergy(float energy)