EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
EicCalorimeterDigiHitProducer.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file EicCalorimeterDigiHitProducer.cxx
1 //
2 // AYK (ayk@bnl.gov), 2013/06/13
3 //
4 // Calorimeter digi hit producer classes;
5 //
6 
7 //#include <stdlib.h>
8 #include <assert.h>
9 #include <cmath>
10 
11 #include <TGeoNode.h>
12 #include <TRotation.h>
13 #include <TParticlePDG.h>
14 #include <TMath.h>
15 #include <TRandom.h>
16 #include <TDatabasePDG.h>
17 
18 #include <FairRunAna.h>
19 #include <FairRuntimeDb.h>
20 
21 #include <EicLibrary.h>
22 #include <EicGeoPar.h>
23 //#include <EicFakeMoCaPoint.h>
25 
26 using namespace std;
27 
28 // FIXME: do it better later;
29 #define _SQR_(x) ((x)*(x))
30 
31 // -------------------------------------------------------------------------
32 
34  //TString inFileName) :
35  EicDigiHitProducer(name)
36 {
37  ResetVars();
38 
39  //mInFileName = inFileName;
40 } // EicCalorimeterDigiHitProducer::EicCalorimeterDigiHitProducer()
41 
42 // -------------------------------------------------------------------------
43 #if _LATER_
44 int EicCalorimeterDigiHitProducer::UseFakeMoCaPointDatabase(const char *dbFileName, UInt_t energyBinNum)
45 {
46 #if _OLD_
47  TString expandedFileName(dbFileName);
48 
49  // Correct path if needed;
50  if (!expandedFileName.BeginsWith("./") && !expandedFileName.BeginsWith("/"))
51  {
52  TString wrkDir = getenv("VMCWORKDIR");
53 
54  expandedFileName = wrkDir + "/input/" + expandedFileName;
55  } //if
56 
57  TFile dbFile(expandedFileName);
58 #else
59  TFile dbFile("input/", ExpandedFileName(dbFileName));
60 #endif
61 
62  // Declare ROOT tree which will hold entries;
63  TTree *dbTree = (TTree*)dbFile.Get(mDetName->Name() + "EicFakeMoCaPointDatabase"); assert(dbTree);
64 
65  // Alocate buffer variable; think on split, etc; later;
66  EicFakeMoCaPointDbEntry *dbEntry = new EicFakeMoCaPointDbEntry();
67 
68  TBranch *branch = dbTree->GetBranch("EicFakeMoCaPointDbEntry"); assert(branch);
69  branch->SetAddress(&dbEntry);
70 
71  mFakeDB = new EicFakeMoCaPointDatabase();
72 
73  mFakeDB->mEnergyBinNum = energyBinNum; assert(energyBinNum);
74 
75  // Yes, expect object with a predefined name; do a better check later;
76  dbFile.GetObject(mDetName->Name() + "EnergyCutOffTable", mFakeDB->mCutOffMap);
77  assert(mFakeDB->mCutOffMap);
78 
79  for (std::map<Int_t, std::pair<Double_t,Double_t> >::iterator it=mFakeDB->mCutOffMap->begin();
80  it!=mFakeDB->mCutOffMap->end(); ++it)
81  mFakeDB->mDB[abs(it->first)] = new std::vector<EicFakeMoCaPointDbEntry>[mFakeDB->mEnergyBinNum];
82 
83  // Read in entries one by one and allocate the whole database;
84  unsigned evtNum = dbTree->GetEntries();
85  printf(" -I- Importing '%s' fake MC hit database (%d DB entries) ...\n",
86  mDetName->Name().Data(), evtNum);
87  for(unsigned evt=0; evt<evtNum; evt++)
88  {
89  dbTree->GetEntry(evt);
90 
91  unsigned hitNum = dbEntry->fHits->GetEntriesFast();
92  //printf(" %4d: -> %4d hits\n", evt, hitNum);
93 
94  // Well, assume it is available;
95  TDatabasePDG *pdgTable = TDatabasePDG::Instance(); assert(pdgTable);
96 
97  Int_t PDG = dbEntry->getPrimaryParticlePDG();
98 
99  //printf("%d %d\n", PDG, hitNum);
100  // Check, that dbEntry->PDG is present in the map!; fix!;
101  if (mFakeDB->mCutOffMap->find(abs(PDG)) == mFakeDB->mCutOffMap->end())
102  {
103  printf("\n *** Primary mother PDG is not present in the DB map; "
104  "(must be the wrong input DB file)! ***\n\n");
105  exit(0);
106  } //if
107 
108  double eLogMin = log((*mFakeDB->mCutOffMap)[abs(PDG)].first);
109  double eLogMax = log((*mFakeDB->mCutOffMap)[abs(PDG)].second);
110  double bWidth = (eLogMax - eLogMin)/mFakeDB->mEnergyBinNum;
111 
112  double mass = ((TParticlePDG*)pdgTable->GetParticle(PDG))->Mass();
113  double eKin = sqrt(mass*mass + dbEntry->fP.Mag2()) - mass;
114  double eLogKin = log(eKin);
115  int bId = (int)floor((eLogKin - eLogMin)/bWidth);
116 
117  //printf("%d (%d)\n", bId, mFakeDB->mEnergyBinNum);
118  // Check value-at-the-edge condition later, please;
119  if (bId < 0 || bId >= mFakeDB->mEnergyBinNum)
120  {
121  printf("\n *** Primary mother energy out of range; "
122  "(must be the wrong input MC file)! ***\n\n");
123  exit(0);
124  } //if
125 
126  // Do this better, please; suffices for now (just recreate pointer and copy contents over);
127  mFakeDB->mDB[abs(PDG)][bId].push_back(*dbEntry);
128  mFakeDB->mDB[abs(PDG)][bId][mFakeDB->mDB[abs(PDG)][bId].size()-1].fHits =
129  //new TClonesArray("EicFakeMoCaPointDbHit");
130  new TClonesArray(*dbEntry->fHits);
131  //printf("%p %p\n", dbEntry->fHits, mFakeDB->mDB[abs(PDG)][bId][mFakeDB->mDB[abs(PDG)][bId].size()-1].fHits);
132  //*(mFakeDB->mDB[abs(PDG)][bId][mFakeDB->mDB[abs(PDG)][bId].size()-1].fHits) = *(dbEntry->fHits);
133  // Really needed?;
134  //dbEntry->fHits->Delete();
135  } //for evt
136 
137  dbFile.Close();
138 
139  return 0;
140 } // EicCalorimeterDigiHitProducer::UseFakeMoCaPointDatabase()
141 #endif
142 // -------------------------------------------------------------------------
143 
144 int EicCalorimeterDigiHitProducer::RequestTimeSpectra(Double_t tRange, UInt_t tDim)
145 {
146  if (!mDigi || !tRange || !tDim) return -1;
147 
148  mDigi->mTimeRange = tRange;
149  mDigi->mTimeDim = tDim;
150 
151  mDigi->mTimeBinWidth = tRange / tDim;
152 
153  return 0;
154 } // EicCalorimeterDigiHitProducer::RequestTimeSpectra()
155 
156 // -------------------------------------------------------------------------
157 
159 {
160 #if _MOVED_
161  // Loop through all the maps and set sensitive flags for those, whose
162  // top-level volume names match the requested ones;
163  for(int iq=0; iq<mGptr->GetMapNum(); iq++)
164  {
165  EicGeoMap *fmap = mGptr->GetMapPtrViaMapID(iq);
166 
167  const std::pair<TString, double> *entry =
169 
170  if (entry) fmap->SetSensitivityFlag(entry->second);
171  } //for iq
172 #endif
173 
174  // May want to move this stuff to EicDigiHitProducer::Init();
176  //mFakeMoCaPointArray = (TClonesArray*) ioman->GetObject(mDetName->Name() + "FakeMoCaPoint");
177  //if ( !mFakeMoCaPointArray ) {
178  //std::cout << "-W- "<<mDetName->Name()<<"DigiHitProducer::Init: "
179  // << "No "<<mDetName->Name()<<"Fake MC point array!" << std::endl;
180  //return kERROR;
181  //} //if
182 
183  mDigiHitArray = new TClonesArray("EicCalorimeterDigiHit");
184  //ioman->Register(mDetName->Name() + "DigiHit", mDetName->NAME(), mDigiHitArray, mPersistence);
185  ioman->Register(mDetName->Name() + "CalorimeterDigiHit", mDetName->NAME(), mDigiHitArray, mPersistence);
186 
187  return kSUCCESS;
188 } // EicCalorimeterDigiHitProducer::ExtraInit()
189 
190 // -------------------------------------------------------------------------
191 
193 {
194  // Obviously need to clean cell array before next event;
195  mCells.clear();
196 
197  return 0;
198 } // EicCalorimeterDigiHitProducer::PreExec()
199 
200 // -------------------------------------------------------------------------
201 
203 {
204 
205  return 0;
206 } // EicCalorimeterDigiHitProducer::HandleHitCore()
207 
208 // -------------------------------------------------------------------------
209 
211 {
212  // Obtain map pointer;
213  const EicGeoMap *fmap = mGptr->GetMapPtrViaHitMultiIndex(point->GetMultiIndex());
214 
215  // If map is not declared as "sensitive" (so its top-level volume is not sensitive)
216  // and energy deposit accouting is not requested, no reason to allocate a new cell;
217  if ((!fmap || !fmap->IsSensitive()) && !EnergyDepositAccountingRequested()) return 0;
218 
220 
221  //#ifdef _OLD_STYLE_
222  CalorimeterCell *cell = &mCells[xy];
223  //#else
224  //ULogicalIndex_t xy_screwed = ((xy & 0x0003) << 16) | ((xy & 0x000C) >> 2);
225  //CalorimeterCell *cell = &mCells[xy_screwed];
226  //#endif
227  CalorimeterCellParent *parent =
228  &cell->mCellParents[std::pair<UInt_t, UInt_t>(point->GetPrimaryMotherID(), point->GetSecondaryMotherID())];
229 
230  // If timing spectrum is needed on output (and unless this cell was initialized
231  // already), do it now;
233  if ( !parent->mZCoordBins) parent->InitializeZCoordSpectrum(mDigi->mZCoordDim);
234 
235  // Default is the full GEANT volume name;
236  const char *volumePlottingName = fmap->GetInnermostVolumeName()->Data();
237 
238  // If energy deposit accounting requested, do it now; do NOT account Birk's law here;
240  // Figure out which name to use to fill out energy deposit entries; the logic is: if
241  // exact name match exists, use it; otherwise use pattern name;
242  {
243  // FIXME: well, even that this stuff is useful for test purposes only, may want
244  // to optimize it a bit later (look-up table, etc);
245  const std::pair<TString, double> *entry =
246  /*mDigi->*/mSensitiveVolumes.ExactMatch(volumePlottingName);
247 
248  if (!entry) {
249  entry = /*mDigi->*/mSensitiveVolumes.PrefixMatch(volumePlottingName);
250 
251  if (entry) volumePlottingName = entry->first.Data();
252  } //if
253  }
254 
255  if (fmap->IsSensitive())
256  cell->mEnergyDeposits[volumePlottingName].mSensitive += point->GetEnergyLoss();
257  else
258  cell->mEnergyDeposits[volumePlottingName].mPassive += point->GetEnergyLoss();
259  } //if
260 
261  // If map is not "sensitive", just return; nothing else to do;
262  if (!fmap->IsSensitive()) return 0;
263 
264  {
265  // Figure out whether have access to cell length;
266  const CalorimeterGeoParData* cGptr = dynamic_cast <const CalorimeterGeoParData*>(mGptr);
267  double towerLength = cGptr ? 0.1 * cGptr->mCellLength : 0.0, local[3];
268  //, towerLength = 0.1 * (dynamic_cast <const CalorimeterGeoParData*>(mGptr))->mCellLength;
269 
270  // In principle mGptr can be a pointer to basic EicGeoParData rather than
271  // CalorimeterGeoParData (see two-in-one.C in tutorials); in this case mGptr->mCellLength
272  // is not available (and also I do not want to give it by hand in digitization.C as an extra
273  // parameter in order to avoid inconsistencies); the code should still work though, without
274  // Z-binning (so ignore attenuation length);
275  if (!towerLength && (mDigi->mZCoordDim != 1 || mDigi->mAttenuationLength)) {
276  printf("\n *** either mDigi->mZCoordDim != 1 or mDigi->mAttenuationLength != 0.0 ***\n"
277  " *** but tower length unknown, sorry! ***\n\n");
278  exit(0);
279  } //if
280 
281  {
282  // Figure out local Z-coordinate *with*respect*to*TOWER*middle; yes, not with respect to
283  // EmCal fiber piece or HCal sintillator plate (because attenuation is defined in tower
284  // coordinates); also calculate respective Z-bin; NB: ;
285  TVector3 middle( 0.5 * (point->GetPosIn() + point->GetPosOut()) );
286 
287  double midarr[3] = {middle.X(), middle.Y(), middle.Z()};
288 
290 
291  //mGeometryHub->mLookup + mGeometryHub->GetGptr()->GetX(xy)*mGeometryHub->mRealDim[1]*mGeometryHub->mRealDim[2] +
292  //mGeometryHub->GetGptr()->GetY(xy)*mGeometryHub->mRealDim[2] + mGeometryHub->GetGptr()->GetZ(xy);
293  assert(node->mGeoNode);
294 
295  // Transformation from MARS to tower coordinate system;
296  node->mGeoMtx->MasterToLocal(midarr, local);
297  }
298 
299 
300  // Z-coordinate in the range [0..fzLen] rather than +/-fzLen/2; reset to 0.0 if tower
301  // length unknown, just in order to avoid confusions in all other places;
302  double zCoord = towerLength ? local[2] + towerLength/2. : 0.0;
303 
304  //printf("%f %f %d\n", zCoord, towerLength, mDigi->mZCoordDim); exit(0);
305  // Respective bin in Z-plot;
306  int nz = towerLength ? (int)floor(zCoord/(towerLength/mDigi->mZCoordDim)) : 0;
307  //assert(nz >= 0 && nz < mDigi->mZDim);
308  if (nz < 0 || nz >= mDigi->mZCoordDim) {
309  // Consider to regularize?; hard to control boundary conditions; think later;
310  if (nz < 0)
311  nz = 0;
312  else
313  if (nz >= mDigi->mZCoordDim)
314  nz = mDigi->mZCoordDim - 1;
315  } //if
316 
317  CalorimeterCellZCoordBin *zbin = parent->mZCoordBins + nz;
318 
319  //return 0;
320 
321  double qTime = point->GetTime();
322  // Should not happen, see EicDetector::ProcessHits();
323  assert(/*point->GetEnergyLoss() &&*/ point->GetStep());
324  // Rescale by Birk's law right here;
325  double dE = point->GetEnergyLoss()/
326  (1. + fmap->GetBirkConstant()*point->GetEnergyLoss()/point->GetStep());
327 
328  // Birk's law corrected energy deposit for sensitive volumes only;
330  cell->mEnergyDeposits[volumePlottingName].mSensitiveBirk += dE;
331 
332  zbin->mHitNum++;
333  zbin->mEnergyDeposit += dE;
334  zbin->mTime += dE*qTime;
335  zbin->mZ += dE*zCoord;
336 
337  //#ifdef _OLD_STYLE_
338  //printf("@@QQ@@ %0X %0X %f\n", point->GetMultiIndex(), xy, dE);
339  //#else
340  //printf("@@QQ@@ %0X %0X %f\n", point->GetMultiIndex(), xy_screwed, dE);
341  //#endif
342  }
343 
344  return HandleHitCore(cell);
345 } // EicCalorimeterDigiHitProducer::HandleHit()
346 
347 // -------------------------------------------------------------------------
348 
349 #if _LATER_
350 LogicalVolumeLookupTableEntry *EicCalorimeterDigiHitProducer::findHitNode(EicFakeMoCaPoint *point,
351  double master[3],
352  double local[3])
353 {
354 #if _LATER_
355  // Return same node as EicFakeMoCaPoint; no optimization can go faster than this
356  // branch (which is of course fake);
357 #if 0
358  UGeo_t xyPoint = GetGptr()->remapGeantMultiIndex(point->fMultiIndex) & 0xFFFFFFFF;
359  CalorimeterLookupTableEntry *node =
360  mLookup + GetGptr()->getX(xyPoint)*mRealDim[1]*mRealDim[2] + GetGptr()->getY(xyPoint)*mRealDim[2] + GetGptr()->getZ(xyPoint);
361  assert(node->gNode);
362 
363  //printf(" -> trying %s\n", node->first->GetName());
364  node->gMtx->MasterToLocal(master, local);
365  return node;
366 #endif
367 
368  // Dumb linear search through all the calorimeter cells; prohibitively CPU expensive, but
369  // safe (debugging purposes only -> to check, that optimized version gives identical result);
370 #if 0
371  for(unsigned nd=0; nd<mDim3D; nd++)
372  {
373  CalorimeterLookupTableEntry *node = mLookup + nd;
374 
375  if (!node->gNode) continue;
376 
377  //printf(" -> trying %s\n", node->first->GetName());
378  node->gMtx->MasterToLocal(master, local);
379 
380  //node->second->Print();
381  //printf("%f %f %f\n", master[0], master[1], master[2]);
382  //printf(" %f %f %f\n", local[0], local[1], local[2]);
383 
384  if (node->gNode->GetVolume()->GetShape()->Contains(local)) return node;
385  } //for nd
386 #endif
387 
388  //
389  // The below code checks hit-to-cell match in rectangular slices starting
390  // off EicFakeMoCaPoint cell; perhaps can be optimized further;
391  //
392 #if 1
393  // Check last node (perhaps there is a match); does it work actually?;
394  if (mLastOkNode)
395  {
396  mLastOkNode->mGeoMtx->MasterToLocal(master, local);
397 
398  if (mLastOkNode->mGeoNode->GetVolume()->GetShape()->Contains(local))
399  return mLastOkNode;
400  else
401  mLastOkNode = 0;
402  } //if
403 
404  // So: move outwards starting off the node which contained FakeMoCaPoint;
405  UGeo_t xyPoint = mGeometryHub->GetGptr()->RemapMultiIndex(point->fMultiIndex) & 0xFFFFFFFF;
406  unsigned ixyzPoint[3] = {mGeometryHub->GetGptr()->GetX(xyPoint),
407  mGeometryHub->GetGptr()->GetY(xyPoint),
408  mGeometryHub->GetGptr()->GetZ(xyPoint)};
409  unsigned dMax[3], lrDist[2][3], d3Max = 0;
410 
411  // Figure out absolute max 1D distances to map edges from this point;
412  for(unsigned iq=0; iq<3; iq++)
413  {
414  lrDist[0][iq] = ixyzPoint[iq];
415  lrDist[1][iq] = mGeometryHub->GetRealDim(iq) - ixyzPoint[iq] - 1;
416  dMax [iq] = lrDist[0][iq] > lrDist[1][iq] ? lrDist[0][iq] : lrDist[1][iq];
417 
418  d3Max += dMax[iq];
419  } //for iq
420 
421  double dMinOverall = 0.0;
422 
423  // Loop through all possible Chebyshev 3D distances, starting off the actually hit node;
424  for(unsigned dst=0; dst<d3Max; dst++)
425  {
426  double dMin = 0.0;
427 
428  for(unsigned ix=0; ix<=dst; ix++)
429  for(unsigned iy=0; iy<=dst-ix; iy++)
430  {
431  // So that ix+iy+iz=dst, right?;
432  unsigned iz = dst - ix - iy;
433 
434  // And loop through all 3 LR-combinations;
435  for(unsigned ixlr=0; ixlr<2; ixlr++)
436  {
437  if (ix > lrDist[ixlr][0]) continue;
438 
439  unsigned idX = ixyzPoint[0] + (ixlr ? ix : -ix);
440 
441  for(unsigned iylr=0; iylr<2; iylr++)
442  {
443  if (iy > lrDist[iylr][1]) continue;
444 
445  unsigned idY = ixyzPoint[1] + (iylr ? iy : -iy);
446 
447  for(unsigned izlr=0; izlr<2; izlr++)
448  {
449  if (iz > lrDist[izlr][2]) continue;
450 
451  unsigned idZ = ixyzPoint[2] + (izlr ? iz : -iz);
452 
453  // Eventually get node pointer and check whether 3D point is
454  // inside this volume or not;
455  CalorimeterLookupTableEntry *node =
456  mGeometryHub->mLookup + idX*mGeometryHub->mRealDim[1]*mGeometryHub->mRealDim[2] +
457  idY*mGeometryHub->mRealDim[2] + idZ;
458 
459  if (!node->mGeoNode) continue;
460 
461  //printf(" -> trying %s\n", node->first->GetName());
462  node->mGeoMtx->MasterToLocal(master, local);
463 
464  // Calculate point-to-volume-center distance in XY-projection; since this
465  // appens in the *local* cell coordinate system, should be correct for
466  // both FEMC (XY) and CEMC (XZ) calorimeters;
467  double dd = sqrt(_SQR_(local[0]) + _SQR_(local[1])/* + _SQR_(local[2])*/);
468 
469  // If 'dd' is not the smallest of the ones checked so far, no
470  // sense to call this expensive function; this is not really true
471  // for arbitrary calorimeter, but seems to be so if blocks are
472  // packed in a "regular" fashion; perhaps allow to disable for
473  // test purposes?; think later;
474  if ((!dMinOverall || dd <= dMinOverall) &&
475  node->mGeoNode->GetVolume()->GetShape()->Contains(local))
476  {
477  mLastOkNode = node;
478  return node;
479  } //if
480 
481  if (!dMin || dd < dMin) dMin = dd;
482  if (!dMinOverall || dd < dMinOverall) dMinOverall = dd;
483  } //for izlr
484  } //for iylr
485  } //for ixlr
486  } //for ix..iz
487 
488  // Well, if true 3D distances from cell centers at this 'dst' to point
489  // transfered to local coordinates started to get bigger, I'm definitely
490  // out of luck (would most likely mean 3D point fits into a gap or perhaps
491  // is outside of calorimeter rad.length reach); may actually want to
492  // still account this point taking the cell with *smallest* distance if
493  // Z-coordinate is Ok (so then it's an intercell gap); again, may want
494  // to disable this check (though it should work for "regular" volume
495  // collection); think later;
496  if (dMinOverall && dMin > dMinOverall) break;
497  } //for dst
498 #endif
499 #endif
500 
501  // Missed any calorimeter cell -> return NULL pointer;
502  return 0;
503 } // EicCalorimeterDigiHitProducer::findHitNode
504 #endif
505 // -------------------------------------------------------------------------
506 
507 #if _LATER_
508 int EicCalorimeterDigiHitProducer::ProcessFakeMoCaPoints( void )
509 {
510 #if _LATER_
511  // All this eKin/mass stuff needs to be optimized later, please;
512  TDatabasePDG *pdgTable = TDatabasePDG::Instance(); assert(pdgTable);
513 
514  unsigned nEntries = fFakeMoCaPointArray->GetEntriesFast();
515  //cout << nEntries << endl;
516 
517  for(unsigned iq=0; iq<nEntries; iq++)
518  {
519  EicFakeMoCaPoint *point = (EicFakeMoCaPoint*) fFakeMoCaPointArray->At(iq);
520 
521  // Figure out PDG and kin.energy bin;
522  int PDG = point->getPDG();
523  TParticlePDG *particle = pdgTable->GetParticle(PDG);
524  double mass = particle->Mass(), eKin = sqrt(_SQR_(mass) + point->p().Mag2()) - mass;
525 
526  // Get random shower database entry matching PDG & eKin bin;
527  EicFakeMoCaPointDbEntry *dbEntry = mFakeDB->getDbEntry(PDG, point->p());
528 
529  // Either PDG unknown or no entries in this energy bin;
530  if (!dbEntry)
531  {
532  std::cout << "-E- EicCalorimeterDigiHitProducer::ProcessFakeMoCaPoints(): mFakeDB->getDbEntry() returned NULL!" << std::endl;
533  return -1;
534  } //if
535 
536  // Figure out energy scaling coefficient; based on what?;
537  double eKinDb = sqrt(_SQR_(mass) + dbEntry->fP.Mag2()) - mass;
538  double scale = eKin / eKinDb;
539 
540  unsigned nHits = dbEntry->fHits->GetEntriesFast();
541 
542  // Construct 3D rotation matrix, which moves unit vector along dbEntry->fP to
543  // a unit vector along point->p(); first calculate a vector along
544  // [dbEntry->fP x point->p()]; NB: no need to normalize it (rr.Rotate() works
545  // fine without that); use TVector3 and TMatrix machinery here and below;
546  TVector3 axis = dbEntry->fP.Cross(point->p());
547  // Rotation angle to align shower with point-p(); yeat another random
548  // angle [0 .. 2pi] around point->p();
549  double angle = dbEntry->fP.Angle(point->p()), yaangle = gRandom->Uniform(2.*TMath::Pi());
550  // Whatever dummy frame to be used with rr.Rotate(); C++ is funny sometimes;
551  TRotation rr;
552 
553  // Loop through all energy deposit hits in this shower;
554  for(unsigned ip=0; ip<nHits; ip++)
555  {
556  EicFakeMoCaPointDbHit *dbHit = (EicFakeMoCaPointDbHit*)dbEntry->fHits->At(ip);
557 
558  // Figure out energy deposit vertex; do in steps for clarity; original offset
559  // is just 3D vector difference between database shower vertex and point of
560  // energy deposit;
561  TVector3 origOffset = dbHit->getHitCoord() - dbEntry->getPrimaryVtx();
562  // Rotate to align with point->p();
563  TVector3 rotOffset = rr.Rotate(angle, axis) * origOffset;
564  // Rotate by random angle along point->p() in addition and add to MoCaPoint vertex;
565  TVector3 vtx = point->vtx() + rr.Rotate(yaangle, point->p()) * rotOffset;
566  double master[3] = {vtx.x(), vtx.y(), vtx.z()}, local[3];
567 
568  // Figure out, which calorimeter cell fiducial volume this 3D point belongs to;
569  CalorimeterLookupTableEntry *node = findHitNode(point, master, local);
570 
571  if (!node) continue;
572 
573  UGeo_t xy = node->gGeo;
574 
575  //
576  // -> these codes should be unified with HandleHit(); later;
577  //
578 
579  CalorimeterCell *cell =
580  &mCells[std::pair<UInt_t, UInt_t>(xy, mDigi->mAcknowledgePrimaryMother ? point->fPrimaryMotherId : 0)];
581 
582  // Energy deposit accounting does not make sense here?;
583 
584  // If timing spectrum is needed on output (and unless this cell was initialized
585  // already), do it now;
586  if (mDigi->mTDim && !cell->fTimeSpectrum) cell->initializeTimeSpectrum(mDigi->mTDim);
587  if ( !cell->mZCoordBins) cell->initializeZSpectrum (mDigi->mZDim);
588 
589  // Well, could use node->gNode here to assign cell->fzLen; assume, that calorimeter
590  // cells are all of the same length -> use same procedure as in HandleHit();
591  if (!cell->fzLen) cell->fzLen = 2*fGeoH->GetSensorDimensionsPath(point->fVolumePath)[2];
592  // Z-coordinate in the range [0..fzLen] rather than +/-fzLen/2;
593  double zCoord = local[2] + cell->fzLen/2.;
594 
595  // Respective bin in Z-plot;
596  int nz = (int)floor(zCoord/(cell->fzLen/mDigi->mZDim));
597  //assert(nz >= 0 && nz < mDigi->mZDim);
598  if (nz < 0 || nz >= mDigi->mZDim)
599  {
600  // Consider to regularize?; hard to control boundary conditions; think later;
601  if (nz == -1)
602  nz = 0;
603  else
604  if (nz == mDigi->mZDim)
605  nz = mDigi->mZDim - 1;
606  else
607  // Think on this condition later;
608  break;
609  }
610 
611  CalorimeterCellZCoordBin *zbin = cell->mZCoordBins + nz;
612 
613  double qTime = point->GetTime();
614  // Take care to implemenet Birk's law here as well;
615  assert(0);
616  double dE = scale * dbHit->getEnergyLoss();
617 
618  zbin->fHitNum++;
619  zbin->mEnergyDeposit += dE;
620  zbin->fTime += dE*qTime;
621  zbin->mZ += dE*zCoord;
622 
623  // Check condition later,
624  if (HandleHitCore(cell))
625  {
626  std::cout << "-E- EicCalorimeterDigiHitProducer::HandleHitCore(): faled!" << std::endl;
627  return -1;
628  } //if
629  } //for ip
630  } //for iq
631 #endif
632 
633  return 0;
634 } // EicCalorimeterDigiHitProducer::ProcessFakeMoCaPoints()
635 #endif
636 // -------------------------------------------------------------------------
637 
638 //
639 // FIXME: may want to optimize this stuff for performance later;
640 //
641 
643 {
644  // Allocate new element if needed; always 1000 channels, scale is configurable;
645  if (mEnergyDepositPlots.find(name) == mEnergyDepositPlots.end())
646  mEnergyDepositPlots[name] =
648 
649  mEnergyDepositPlots[name]->Fill(dE);
650 } // EicCalorimeterDigiHitProducer::FillEnergyDepositPlot()
651 
652 // -------------------------------------------------------------------------
653 
655 {
656  // Require fake hit database to be present if MC file has fFakeMoCaPointArray
657  // entries; just be consistent;
658 #if _LATER_
659  if (mFakeMoCaPointArray->GetEntriesFast() && !mFakeDB)
660  {
661  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): fFakeMoCaPointArray entries present," << std::endl;
662  std::cout << "-E- but no fake hit database available -> forgot useFakeMoCaPointDatabase() call?" << std::endl;
663  return -1;
664  } //if
665 
666  // Convert EicFakeMoCaPoint entries to CalorimeterCell ones in a way both branches
667  // merge and the remainder of the code "does not notice" that part of the shower
668  // was fake;
669  if (mFakeDB && ProcessFakeMoCaPoints())
670  {
671  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): ProcessFakeMoCaPoints() failed!" << std::endl;
672  return -1;
673  } //if
674 #endif
675 
676  // Energy deposit sums (passive/sensitive/sensitive-after-Birk) over all accounted cells;
677  // makes sense for test beam conditions only (like T1018 test run);
678  std::map<std::string, EnergyDeposit> EnergyDepositSum;
679 
680  // Well, application should call setLightYield();
682  {
683  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): light yield is not defined!" << std::endl;
684  return -1;
685  } //if
686 
687  switch (mDigi->mSensorType)
688  {
690  // And yet another sanity check; do once, or?;
692  {
693  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): simulating noise "
694  "requires timing gate set!" << std::endl;
695  return -1;
696  } //if
697  break;
699  // No checks here, default values are useable;
700  break;
701  default:
702  // Sensort type should be declared, no default;
703  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): sensor type is not defined!" << std::endl;
704  return -1;
705  } //switch
706 
707  // And yet another one;
709  {
710  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): timing control requires "
711  "light propagation velocity set!" << std::endl;
712  return -1;
713  } //if
714 
715  // And also check, that at least one qSENSOR group is defined;
718  {
719  std::cout << "-E- EicCalorimeterDigiHitProducer::PostExec(): at least one of the upstream/downstream "
720  "ends should be equipped with sensors!" << std::endl;
721  return -1;
722  } //if
723 
724  // Just a shortcut; it looks like 'towerLength = 0' (unknown) case will work here;
725  const CalorimeterGeoParData* cGptr = dynamic_cast <const CalorimeterGeoParData*>(mGptr);
726  const double towerLength = cGptr ? 0.1 * cGptr->mCellLength : 0.0;
727  //const double towerLength = 0.1 * (dynamic_cast <const CalorimeterGeoParData*>(mGptr))->mCellLength;
728 
729  // Loop through all the cells and perform the actual digitization;
730  for (std::map<ULogicalIndex_t, CalorimeterCell>::iterator it=mCells.begin(); it!=mCells.end(); ++it) {
731  // Yes, want to work with 'cell' pointer rather than 'it->second';
732  CalorimeterCell *cell = &it->second;
733 
734  for (std::map<std::pair<UInt_t, UInt_t>, CalorimeterCellParent>::iterator jt=cell->mCellParents.begin();
735  jt!=cell->mCellParents.end(); ++jt) {
736  CalorimeterCellParent *parent = &jt->second;
737 
738  // Loop through all Z-bins independently; in principle could merge input from all
739  // parent particles before simulating the photon yield; yet want to have mPhotonCount
740  // separately in th eoutput -> just do everything separately assuming energy deposits
741  // from different parents do not interfere due to something like Birk's saturation
742  // effects (and I guess they should not);
743  for(unsigned iz=0; iz<mDigi->mZCoordDim; iz++) {
744  CalorimeterCellZCoordBin *zbin = parent->mZCoordBins + iz;
745 
746  if (!zbin->mEnergyDeposit) continue;
747 
748  // First normalize times and <z> in Z-bins;
749  zbin->mTime /= zbin->mEnergyDeposit; zbin->mZ /= zbin->mEnergyDeposit;
750 
751  // Simulate separately contributions from upstream and downstream ends;
752  for(unsigned ud=0; ud<2; ud++) {
753  CalorimeterSensorGroup *sgroup = mDigi->mSensors + ud;
754 
755  // If this sensor group end is dead, just skip;
756  if (sgroup->mGroupType == CalorimeterSensorGroup::Dead) continue;
757 
758  // If sensor group is qREFLECTION, but reflectivity is 0.0, also skip;
759  if (sgroup->mGroupType == CalorimeterSensorGroup::Reflection && !sgroup->mReflectivity) continue;
760 
761  //
762  // The rest of this calculation is the same for both ::Reflection and ::Sensor
763  // types; the only difference is how attenuation factor is calculated; well, and
764  // arrival time as well;
765  //
766 
767  // Estimate attenuation; account only longitudinal coordinate in the crystal,
768  // since (to first order) the rest of the optical path will affect all emitted
769  // photons in the same way; well, real life can be much more complicated of course;
770  double attenuation = 1.0, distance = ud ? towerLength - zbin->mZ : zbin->mZ;
771 
772  if (mDigi->mAttenuationLength) attenuation *= exp(-distance/mDigi->mAttenuationLength);
773 
774  // If sensor group is in fact reflective end, rescale attenuation factor with
775  // a deterministic reflectivity factor and account extra attenuation length on
776  // the full way back to the other end; NB: it was checked during initialization,
777  // that at least one qSENSOR group is present;
779  {
780  attenuation *= sgroup->mReflectivity;
781 
783  attenuation *= exp(-towerLength/mDigi->mAttenuationLength);
784  } //if
785 
786  // FIXME: figure out what's wrong with this code;
787 #if _BACK_
788  assert(0);
789 
790  // Calculate expected number of photons; ignore wave length, etc for now;
791  // assume pure Poisson distribution for now; NB: assume 1/2 average yield
792  // for both ends;
793  unsigned NG;
794  switch (mDigi->mSensorType)
795  {
797  //NG = gRandom->Poisson(zbin->mEnergyDeposit*mDigi->mLightYieldRescaled/2.);
798  NG = gRandom->Poisson(zbin->mEnergyDeposit*mDigi->mPrimaryLightYield/2.);
799  break;
800 #if _THINK_
801  // Since mDigi->mPrimaryLightYield is decoupled from sampling fraction
802  // as of 2014/07/17, need to rething calculation for APD case one day;
804  // Rescaling back and forth here should take distribution widening in a proper
805  // way here; may want to cross-check this later;
806  NG = (Int_t)(mDigi->mApdExcessNoiseFactor*gRandom->PoissonD(zbin->mEnergyDeposit*
807  (mDigi->mLightYieldRescaled/2.)/
809  break;
810 #endif
811  default:
812  // Put something here if ever want to implement say PMTs;
813  assert(0);
814  } //switch
815 
816  // Loop through all the expected photons; may eventually want to drop
817  // spectrum construction and save CPU time here;
818  for(unsigned ng=0; ng<NG; ng++)
819  {
820  // Account for attenuation; do it more efficiently later (just reduce
821  // NG before entering this loop);
822  if (gRandom->Uniform() > attenuation) continue;
823 
824  // Simulate decay time if needed; ignore possible spread in arrival times
825  // at the moment of energy deposit;
826  double dTime = zbin->fTime + (mDigi->mDecayConstant ?
827  gRandom->Exp(mDigi->mDecayConstant) : 0);
828 
829  // Add propagation time if needed; add cell length in case of reflective end;
831  //dTime += (cell->fzLen - zbin->mZ)/mDigi->mLightPropagationVelocity;
832  dTime += (sgroup->groupType == qREFLECTION ? distance + cell->fzLen : distance)/
834 
835  // Increment entry in respective time bin;
836  if (mDigi->mTDim)
837  {
838  int tid = (int)floor(dTime/mDigi->mTBinWidth);
839  if (tid < 0 || tid >= mDigi->mTDim) continue;
840 
841  cell->fTimeSpectrum[tid]++;
842  } //if
843 
844  // Attenuation estimate will require its own normalization;
845  //cell->fAttenuation += attenuation;
846  //attenuationNorm++;
847 
848  // Check gate condition and increment overall amplitude if fit; yes,
849  // just use original double values here and inclusive limits;
850  if (!mDigi->mTimingGateWidth ||
851  (dTime >= mDigi->mTimingGateOffset &&
852  dTime <= mDigi->mTimingGateOffset + mDigi->mTimingGateWidth))
853  cell->mPhotonCount += 1;
854  } //for ng
855 #else
856  switch (mDigi->mSensorType)
857  {
859  parent->mSignalPhotonCount +=
860  gRandom->Poisson(attenuation*zbin->mEnergyDeposit*mDigi->mPrimaryLightYield/2.);
861  break;
863  parent->mSignalPhotonCount += (Int_t)(mDigi->mApdExcessNoiseFactor*
864  gRandom->PoissonD(attenuation*
867  break;
868  default:
869  // Put something here if ever want to implement say PMTs;
870  assert(0);
871  } //switch
872 #endif
873  } //for ud
874  } //for iz
875  } //for jt
876 
877  // Well, eventually may want to put noise contribution in;
878  switch (mDigi->mSensorType)
879  {
881  // Assume for SiPM it is proportional to the GATE width;
882  cell->mNoisePhotonCount =
884  break;
886  // THINK: well, I guess I just need to rescale expected ASIC noise by APD gain factor
887  // in order to equivalize it in "units" with the registered photon count;
888  cell->mNoisePhotonCount = (Int_t)(gRandom->Gaus(0.0, mDigi->mApdEquivalentNoiseCharge)/
890  break;
891  default:
892  // Put something here if ever want to implement say PMTs;
893  assert(0);
894  } //switch
895 
897  for(std::map<std::string, EnergyDeposit>::iterator itr=it->second.mEnergyDeposits.begin();
898  itr!=it->second.mEnergyDeposits.end(); itr++) {
899 
900  // FIXME: '+' operator, please;
901  EnergyDepositSum[itr->first].mPassive += itr->second.mPassive;
902  EnergyDepositSum[itr->first].mSensitive += itr->second.mSensitive;
903  EnergyDepositSum[itr->first].mSensitiveBirk += itr->second.mSensitiveBirk;
904  } //if..fot it
905 
906  // Yes: cut away low cells right here?; NB:
907  // - no light yield rescaling is needed here since I deal with cell as a whole!;
908  // - it is assumed that this threshold is yet lower than any of the clustering
909  // code thresholds; so the only purpose is to reduce digitized file throwing
910  // away really very low deposits;
911 #if _THINK_
912  // - yet retain cell on output if energy accounting was requested; otherwise
913  // would through away cells with eg. only absorber dE deposit;
914 #endif
915  if (//energyDepositAccountingRequested() ||
916  // NB: small-deposit APD-equipped cells may go negative in photon
917  // count; default threshold is 0.0, so for these cells check is effective;
918  // is it good or bad or does not matter?; think later;
920  //#ifndef _OLD_STYLE_
921  //ULogicalIndex_t xy_unscrewed = ((it->first >> 16) & 0x0003) | ((it->first << 2) & 0x000C);
922  //#endif
923 
924  new((*mDigiHitArray)[mDigiHitArray->GetEntriesFast()])
925  //#ifdef _OLD_STYLE_
926  // I believe it is safe to use "cell" pointer here (so it is persistent), or?;
927  EicCalorimeterDigiHit(it->first, cell);
928  //#else
929  //EicCalorimeterDigiHit(xy_unscrewed, cell);
930  //#endif
931 
932  //printf("@@QQ@@ %8X %d\n", it->first, cell->GetPhotonCountSum());
933  }
934  } //for cells
935 
936  // Produce printout and fill out dE/dx plots;
938  for(std::map<std::string, EnergyDeposit>::iterator itr=EnergyDepositSum.begin();
939  itr!=EnergyDepositSum.end(); itr++) {
940  printf("%-20s -> %9.3f MeV (passive) ... %9.3f MeV (sensitive) ->"
941  " %9.3f MeV (after Birk)\n", itr->first.c_str(),
942  1E3*itr->second.mPassive, 1E3*itr->second.mSensitive, 1E3*itr->second.mSensitiveBirk);
943 
944  // FIXME: well, this stuff should clearly be performance optimized;
945  if (itr->second.mPassive)
946  FillEnergyDepositPlot((std::string(mDetName->NAME()) + std::string("-") + itr->first +
947  std::string("-dE-passive")).c_str(), 1E3*itr->second.mPassive);
948 
949  // Want same statistics here?;
950  if (itr->second.mSensitive) {
951  FillEnergyDepositPlot((std::string(mDetName->NAME()) + std::string("-") + itr->first +
952  std::string("-dE-sensitive")).c_str(), 1E3*itr->second.mSensitive);
953 
954  FillEnergyDepositPlot((std::string(mDetName->NAME()) + std::string("-") + itr->first +
955  std::string("-dE-sensitive-Birk")).c_str(), 1E3*itr->second.mSensitiveBirk);
956  } //
957  } //if..for it
958 
959  return 0;
960 } // EicCalorimeterDigiHitProducer::PostExec()
961 
962 // -------------------------------------------------------------------------
963 
965 {
966  FairRun *fRun = FairRun::Instance();
967 
968  // I guess there is no need to save/restore current directory here?;
969  fRun->GetOutputFile()->cd();
970 
971  // Save energy deposit plots if needed;
972  for(std::map<std::string, TH1F*>::iterator itr=mEnergyDepositPlots.begin();
973  itr!=mEnergyDepositPlots.end(); itr++)
974  itr->second->Write();
975 
976  // Yes, save under detector-specific pre-defined name;
977  //mDigi->Write(mDetName->Name() + "DigiParData");
978  mDigi->Write(mDetName->Name() + "CalorimeterDigiParData");
979 } // EicCalorimeterDigiHitProducer::Finish()
980 
981 // -------------------------------------------------------------------------
982