All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups
RealisticCaloDigi.cc
Go to the documentation of this file.
1 // Calorimeter digitiser
2 #include "RealisticCaloDigi.h"
3 
4 #include <marlin/Global.h>
5 #include <marlin/Exceptions.h>
6 #include <EVENT/LCCollection.h>
7 #include <IMPL/LCCollectionVec.h>
8 #include <IMPL/CalorimeterHitImpl.h>
9 #include <IMPL/LCRelationImpl.h>
10 #include <CalorimeterHitType.h>
11 #include <EVENT/LCParameters.h>
12 #include <UTIL/CellIDDecoder.h>
13 #include <marlin/ProcessorEventSeeder.h>
14 
15 #include <iostream>
16 #include <string>
17 #include <assert.h>
18 #include <cmath>
19 #include <set>
20 
21 #include "CLHEP/Random/Random.h"
22 #include "CLHEP/Random/RandGauss.h"
23 #include "CLHEP/Random/RandFlat.h"
24 #include "CLHEP/Units/PhysicalConstants.h"
25 
26 #define RELATIONFROMTYPESTR "FromType"
27 #define RELATIONTOTYPESTR "ToType"
28 
29 using namespace std;
30 using namespace lcio ;
31 using namespace marlin ;
32 using namespace std::placeholders;
33 
34 struct MCC {
35  float energy {0.f};
36  float time {0.f};
37 };
38 
39 RealisticCaloDigi::RealisticCaloDigi( ) : Processor( "RealisticCaloDigi" ) {
40 
41  _description = "Performs digitization of sim calo hits. Virtual class." ;
42 
43  // input collections of simcalohits
44  std::vector<std::string> inputCollections;
45  inputCollections.push_back("SimCalorimeterHits");
46 
47  registerInputCollections( LCIO::SIMCALORIMETERHIT,
48  "inputHitCollections" ,
49  "input simcalhit Collection Names" ,
51  inputCollections);
52 
53  // since we don't know number of output collections a priori,
54  // we can't use registerOutputCollection, but have to use registerProcessorParameter
55  std::vector<std::string> outputCollections;
56  registerProcessorParameter( "outputHitCollections",
57  "output calorimeterhit Collection Names" ,
59  outputCollections );
60 
61  // the collections of relations between sim and digitised hits
62  std::vector<std::string> outputRelCollections;
63  registerProcessorParameter( "outputRelationCollections",
64  "output hit relatiob Collection Names" ,
66  outputRelCollections );
67 
68  // energy threshold
69  registerProcessorParameter("threshold" ,
70  "Threshold for Hit" ,
72  (float) 0.5 );
73 
74  registerProcessorParameter("thresholdUnit" ,
75  "Unit for threshold. Can be \"GeV\", \"MIP\" or \"px\". MIP and px need properly set calibration constants" ,
77  std::string("MIP"));
78 
79  // timing
80  registerProcessorParameter("timingCut" ,
81  "Use hit times" ,
82  _time_apply ,
83  (int)0);
84 
85  registerProcessorParameter("timingCorrectForPropagation" ,
86  "Correct hit times for propagation: radial distance/c" ,
88  (int)0);
89 
90  registerProcessorParameter("timingWindowMin" ,
91  "Time Window minimum time in ns" ,
93  (float)-10.0);
94 
95  registerProcessorParameter("timingWindowMax" ,
96  "Time Window maximum time in ns" ,
98  (float)+100.0);
99 
100  registerProcessorParameter("integrationMethod" ,
101  "Energy integration and time calculation method. Options: Standard, ROC" ,
103  std::string("Standard"));
104 
105  registerProcessorParameter("fastShaper" ,
106  "Fast shaper value. Unit in ns" ,
107  _fast_shaper,
108  (float)0.f);
109 
110  registerProcessorParameter("slowShaper" ,
111  "Slow shaper value. Unit in ns" ,
112  _slow_shaper,
113  (float)0.f);
114 
115  registerProcessorParameter("timingResolution" ,
116  "Time resolution to apply (gaussian smearing). Unit in ns" ,
117  _time_resol,
118  (float)0);
119 
120 
121  // additional digi effects
122 
123  registerProcessorParameter("calibration_mip" ,
124  "average G4 deposited energy by MIP for calibration" ,
125  _calib_mip,
126  (float)1.0e-4);
127 
128  registerProcessorParameter("miscalibration_uncorrel" ,
129  "uncorrelated random gaussian miscalibration (as a fraction: 1.0 = 100%) " ,
131  (float)0.0);
132 
133  registerProcessorParameter("miscalibration_uncorrel_memorise" ,
134  "store oncorrelated miscalbrations in memory? (i.e. use same miscalibrations in each event. WARNING: can take a lot of memory if used...) " ,
136  (bool)false);
137 
138  registerProcessorParameter("miscalibration_correl" ,
139  "correlated random gaussian miscalibration (as a fraction: 1.0 = 100%) " ,
141  (float)0.0);
142 
143  registerProcessorParameter("deadCell_fraction" ,
144  "random dead cell fraction (as a fraction: 0->1) " ,
146  (float)0.);
147 
148  registerProcessorParameter("deadCell_memorise" ,
149  "store dead cells in memory? (i.e. use same dead cells in each event. WARNING: can take a lot of memory if used...) " ,
151  (bool)false);
152 
153  // simple model of electronics properties
154  registerProcessorParameter("elec_noise_mip",
155  "typical electronics noise (in MIP units)",
157  float (0));
158 
159  registerProcessorParameter("elec_range_mip",
160  "maximum of dynamic range of electronics (in MIPs)",
162  float (2500) );
163 
164  // code for layer info for cellID decoder
165  registerProcessorParameter("CellIDLayerString" ,
166  "name of the part of the cellID that holds the layer" ,
168  std::string("K-1")
169  );
170 
171 }
172 
174 
175  printParameters();
176 
177  // if no output collection names specified, set some default based on the input collection names
178  if ( _outputCollections.size()==0 ) {
179  for (size_t i=0; i<_inputCollections.size(); i++) {
180  _outputCollections.push_back( _inputCollections[i] + "Digi" );
181  }
182  }
183  if ( _outputRelCollections.size()==0 ) {
184  for (size_t i=0; i<_inputCollections.size(); i++) {
185  _outputRelCollections.push_back( _inputCollections[i] + "DigiRelation" );
186  }
187  }
188 
189  // check that number of input and output collections names are the same
190  assert ( _outputCollections.size() == _inputCollections.size() );
191  assert ( _outputRelCollections.size() == _inputCollections.size() );
192 
193  // unit in which threshold is specified
194  if (_threshold_unit.compare("MIP") == 0){
196  } else if (_threshold_unit.compare("GeV") == 0){
198  } else if (_threshold_unit.compare("px") == 0){
200  } else {
201  streamlog_out(ERROR) << "could not identify threshold unit. Please use \"GeV\", \"MIP\" or \"px\"! Aborting." << std::endl;
202  marlin::StopProcessingException(this);
203  }
204 
205  // convert the threshold to the approriate units (i.e. MIP for silicon, NPE for scint)
207 
208  // deal with timing calculations
209  std::map<std::string, integr_function> integrations = {
210  {"Standard", std::bind(&RealisticCaloDigi::StandardIntegration, this, _1)},
211  {"ROC", std::bind(&RealisticCaloDigi::ROCIntegration, this, _1)}
212  };
213  auto findIter = integrations.find( _integration_method ) ;
214  if(integrations.end() == findIter) {
215  streamlog_out(ERROR) << "Could not guess timing calculation method!" << std::endl;
216  streamlog_out(ERROR) << "Available are: Standard, ROC. Provided: " << _integration_method << std::endl;
217  streamlog_out(ERROR) << "Aborting..." << std::endl;
218  marlin::StopProcessingException(this);
219  }
220  _integr_function = findIter->second;
221 
222  // check if parameters are correctly set for the ROC integration
223  if("ROC" == _integration_method) {
224  if(!parameterSet("fastShaper") || !parameterSet("slowShaper")) {
225  streamlog_out(ERROR) << "Fast/slow shaper parameter(s) not set. Required for ROC integration!" << std::endl;
226  streamlog_out(ERROR) << "Aborting..." << std::endl;
227  marlin::StopProcessingException(this);
228  }
229  }
230 
231  _flag.setBit(LCIO::CHBIT_LONG);
232  _flag.setBit(LCIO::RCHBIT_TIME); //store timing on output hits.
233 
234  _flag_rel.setBit(LCIO::LCREL_WEIGHTED); // for the hit relations
235 
236  marlin::Global::EVENTSEEDER->registerProcessor(this);
237  return;
238 }
239 
240 
241 void RealisticCaloDigi::processRunHeader( LCRunHeader* /*run*/) {
242 }
243 
244 void RealisticCaloDigi::processEvent( LCEvent * evt ) {
245 
246  CLHEP::HepRandom::setTheSeed( marlin::Global::EVENTSEEDER->getSeed(this) );
247 
248  // decide on this event's correlated miscalibration
249  if ( _misCalib_correl>0 ) _event_correl_miscalib = CLHEP::RandGauss::shoot( 1.0, _misCalib_correl );
250 
251  //
252  // * Reading Collections of Simulated Hits *
253  //
254 
255  for (unsigned int i(0); i < _inputCollections.size(); ++i) {
256  std::string colName = _inputCollections[i] ;
257  streamlog_out ( DEBUG1 ) << "looking for collection: " << colName << endl;
258  try{
259  LCCollection * col = evt->getCollection( colName.c_str() ) ;
260  string initString = col->getParameters().getStringVal(LCIO::CellIDEncoding);
261  CHT::CaloType cht_type = caloTypeFromString(colName);
262  CHT::CaloID cht_id = caloIDFromString(colName);
263  CHT::Layout cht_lay = layoutFromString(colName);
264 
265  CellIDDecoder<SimCalorimeterHit> idDecoder( col );
266 
267  int numElements = col->getNumberOfElements();
268  streamlog_out ( DEBUG1 ) << colName << " number of elements = " << numElements << endl;
269 
270  if ( numElements==0 ) continue;
271 
272  // create new collection: hits
273  LCCollectionVec *newcol = new LCCollectionVec(LCIO::CALORIMETERHIT);
274  newcol->setFlag(_flag.getFlag());
275 
276  // hit relations to simhits [calo -> sim]
277  LCCollectionVec *relcol = new LCCollectionVec(LCIO::LCRELATION);
278  relcol->setFlag(_flag_rel.getFlag());
279  relcol->parameters().setValue( RELATIONFROMTYPESTR , LCIO::CALORIMETERHIT ) ;
280  relcol->parameters().setValue( RELATIONTOTYPESTR , LCIO::SIMCALORIMETERHIT ) ;
281 
282  // loop over input hits
283  for (int j=0; j < numElements; ++j) {
284  SimCalorimeterHit * simhit = dynamic_cast<SimCalorimeterHit*>( col->getElementAt( j ) ) ;
285 
286  // deal with energy integration and timing aspects
287  auto integrationResult = Integrate(simhit);
288  if( ! integrationResult.has_value() ) {
289  continue;
290  }
291  float time = integrationResult.value().first;
292  float energyDep = integrationResult.value().second;
293  // apply extra energy digitisation onto the energy
294  float energyDig = EnergyDigi(energyDep, simhit->getCellID0() , simhit->getCellID1() );
295 
296  if (energyDig > _threshold_value) { // write out this hit
297  CalorimeterHitImpl* newhit = new CalorimeterHitImpl();
298  newhit->setCellID0( simhit->getCellID0() );
299  newhit->setCellID1( simhit->getCellID1() );
300  newhit->setTime( time );
301  newhit->setPosition( simhit->getPosition() );
302  newhit->setEnergy( energyDig );
303  int layer = idDecoder(simhit)[_cellIDLayerString];
304  newhit->setType( CHT( cht_type, cht_id, cht_lay, layer ) );
305  newhit->setRawHit( simhit );
306  newcol->addElement( newhit ); // add hit to output collection
307 
308  streamlog_out ( DEBUG1 ) << "orig/new hit energy: " << simhit->getEnergy() << " " << newhit->getEnergy() << endl;
309 
310  LCRelationImpl *rel = new LCRelationImpl(newhit,simhit,1.0);
311  relcol->addElement( rel );
312 
313  } // theshold
314  } // input hits
315 
316  // add collection to event
317  newcol->parameters().setValue(LCIO::CellIDEncoding,initString);
318  evt->addCollection(newcol,_outputCollections[i].c_str());
319 
320  // add relation collection to event
321  evt->addCollection(relcol,_outputRelCollections[i].c_str());
322 
323  } catch(DataNotAvailableException &e){
324  streamlog_out(DEBUG1) << "could not find input collection " << colName << std::endl;
325  }
326  }
327 
328 
329  return;
330 }
331 
332 //------------------------------------------------------------------------------
333 
334 void RealisticCaloDigi::check( LCEvent * /*evt*/ ) {
335 
336 }
337 
338 //------------------------------------------------------------------------------
339 
341 }
342 
343 //------------------------------------------------------------------------------
344 
345 RealisticCaloDigi::integr_res_opt RealisticCaloDigi::Integrate( const SimCalorimeterHit * hit ) const {
346  return _integr_function(hit);
347 }
348 
349 //------------------------------------------------------------------------------
350 
351 float RealisticCaloDigi::EnergyDigi(float energy, int id0, int id1) {
352  // some extra digi effects
353  // controlled by _applyDigi = 0 (none), 1 (apply)
354  // input parameters: hit energy ( in any unit: effects are all relative )
355  // id0,1 - cell IDs (used to memorise miscalibrations/dead cells between events if requested)
356  // returns energy ( in units determined by the overloaded digitiseDetectorEnergy )
357 
358  float e_out(energy);
359  e_out = digitiseDetectorEnergy(energy); // this is an overloaded method, provides energy in technology-dependent units
360 
361  // the following make only relative changes to the energy
362 
363  // random miscalib, uncorrelated in cells
364  if (_misCalib_uncorrel>0) {
365  float miscal(0);
366  if ( _misCalib_uncorrel_keep ) { // memorise the miscalibrations from event-to-event
367  std::pair <int, int> id(id0, id1);
368  if ( _cell_miscalibs.find(id)!=_cell_miscalibs.end() ) { // this cell was previously seen, and a miscalib stored
369  miscal = _cell_miscalibs[id];
370  } else { // we haven't seen this one yet, get a new miscalib for it
371  miscal = CLHEP::RandGauss::shoot( 1.0, _misCalib_uncorrel );
372  _cell_miscalibs[id]=miscal;
373  }
374  } else { // get a new calibration for each hit
375  miscal = CLHEP::RandGauss::shoot( 1.0, _misCalib_uncorrel );
376  }
377  e_out*=miscal;
378  }
379 
380  // random miscalib, correlated across cells in one event
382 
383  float oneMipInMyUnits = convertEnergy( 1.0, MIP );
384  // limited electronics dynamic range
385  if ( _elec_rangeMip > 0 ) e_out = std::min ( e_out, _elec_rangeMip*oneMipInMyUnits );
386  // add electronics noise
387  if ( _elec_noiseMip > 0 ) e_out += CLHEP::RandGauss::shoot(0, _elec_noiseMip*oneMipInMyUnits );
388 
389  // random cell kill
390  if (_deadCell_fraction>0){
391  if (_deadCell_keep == true){ // memorise dead cells from event to event
392  std::pair <int, int> id(id0, id1);
393  if (_cell_dead.find(id)!=_cell_dead.end() ) { // this cell was previously seen
394  if (_cell_dead[id] == true){
395  e_out = 0;
396  }
397  } else { // we haven't seen this one yet, get a miscalib for it
398  bool thisDead = (CLHEP::RandFlat::shoot(0.0, 1.0) < _deadCell_fraction);
399  _cell_dead[id] = thisDead;
400  if (thisDead == true){
401  e_out = 0;
402  }
403  }
404  } else { // decide on cell-by-cell basis
405  if (CLHEP::RandFlat::shoot(0.0,1.0)<_deadCell_fraction ) e_out=0;
406  }
407  }
408  return e_out;
409 }
410 
411 //------------------------------------------------------------------------------
412 
414  // apply timing cuts on simhit contributions
415  // outputs a (time,energy) pair
416  float timeCorrection(0);
417  if ( _time_correctForPropagation ) { // time of flight from IP to this point
418  float r(0);
419  for (int i=0; i<3; i++)
420  r+=pow(hit->getPosition()[i],2);
421  timeCorrection = sqrt(r)/CLHEP::c_light; // [speed of light in mm/ns]
422  }
423  // this is Oskar's simple (and probably the most correct) method for treatment of timing
424  // - collect energy in some predefined time window around collision time (possibly corrected for TOF)
425  // - assign time of earliest contribution to hit
426  float energySum = 0;
427  float earliestTime=std::numeric_limits<float>::max();
428  for(int i = 0; i<hit->getNMCContributions(); i++){ // loop over all contributions
429  float timei = hit->getTimeCont(i); //absolute hit timing of current subhit
430  float energyi = hit->getEnergyCont(i); //energy of current subhit
431  float relativetime = timei - timeCorrection; // wrt time of flight
432  if (relativetime>_time_windowMin && relativetime<_time_windowMax){
433  energySum += energyi;
434  if (relativetime<earliestTime){
435  earliestTime = relativetime; //use earliest hit time for simpletimingcut
436  }
437  }
438  }
439  if(earliestTime > _time_windowMin && earliestTime < _time_windowMax){ //accept this hit
440  return integr_res{SmearTime(earliestTime), energySum};
441  }
442  return std::nullopt;
443 }
444 
445 //------------------------------------------------------------------------------
446 
448  const unsigned int ncontrib = hit->getNMCContributions() ;
449  // Sort MC contribution by time
450  std::vector<MCC> mcconts{ncontrib};
451  for(unsigned int i = 0; i<ncontrib; i++){
452  mcconts[i].energy = hit->getEnergyCont(i);
453  mcconts[i].time = hit->getTimeCont(i);
454  }
455  std::sort(mcconts.begin(), mcconts.end(), [](auto lhs, auto rhs){
456  return (lhs.time < rhs.time);
457  });
458  // Accumulate energy until threshold is reached.
459  // The first MC contriubtion after the threshold has been reached sets the hit time
460  bool passThreshold = false;
461  float epar=0.f, hitTime=0.f;
462  unsigned int thresholdIndex=0;
463  // First determine the hit time (hitTime) and the initial hit index
464  // at which we need to start the integration (thresholdIndex)
465  for(unsigned int i=0; i<ncontrib ; ++i) {
466  const auto timei = mcconts[i].time;
467  thresholdIndex = i ;
468  epar = 0.f;
469  for(unsigned int j=i; j<ncontrib ; ++j) {
470  const auto timej = mcconts[j].time;
471  if( (timej-timei) < _fast_shaper) {
472  epar += mcconts[j].energy;
473  }
474  else {
475  break;
476  }
477  if( convertEnergy(epar, GEVDEP) > _threshold_value ) {
478  hitTime = timej;
479  passThreshold = true ;
480  break;
481  }
482  }
483  if(passThreshold) {
484  break;
485  }
486  }
487  // check hit time
488  const float thresholdTime = mcconts[thresholdIndex].time;
489  if( not (thresholdTime>_time_windowMin && thresholdTime<_time_windowMax) ) {
490  return std::nullopt;
491  }
492  // If we've found a hit above the threshold, accumulate the energy until
493  // until the maximum time given by the slow shaper
494  if(passThreshold) {
495  float energySum = 0.f ;
496  for(unsigned int i=thresholdIndex ; i<ncontrib ; ++i) {
497  if( mcconts[i].time < thresholdTime + _slow_shaper) {
498  energySum += mcconts[i].energy;
499  }
500  }
501  hitTime = SmearTime(hitTime);
502  return integr_res{hitTime, energySum};
503  }
504  // else no hit dude !
505  else {
506  return std::nullopt;
507  }
508 }
509 
510 //------------------------------------------------------------------------------
511 
512 float RealisticCaloDigi::SmearTime(float time) const{
513  return _time_resol>0.f ? time + CLHEP::RandGauss::shoot(0, _time_resol ) : time;
514 }
515 
std::map< std::pair< int, int >, bool > _cell_dead
virtual float EnergyDigi(float energy, int id0, int id1)
integr_function _integr_function
virtual integr_res_opt Integrate(const SimCalorimeterHit *hit) const
#define RELATIONTOTYPESTR
virtual float digitiseDetectorEnergy(float energy) const =0
std::string _integration_method
std::string _cellIDLayerString
virtual float convertEnergy(float energy, int inScale) const =0
std::vector< std::string > _outputCollections
std::map< std::pair< int, int >, float > _cell_miscalibs
float SmearTime(float time) const
integr_res_opt StandardIntegration(const SimCalorimeterHit *hit) const
std::vector< std::string > _outputRelCollections
virtual void processRunHeader(LCRunHeader *run)
std::string _threshold_unit
std::optional< integr_res > integr_res_opt
static const float e
virtual void check(LCEvent *evt)
std::vector< LCCollection * > LCCollectionVec
Definition: SiStripClus.h:55
integr_res_opt ROCIntegration(const SimCalorimeterHit *hit) const
std::vector< std::string > _inputCollections
virtual void processEvent(LCEvent *evt)
#define RELATIONFROMTYPESTR
std::pair< float, float > integr_res