G4StationFastCerenkov.cc
Go to the documentation of this file.
1 //
2 // ********************************************************************
3 // * DISCLAIMER *
4 // * *
5 // * The following disclaimer summarizes all the specific disclaimers *
6 // * of contributors to this software. The specific disclaimers,which *
7 // * govern, are listed with their locations in: *
8 // * http://cern.ch/geant4/license *
9 // * *
10 // * Neither the authors of this software system, nor their employing *
11 // * institutes,nor the agencies providing financial support for this *
12 // * work make any representation or warranty, express or implied, *
13 // * regarding this software system or assume any liability for its *
14 // * use. *
15 // * *
16 // * This code implementation is the intellectual property of the *
17 // * GEANT4 collaboration. *
18 // * By copying, distributing or modifying the Program (or any work *
19 // * based on the Program) you indicate your acceptance of this *
20 // * statement, and all its terms. *
21 // ********************************************************************
22 //
23 // MODIFIED by P Skelton November 2003 - only use this as part of the
24 // G4FasterSimulatorModule in the Auger observatory simulation.
25 //
26 // Due to size, the code is split into several files. The subsidiary ones
27 // are #included into this file Subsidiary file: Propagate.icc
28 
29 #include "G4StationFastCerenkov.h"
30 #include "G4StationConstruction.h"
31 #include "G4Utils.h"
32 
33 #include <fwk/RunController.h>
34 #include <fwk/CentralConfig.h>
35 #include <utl/Reader.h>
36 #include <utl/AugerException.h>
37 #include <utl/ErrorLogger.h>
38 #include <utl/MathConstants.h>
39 #include <utl/AugerException.h>
40 #include <utl/Math.h>
41 
42 #include <G4PhysicalConstants.hh>
43 #include <G4SystemOfUnits.hh>
44 
45 #include <algorithm>
46 
47 using namespace fwk;
48 using namespace std;
49 using utl::Sqr;
50 
51 
52 #define IN_TANK 0
53 #define DOWN_IN_TANK 1
54 #define IN_DOME_1 11
55 #define IN_DOME_2 12
56 #define IN_DOME_3 13
57 #define IN_DOME_4 14
58 #define IN_INTERFACE_1 21
59 #define IN_INTERFACE_2 22
60 #define IN_INTERFACE_3 23
61 #define IN_INTERFACE_4 24
62 #define INWARD 31
63 #define OUTWARD 32
64 
65 #define TARGET_PMT1 0
66 #define TARGET_PMT2 1
67 #define TARGET_PMT3 2
68 #define TARGET_SPMT 3
69 #define TARGET_WALL 4
70 #define TARGET_FLOOR 5
71 #define BOUNCED 10
72 
73 
74 namespace G4StationSimulatorOG {
75 
76  double G4StationFastCerenkov::fTankHeight;
77  double G4StationFastCerenkov::fTankHalfHeight;
78  double G4StationFastCerenkov::fTankThickness;
79  double G4StationFastCerenkov::fTankRadius;
80  double G4StationFastCerenkov::fTankRadiusSq;
81  double G4StationFastCerenkov::fDomeRadius;
82  double G4StationFastCerenkov::fDomeRadiusz;
83  double G4StationFastCerenkov::fInterfaceRadius;
84  double G4StationFastCerenkov::fInterfaceRadiusz;
85  double G4StationFastCerenkov::fPMTRadius;
86  double G4StationFastCerenkov::fPMTRadiusz;
87  double G4StationFastCerenkov::fDomeRadiusSq;
88  double G4StationFastCerenkov::fDomeRadiuszSq;
89  double G4StationFastCerenkov::fInterfaceRadiusSq;
90  double G4StationFastCerenkov::fInterfaceRadiuszSq;
91  double G4StationFastCerenkov::fPMTRadiusSq;
92  double G4StationFastCerenkov::fPMTRadiuszSq;
93  double G4StationFastCerenkov::fHeightz;
94 
95  bool G4StationFastCerenkov::fHasSPMT;
96  double G4StationFastCerenkov::fDomeRadius_SPMT;
97  double G4StationFastCerenkov::fDomeThick_SPMT;
98  double G4StationFastCerenkov::fInterfaceRadius_SPMT;
99  double G4StationFastCerenkov::fInterfaceThick_SPMT;
100  double G4StationFastCerenkov::fPMTRadius_SPMT;
101  double G4StationFastCerenkov::fPMTActiveRadius_SPMT;
102  double G4StationFastCerenkov::fPMTThick_SPMT;
103  double G4StationFastCerenkov::fDomeRadius_SPMT_Sq;
104  double G4StationFastCerenkov::fInterfaceRadius_SPMT_Sq;
105  double G4StationFastCerenkov::fPMTRadius_SPMT_Sq;
106  double G4StationFastCerenkov::fPMTActiveRadius_SPMT_Sq;
107 
108  double G4StationFastCerenkov::fSigmaAlpha;
109 
110  utl::TabulatedFunction G4StationFastCerenkov::fQE;
111  utl::TabulatedFunction G4StationFastCerenkov::fQE_SPMT;
112 
113  utl::TabulatedFunction G4StationFastCerenkov::fWaterAbsLength;
114 
115  utl::TabulatedFunction G4StationFastCerenkov::fInterfaceRIndex;
116  utl::TabulatedFunction G4StationFastCerenkov::fInterfaceAbsLength;
117 
118  utl::TabulatedFunction G4StationFastCerenkov::fDomeRIndex;
119  utl::TabulatedFunction G4StationFastCerenkov::fDomeAbsLength;
120 
121  utl::TabulatedFunction G4StationFastCerenkov::fLinerReflectivity;
122  utl::TabulatedFunction G4StationFastCerenkov::fLinerSpecularLobe;
123  utl::TabulatedFunction G4StationFastCerenkov::fLinerSpecularSpike;
124  utl::TabulatedFunction G4StationFastCerenkov::fLinerBackscatter;
125  G4ThreeVector G4StationFastCerenkov::fPMTPos[5];
126 
127  double G4StationFastCerenkov::fRoofPos[5];
128 
129 
130  G4StationFastCerenkov::G4StationFastCerenkov(const G4String& processName) :
131  G4VContinuousProcess(processName),
132  fG4StationSimulator(
133  dynamic_cast<G4StationSimulator&>(RunController::GetInstance().GetModule("G4StationSimulatorOG"))
134  )
135  {
136  if (verboseLevel > 0)
137  G4cerr << GetProcessName() << " is created " << G4endl;
139 
140  // Rayleigh Scattering data taken from G4OpRayleigh.cc
141  // isothermal compressibility of water
142  const G4double betat = 7.658e-23 * m3 / MeV;
143  // K Boltzman
144  const G4double kboltz = 8.61739e-11 * MeV / kelvin;
145  // Temperature of water is 10 degrees celsius
146  const G4double temp = 283.15 * kelvin;
147  const G4double hc4 = pow(h_Planck * c_light, 4); // constants defined
148  // in G4PhysicalConstants
149  fRayleighConst = 27 * hc4 / (8 * pow(utl::kPi, 3) * betat * temp * kboltz);
150  }
151 
152 
154  {
155  if (fPhysicsTable) {
156  fPhysicsTable->clearAndDestroy();
157  delete fPhysicsTable;
158  fPhysicsTable = nullptr;
159  }
160  }
161 
162 
163  void
165  {
167 
172 
175 
180 
185 
187 
189 
195 
196  // Fill Q.E. x COLL. EFF. of LPMTs
197  const auto& pmt = dStation.GetPMT(1);
198  fQE = pmt.GetQuantumEfficiency();
199  const double collectionEfficiency = pmt.GetCollectionEfficiency();
200  for (auto& qe : fQE.YRange())
201  qe *= collectionEfficiency;
202  for (auto& en : fQE.XRange())
203  en *= CLHEP::eV / utl::eV;
204 
205  const auto& cs = dStation.GetLocalCoordinateSystem();
206  for (int i = 1; i <= 3; ++i) {
207  fPMTPos[i] = ToG4Vector(dStation.GetPMT(i).GetPosition(), cs, CLHEP::meter/utl::meter);
208  fRoofPos[i] = fTankHeight + fTankThickness - fPMTPos[i].z();
209  }
210 
211  // checked above - PMTs are in expected positions
219 
220  // Fill SmallPMT quantities
221  fHasSPMT = dStation.HasSmallPMT();
222 
223  if (fHasSPMT) {
231 
232  // Fill Q.E. x COLL. EFF. of SPMT
233  const auto& small_pmt = dStation.GetPMT(4);
234  fQE_SPMT = small_pmt.GetQuantumEfficiency();
235  const double collectionEfficiency_SPMT = small_pmt.GetCollectionEfficiency();
236  for (auto& qe : fQE_SPMT.YRange())
237  qe *= collectionEfficiency_SPMT;
238  for (auto& en : fQE_SPMT.XRange())
239  en *= CLHEP::eV / utl::eV;
240 
241  fPMTPos[4] = ToG4Vector(dStation.GetPMT(4).GetPosition(), cs, CLHEP::meter/utl::meter);
242  fRoofPos[4] = fTankHeight + fTankThickness - fPMTPos[4].z();
243 
248  }
249  }
250 
251 
252  void
254  const
255  {
256  for (int i = 0, n = fPhysicsTable->entries(); i < n; ++i)
257  static_cast<G4PhysicsOrderedFreeVector*>((*fPhysicsTable)[i])->DumpValues();
258  }
259 
260 
261  G4VParticleChange*
262  G4StationFastCerenkov::AlongStepDoIt(const G4Track& track, const G4Step& step)
263  {
264  aParticleChange.Initialize(track);
265  const G4DynamicParticle* particle = track.GetDynamicParticle();
266  const G4Material* const material = track.GetMaterial();
267  G4StepPoint* const preStepPoint = step.GetPreStepPoint();
268  G4StepPoint* const postStepPoint = step.GetPostStepPoint();
269 
270  const G4ThreeVector x0 = preStepPoint->GetPosition();
271  const G4ThreeVector p0 = step.GetDeltaPosition().unit();
272  const G4double t0 = preStepPoint->GetGlobalTime();
273 
274  if (t0 > 1e6)
275  return G4VContinuousProcess::AlongStepDoIt(track, step);
276 
277  G4ThreeVector primaryDirection = preStepPoint->GetMomentumDirection();
278 
279  const G4double cosAlpha = primaryDirection.z();
280  const G4double sinAlpha = sqrt(1 - Sqr(cosAlpha));
281  const G4double cosBeta = primaryDirection.x() / sinAlpha;
282  const G4double sinBeta = primaryDirection.y() / sinAlpha;
283 
284  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
285 
286  if (!materialPropertiesTable)
287  return G4VContinuousProcess::AlongStepDoIt(track, step);
288 
289  G4MaterialPropertyVector* const rIndex = materialPropertiesTable->GetProperty("RINDEX");
290 
291  if (!rIndex)
292  return G4VContinuousProcess::AlongStepDoIt(track, step);
293 
294  G4double meanNumPhotons = GetAverageNumberOfPhotons(particle, material, rIndex);
295  if (meanNumPhotons <= 0) {
296  aParticleChange.SetNumberOfSecondaries(0);
297  return G4VContinuousProcess::AlongStepDoIt(track, step);
298  }
299 
300  meanNumPhotons *= step.GetStepLength();
301 
302  const G4int numPhotons = static_cast<G4int>(G4Poisson(meanNumPhotons));
303 
304  // we don't want to produce secondaries within Geant4, since they will
305  // be handled within this dedicated Cerenkov routine for Auger.
306  aParticleChange.SetNumberOfSecondaries(0);
307  if (numPhotons <= 0)
308  return G4VContinuousProcess::AlongStepDoIt(track, step);
309 
310  const G4double pMin = rIndex->GetMinLowEdgeEnergy();
311  const G4double pMax = rIndex->GetMaxLowEdgeEnergy();
312  const G4double nMax = rIndex->GetMaxValue();
313 
314  const G4double dp = pMax - pMin;
315 
316  const G4double betaInverse = particle->GetTotalEnergy() / particle->GetTotalMomentum();
317 
318  const G4double maxCos = betaInverse / nMax;
319  const G4double maxSin2 = 1 - maxCos * maxCos;
320 
321  double minQEMomentum = fQE.XFront();
322  double maxQEMomentum = fQE.XBack();
323  if (fHasSPMT) {
324  minQEMomentum = std::max(fQE.XFront(), fQE_SPMT.XFront());
325  maxQEMomentum = std::min(fQE.XBack(), fQE_SPMT.XBack());
326  }
327  // leave in stuff for dealing with change of refractive index with
328  // wavelength - even if the current setup ignores dispersion in water...
329 
330  for (G4int i = 0; i < numPhotons; ++i) {
331  G4double rand = 0;
332  G4double sin2Theta = 0;
333  G4double cosTheta = 0;
334  do {
335  rand = G4UniformRand();
336  fSampledMomentum = pMin + rand * dp;
337  fSampledRI = rIndex->Value(fSampledMomentum);
338  cosTheta = betaInverse / fSampledRI;
339  sin2Theta = 1 - cosTheta * cosTheta;
340  rand = G4UniformRand();
341  } while (rand * maxSin2 > sin2Theta);
342 
343  // Kill according to quantum efficiency of PMT and collection efficiency
344 
345  if (fSampledMomentum < minQEMomentum || fSampledMomentum > maxQEMomentum)
346  continue;
347 
349  if (fHasSPMT)
351 
352  if (G4UniformRand() > fPhotonMaxQE)
353  continue; // Photon is killed
354 
355  // calculate absorption and Rayleigh scattering constants for this photon
356  int iFirstValidBin_Wat = 0;
357  int iLastValidBin_Wat = fWaterAbsLength.GetNPoints() - 1;
358 
359  if (fSampledMomentum < fWaterAbsLength[iFirstValidBin_Wat].X() || fSampledMomentum > fWaterAbsLength[iLastValidBin_Wat].X())
360  continue;
361 
362  const double absorptionMFP = fWaterAbsLength.Y(fSampledMomentum); // CHECK
363 
364  const G4double sampledRI_Sq = fSampledRI * fSampledRI;
365  const G4double rIConst = (sampledRI_Sq + 2) * (sampledRI_Sq - 1);
366 
367  // does this need to be member function?
368  const double rayleighMFP = fRayleighConst / (pow(rIConst, 2) * pow(fSampledMomentum, 4));
369 
370  const G4double invSumMFP = 1 / (absorptionMFP + rayleighMFP);
371 
372  fInvMFP = 1 / absorptionMFP + 1 / rayleighMFP;
373  fRayleighFrac = absorptionMFP * invSumMFP;
374  fAbsorbFrac = rayleighMFP * invSumMFP;
375 
376  // calculate photon vectors (direction, polarisation)
377  const G4double sinTheta = sqrt(sin2Theta);
378  rand = G4UniformRand();
379 
380  const G4double phi = 2 * utl::kPi * rand;
381  const G4double sinThetaSinPhi = sin(phi) * sinTheta;
382  const G4double sinThetaCosPhi = cos(phi) * sinTheta;
383 
384  const G4double px = cosAlpha * cosBeta * sinThetaCosPhi - sinBeta * sinThetaSinPhi + cosBeta * sinAlpha * cosTheta;
385  const G4double py = cosAlpha * sinBeta * sinThetaCosPhi + cosBeta * sinThetaSinPhi + sinBeta * sinAlpha * cosTheta;
386  const G4double pz = cosAlpha * cosTheta - sinAlpha * sinThetaCosPhi;
387 
388  fPhotonMomentum.set(px, py, pz); // This is a momentum direction
389  fPhotonPolarization = primaryDirection - cosTheta * fPhotonMomentum;
391 
392  rand = G4UniformRand();
393  const G4double delta = rand * step.GetStepLength();
394  const G4double deltaTime = delta / (0.5 * (preStepPoint->GetVelocity() + postStepPoint->GetVelocity()));
395  fPhotonTime = t0 + deltaTime;
396  fPhotonPosition = x0 + rand * step.GetDeltaPosition();
397 
398  const G4double radiusTest = fPhotonPosition.x() * fPhotonPosition.x() + fPhotonPosition.y() * fPhotonPosition.y() - fTankRadiusSq;
399 
400  // if Cerenkov photon is inside tank, then track it
401  if (radiusTest < 0 &&
405  }
406 
407  if (verboseLevel > 0) {
408  G4cerr << "\n Exiting from G4Cerenkov::DoIt -- NumberOfSecondaries = "
409  << aParticleChange.GetNumberOfSecondaries() << G4endl;
410  }
411 
412  return G4VContinuousProcess::AlongStepDoIt(track, step);
413  }
414 
415 
416  void
418  {
419  if (fPhysicsTable)
420  return;
421 
422  const G4MaterialTable* const materialTable = G4Material::GetMaterialTable();
423  const G4int numOfMaterials = G4Material::GetNumberOfMaterials();
424  fPhysicsTable = new G4PhysicsTable(numOfMaterials);
425 
426  for (G4int i = 0; i < numOfMaterials; ++i) {
427 
428  G4PhysicsOrderedFreeVector* const physicsOrderedFreeVector = new G4PhysicsOrderedFreeVector();
429  G4Material* const material = (*materialTable)[i];
430  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
431 
432  if (materialPropertiesTable) {
433 
434  G4MaterialPropertyVector* const rIndexVector = materialPropertiesTable->GetProperty("RINDEX");
435 
436  if (rIndexVector) {
437 
438  G4double currentRI = (*rIndexVector)[0];
439 
440  if (currentRI > 1) {
441 
442  G4double currentPM = rIndexVector->Energy(0);
443 
444  G4double currentCAI = 0;
445  physicsOrderedFreeVector->InsertValues(currentPM , currentCAI);
446  G4double prevRI = currentRI;
447  G4double prevPM = currentPM;
448  G4double prevCAI = currentCAI;
449 
450  for (size_t ii = 1; ii < rIndexVector->GetVectorLength(); ++ii) {
451 
452  currentRI = (*rIndexVector)[ii];
453  currentPM = rIndexVector->Energy(ii);
454  currentCAI = 0.5 * (1 / (prevRI * prevRI) + 1 / (currentRI * currentRI));
455  currentCAI = prevCAI + (currentPM - prevPM) * currentCAI;
456  physicsOrderedFreeVector->InsertValues(currentPM, currentCAI);
457  prevPM = currentPM;
458  prevCAI = currentCAI;
459  prevRI = currentRI;
460 
461  }
462 
463  }
464 
465  }
466 
467  }
468 
469  fPhysicsTable->insertAt(i, physicsOrderedFreeVector);
470 
471  }
472  }
473 
474 
475  G4double
476  G4StationFastCerenkov::GetContinuousStepLimit(const G4Track& track, G4double, G4double, G4double&)
477  {
478  if (fMaxPhotons <= 0)
479  return DBL_MAX;
480 
481  const G4Material* const material = track.GetMaterial();
482  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
483 
484  if (!materialPropertiesTable)
485  return DBL_MAX;
486 
487  G4MaterialPropertyVector* const rIndex = materialPropertiesTable->GetProperty("RINDEX");
488 
489  if (!rIndex)
490  return DBL_MAX;
491 
492  const G4DynamicParticle* const particle = track.GetDynamicParticle();
493  const G4double meanNumPhotons = GetAverageNumberOfPhotons(particle, material, rIndex);
494  if (meanNumPhotons <= 0)
495  return DBL_MAX;
496 
497  const G4double stepLimit = fMaxPhotons / meanNumPhotons;
498  return stepLimit;
499  }
500 
501 
502  G4double
504  const G4Material* const material,
505  G4MaterialPropertyVector* const rIndex)
506  const
507  {
508  const G4double rFact = 369.81 / (eV * cm);
509 
510  if (particle->GetTotalMomentum() <= 0)
511  return 0;
512 
513  const G4double betaInverse = particle->GetTotalEnergy() / particle->GetTotalMomentum();
514 
515  const G4int materialIndex = material->GetIndex();
516 
517  G4PhysicsOrderedFreeVector* const cerenkovAngleIntegrals =
518  (G4PhysicsOrderedFreeVector*)((*fPhysicsTable)(materialIndex));
519 
520  if (!cerenkovAngleIntegrals->IsFilledVectorExist())
521  return 0;
522 
523  G4double pMin = rIndex->GetMinLowEdgeEnergy();
524  const G4double pMax = rIndex->GetMaxLowEdgeEnergy();
525  const G4double nMin = rIndex->GetMinValue();
526  const G4double nMax = rIndex->GetMaxValue();
527 
528  const G4double cAImax = cerenkovAngleIntegrals->GetMaxValue();
529  G4double dp = 0;
530  G4double ge = 0;
531 
532  if (nMax < betaInverse) {
533 
534  dp = 0;
535  ge = 0;
536 
537  } else if (nMin > betaInverse) {
538 
539  dp = pMax - pMin;
540  ge = cAImax;
541 
542  } else {
543 
544  pMin = rIndex->GetEnergy(betaInverse);
545  dp = pMax - pMin;
546  G4bool isOutRange = false;
547  const G4double cAImin = cerenkovAngleIntegrals->GetValue(pMin, isOutRange);
548  ge = cAImax - cAImin;
549 
550  if (verboseLevel > 0) {
551  G4cerr << "CAImin = " << cAImin << G4endl;
552  G4cerr << "ge = " << ge << G4endl;
553  }
554 
555  }
556 
557  const G4double charge = particle->GetDefinition()->GetPDGCharge();
558  const G4double numPhotons = rFact * pow(charge / eplus, 2) * (dp - ge * betaInverse*betaInverse);
559 
560  return numPhotons;
561  }
562 
563 
564  //#include "Propagate.icc" // Contains the code for propogating photons -
565  // part of the same class and namespace, but put in a different file
566  // for clarity
567 
568  void
570  {
571  const G4double rand = 2 * G4UniformRand() - 1;
572  const G4double cosTheta = (rand < 0) ? -pow(-rand, 1 / 3.) : pow(rand, 1 / 3.);
573  const G4double sinTheta = sqrt(1 - cosTheta * cosTheta);
574  const G4double phi = 2 * utl::kPi * G4UniformRand();
575  const G4double cosPhi = cos(phi);
576  const G4double sinPhi = sin(phi);
577  const G4double a = fPhotonPolarization.x();
578  const G4double b = fPhotonPolarization.y();
579  const G4double c = fPhotonPolarization.z();
580  const G4double k = 1 / sqrt(1 - c * c);
581  const G4double R11 = k * b;
582  const G4double R12 = k * a * c;
583  const G4double R21 = -k * a;
584  const G4double R22 = k * b * c;
585  const G4double R32 = -1 / k;
586 
587  G4double x = 0;
588  G4double y = 0;
589  G4double z = 0;
590 
591  if (G4UniformRand() < 0.5) {
592 
593  x = sinTheta * cosPhi;
594  y = sinTheta * sinPhi;
595  z = cosTheta;
596  fPhotonPolarization.set((R11 * x + R12 * y + a * z),
597  (R21 * x + R22 * y + b * z),
598  (R32 * y + c * z));
599  x = cosTheta * cosPhi;
600  y = cosTheta * sinPhi;
601  z = -sinTheta;
602  fPhotonMomentum.set((R11 * x + R12 * y + a * z),
603  (R21 * x + R22 * y + b * z),
604  (R32 * y + c * z));
605 
606  } else {
607 
608  x = sinTheta * cosPhi;
609  y = sinTheta * sinPhi;
610  z = cosTheta;
611  fPhotonPolarization.set((R11 * x + R12 * y + a * z),
612  (R21 * x + R22 * y + b * z),
613  (R32 * y + c * z));
614  x = -cosTheta * cosPhi;
615  y = -cosTheta * sinPhi;
616  z = sinTheta;
617 
618  fPhotonMomentum.set((R11 * x + R12 * y + a * z),
619  (R21 * x + R22 * y + b * z),
620  (R32 * y + c * z));
621 
622  }
623  }
624 
625 
626  // included into the G4StationFastCerenkov class for handling cerenkov photons
627  // in the Geant4 Auger tank simulation
628 
629  void
631  {
632  // This routine makes use of knowledge about the design of the Auger
633  // tanks that it shouldn't have (numbers not obtained through official
634  // routes, but hard-coded). That's life, if you want to make a program
635  // run faster.
636 
637  // The actual tracking is done in PropagateInTank and other routines -
638  // this one just determines which volume we start in, and gets us to
639  // the correct starting point in the nested methods. Once we return
640  // to this routine we fall straight back down to the level below,
641  // and move on to the next photon. Any detection of the photon has to
642  // done in the routines called from this one.
643 
644  if (fPhotonMomentum.z() < 0 &&
647  return;
648  }
649 
650  G4double closest = 0;
651  G4ThreeVector displacement;
652 
653  // loop over the PMTs and return whenever a PMT is hit
654  for (unsigned int i = 1; i <= 3; ++i) {
655  displacement = fPhotonPosition - fPMTPos[i];
656  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fDomeRadiusSq + Sqr(displacement.z()) / fDomeRadiuszSq;
657  if (closest < 1) {
658  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fInterfaceRadiusSq + Sqr(displacement.z()) / fInterfaceRadiuszSq;
659  if (closest < 1) {
660  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fPMTRadiusSq + Sqr(displacement.z()) / fPMTRadiuszSq;
661  if (closest < 1 && -displacement.z() > fHeightz) { // z < fHeightz : insensitive area
663  return;
664  }
666  return;
667  }
668  PropagateInTank(IN_DOME_1 + i - 1);
669  return;
670  }
671  }
672 
674  }
675 
676 
677  void
679  {
680  G4bool dead;
681  if (flag > DOWN_IN_TANK) {
682  if (flag == IN_DOME_1 || flag == IN_INTERFACE_1) {
683  dead = TransitionToDome<1>(flag);
684  if (dead)
685  return;
686  } else if (flag == IN_DOME_2 || flag == IN_INTERFACE_2) {
687  dead = TransitionToDome<2>(flag);
688  if (dead)
689  return;
690  } else if (flag == IN_DOME_3 || flag == IN_INTERFACE_3) {
691  dead = TransitionToDome<3>(flag);
692  if (dead)
693  return;
694  } else { // IN_DOME_4 or IN_INTERFACE_4
695  dead = TransitionToDome<4>(flag);
696  if (dead)
697  return;
698  }
699  if (fPhotonMomentum.z() < -0.0463)
700  flag = DOWN_IN_TANK;
701  // A photon emerging anywhere from a dome with z component of
702  // direction below this cannot possibly hit another dome
703  }
704 
705  G4double distanceToFloor, distanceToWall, distanceTravelled, distance;
706  G4double distanceToRoof, distanceToPMT1, distanceToPMT2, distanceToPMT3, distanceToSPMT;
707  G4double alpha, beta, gamma;
708  G4double testVal;
709  G4bool repeat;
710  G4double closest1, closest2, closest3, closest4;
711  G4ThreeVector closest, displacement1, displacement2, displacement3, displacement4;
712  G4double dotProduct, rand, distanceToPMT;
713  G4int hitPMT, targetPMT = -1, target;
714 
715  do {
716  repeat = false;
717  if (flag == DOWN_IN_TANK) {
718  // photon cannot hit domes, so only check for base and wall
719 
720  if (!fPhotonMomentum.z()) {
721  WARNING("The z component of the momentul is 0, skipping photon.");
722  return;
723  }
724 
725  distanceToFloor = -(fPhotonPosition.z() - fTankThickness) / fPhotonMomentum.z();
726 
727  gamma = 1 / (Sqr(fPhotonMomentum.x()) + Sqr(fPhotonMomentum.y()));
728  alpha = (fPhotonMomentum.x()*fPhotonPosition.x() + fPhotonMomentum.y()*fPhotonPosition.y()) * gamma;
729  beta = (Sqr(fPhotonPosition.x()) + Sqr(fPhotonPosition.y()) - fTankRadiusSq) * gamma;
730 
731  const double distance = Sqr(alpha) - beta;
732  if (distance < 0) {
733  WARNING("Distance to wall is not finite, skipping photon");
734  return;
735  }
736 
737  distanceToWall = sqrt(distance) - alpha;
738 
739  // check for Rayleigh scattering or absorption en route
740  rand = G4UniformRand();
741  testVal = 1 - exp(-min(distanceToWall, distanceToFloor) * fInvMFP);
742 
743  if (rand < testVal) {
744  // some interaction has occurred before reaching destination
745  if (rand < testVal * fRayleighFrac) {
746  // we have a Rayleigh scatter
747  distanceTravelled = -log(1 - rand/fRayleighFrac) / fInvMFP;
748  fPhotonPosition += distanceTravelled * fPhotonMomentum;
749  fPhotonTime += distanceTravelled * fSampledRI / c_light;
750  DoRayleighScatter(); // to pick new polarisation and direction
751  if (fPhotonMomentum.z() < 0 &&
753  flag = DOWN_IN_TANK;
754  else
755  flag = IN_TANK;
756  repeat = true;
757  } else
758  return; // we have absorption
759  }
760  if (!repeat) {
761  // no absorption or scattering - we reach the destination surface
762  if (distanceToWall < distanceToFloor) {
763  // we hit the wall
764  fPhotonPosition += distanceToWall * fPhotonMomentum;
765  fPhotonTime += distanceToWall * fSampledRI / c_light;
766  dead = ScatterOffWall();
767  } else {
768  // we hit the floor
769  fPhotonPosition += distanceToFloor * fPhotonMomentum;
770  fPhotonTime += distanceToFloor * fSampledRI / c_light;
771  dead = ScatterOffFloor();
772  }
773  if (dead)
774  return;
775  repeat = true;
776  }
777  } else {
778 
779  hitPMT = 0;
780  distanceToRoof =
781  (fPhotonMomentum.z() > 0) ?
783  -(fPhotonPosition.z() - fTankThickness) / fPhotonMomentum.z(); // floor
784 
785  gamma = 1 / (Sqr(fPhotonMomentum.x()) + Sqr(fPhotonMomentum.y()));
786  alpha = (fPhotonMomentum.x()*fPhotonPosition.x() + fPhotonMomentum.y()*fPhotonPosition.y()) * gamma;
787  beta = (Sqr(fPhotonPosition.x()) + Sqr(fPhotonPosition.y()) - fTankRadiusSq) * gamma;
788  distanceToWall = sqrt(Sqr(alpha) - beta) - alpha;
789 
790  displacement1 = fPhotonPosition - fPMTPos[1];
791  dotProduct = displacement1.dot(fPhotonMomentum);
792  closest = displacement1 - dotProduct * fPhotonMomentum;
793  closest1 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
794  if (closest1 < 1)
795  hitPMT += 1;
796 
797  displacement2 = fPhotonPosition - fPMTPos[2];
798  dotProduct = displacement2.dot(fPhotonMomentum);
799  closest = displacement2 - dotProduct * fPhotonMomentum;
800  closest2 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
801  if (closest2 < 1)
802  hitPMT += 2;
803 
804  displacement3 = fPhotonPosition - fPMTPos[3];
805  dotProduct = displacement3.dot(fPhotonMomentum);
806  closest = displacement3 - dotProduct * fPhotonMomentum;
807  closest3 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
808  if (closest3 < 1)
809  hitPMT += 4;
810 
811  if (fHasSPMT && fPhotonMomentum.z() > 0) {
812  displacement4 = fPhotonPosition + distanceToRoof * fPhotonMomentum - fPMTPos[4];
813  closest4 = (Sqr(displacement4.x()) + Sqr(displacement4.y())) / fDomeRadius_SPMT_Sq;
814  if (closest4 < 1)
815  hitPMT += 8;
816  }
817 
818  switch (hitPMT) {
819  case 0:
820  distanceToPMT = 1e99;
821  break;
822  case 1:
823  distanceToPMT = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
824  targetPMT = TARGET_PMT1;
825  break;
826  case 2:
827  distanceToPMT = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
828  targetPMT = TARGET_PMT2;
829  break;
830  case 3:
831  distanceToPMT1 = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
832  distanceToPMT2 = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
833  if (distanceToPMT1 < distanceToPMT2) {
834  targetPMT = TARGET_PMT1;
835  distanceToPMT = distanceToPMT1;
836  } else {
837  targetPMT = TARGET_PMT2;
838  distanceToPMT = distanceToPMT2;
839  }
840  break;
841  case 4:
842  distanceToPMT = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
843  targetPMT = TARGET_PMT3;
844  break;
845  case 5:
846  distanceToPMT1 = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
847  distanceToPMT3 = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
848  if (distanceToPMT1 < distanceToPMT3) {
849  targetPMT = TARGET_PMT1;
850  distanceToPMT = distanceToPMT1;
851  } else {
852  targetPMT = TARGET_PMT3;
853  distanceToPMT = distanceToPMT3;
854  }
855  break;
856  case 6:
857  distanceToPMT2 = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
858  distanceToPMT3 = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
859  if (distanceToPMT2 < distanceToPMT3) {
860  targetPMT = TARGET_PMT2;
861  distanceToPMT = distanceToPMT2;
862  } else {
863  targetPMT = TARGET_PMT3;
864  distanceToPMT = distanceToPMT3;
865  }
866  break;
867  case 8:
868  distanceToPMT = distanceToRoof;
869  targetPMT = TARGET_SPMT;
870  break;
871  case 9:
872  distanceToPMT1 = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
873  distanceToSPMT = distanceToRoof;
874  if (distanceToPMT1 < distanceToSPMT) {
875  targetPMT = TARGET_PMT1;
876  distanceToPMT = distanceToPMT1;
877  } else {
878  targetPMT = TARGET_SPMT;
879  distanceToPMT = distanceToSPMT;
880  }
881  break;
882  case 10:
883  distanceToPMT2 = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
884  distanceToSPMT = distanceToRoof;
885  if (distanceToPMT2 < distanceToSPMT) {
886  targetPMT = TARGET_PMT2;
887  distanceToPMT = distanceToPMT2;
888  } else {
889  targetPMT = TARGET_SPMT;
890  distanceToPMT = distanceToSPMT;
891  }
892  break;
893  case 12:
894  distanceToPMT3 = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
895  distanceToSPMT = distanceToRoof;
896  if (distanceToPMT3 < distanceToSPMT) {
897  targetPMT = TARGET_PMT3;
898  distanceToPMT = distanceToPMT3;
899  } else {
900  targetPMT = TARGET_SPMT;
901  distanceToPMT = distanceToSPMT;
902  }
903  break;
904  default:
905  // hit 3 or more pmts - error
906  WARNING("Error determining next photon intersection in PropagateInTank()");
907  return;
908  }
909 
910  if (distanceToPMT < distanceToWall) {
911  if (distanceToPMT < distanceToRoof ||
912  targetPMT == TARGET_SPMT) {
913  target = targetPMT;
914  distance = distanceToPMT;
915  } else {
916  target = TARGET_FLOOR;
917  distance = distanceToRoof;
918  }
919  } else {
920  if (distanceToRoof < distanceToWall) {
921  target = TARGET_FLOOR;
922  distance = distanceToRoof;
923  } else {
924  target = TARGET_WALL;
925  distance = distanceToWall;
926  }
927  }
928  // check for Rayleigh scattering or absorption en route
929  rand = G4UniformRand();
930  testVal = 1 - exp(-distance * fInvMFP);
931  if (rand < testVal) {
932  // some interaction has occurred before reaching destination
933  if (rand >= testVal * fRayleighFrac)
934  return; // absorption
935  // if we get here we have a Rayleigh scatter
936  distanceTravelled = -log(1 - rand/fRayleighFrac) / fInvMFP;
937  fPhotonPosition += distanceTravelled * fPhotonMomentum;
938  fPhotonTime += distanceTravelled * fSampledRI / c_light;
939  DoRayleighScatter(); // to pick new polarisation and direction
940  if (fPhotonMomentum.z() < 0 &&
942  flag = DOWN_IN_TANK;
943  else
944  flag = IN_TANK;
945  repeat = true;
946  } else {
947  fPhotonPosition += distance * fPhotonMomentum;
948  fPhotonTime += distance * fSampledRI / c_light;
949  switch (target) {
950  case TARGET_WALL:
951  dead = ScatterOffWall();
952  if (dead)
953  return;
954  break;
955  case TARGET_FLOOR:
956  if (fPhotonMomentum.z() < 0)
957  dead = ScatterOffFloor();
958  else
959  dead = ScatterOffRoof();
960  if (dead)
961  return;
962  break;
963  case TARGET_PMT1:
964  dead = TransitionToDome<1>(INWARD);
965  if (dead)
966  return;
967  break;
968  case TARGET_PMT2:
969  dead = TransitionToDome<2>(INWARD);
970  if (dead)
971  return;
972  break;
973  case TARGET_PMT3:
974  dead = TransitionToDome<3>(INWARD);
975  if (dead)
976  return;
977  break;
978  case TARGET_SPMT:
979  dead = TransitionToDome<4>(INWARD);
980  if (dead)
981  return;
982  break;
983  default:
984  WARNING("Error finding target in PropagateInTank");
985  return;
986  }
987  repeat = true;
988  }
989  }
990 
991  // quick check if we can ignore PMTs on next track
993  fPhotonMomentum.z() < 0)
994  flag = DOWN_IN_TANK;
995  else
996  flag = IN_TANK;
997 
998  } while (repeat);
999 
1000  // never get here - we either get detected or absorbed inside the loop
1001  const std::string err = "Tank photon tracking ran into an impossible condition";
1002  ERROR(err);
1003  throw utl::OutOfBoundException(err);
1004  }
1005 
1006 
1007  // PMT ROUTINES
1008 
1009  template<int pmtId>
1010  G4bool
1012  {
1013  fPhotonPosition -= fPMTPos[pmtId];
1014  G4ThreeVector alpha, beta, delta, normal;
1015  G4bool reflected, dead;
1016  G4double n, n2, transmission, vDotN;
1017  G4double paraComp, perpComp, paraMag, perpMag, x;
1018  G4ThreeVector paraPolarization, perpPolarization;
1019 
1020  if (flag == IN_INTERFACE_1 ||
1021  flag == IN_DOME_1 ||
1022  flag == IN_INTERFACE_2 ||
1023  flag == IN_DOME_2 ||
1024  flag == IN_INTERFACE_3 ||
1025  flag == IN_DOME_3 ||
1026  flag == IN_INTERFACE_4 ||
1027  flag == IN_DOME_4 ) {
1028  dead = PropagateInDome<pmtId>(flag); // flag needs to be altered in PiD
1029  if (dead)
1030  return true;
1031  }
1032 
1033  do {
1034  if (flag == INWARD) {
1035  n = fSampledRI;
1037  } else {
1039  n2 = fSampledRI;
1040  }
1041  normal.set(
1042  2 * fPhotonPosition.x() / fDomeRadiusSq,
1043  2 * fPhotonPosition.y() / fDomeRadiusSq,
1045  );
1046  normal *= 1/normal.mag();
1047  if (pmtId == 4)
1048  normal.set(0, 0, -1);
1049  vDotN = -normal.dot(fPhotonMomentum); // is actually cos(theta)
1050  if (vDotN > 0.999999) {
1051  transmission = 4 * n * n2 / pow(n + n2, 2);
1052  reflected = (G4UniformRand() > transmission);
1053  if (reflected) {
1056  if (flag == INWARD) {
1057  fPhotonPosition += fPMTPos[pmtId];
1058  return false;
1059  }
1060  flag = INWARD;
1061  dead = PropagateInDome<pmtId>(flag);
1062  if (dead)
1063  return true;
1064  } else {
1065  if (flag == OUTWARD) {
1066  fPhotonPosition += fPMTPos[pmtId];
1067  return false;
1068  }
1069  dead = PropagateInDome<pmtId>(flag);
1070  if (dead)
1071  return true;
1072  }
1073  } else {
1074  alpha = normal.cross(fPhotonMomentum);
1075  alpha *= 1/alpha.mag();
1076  x = Sqr(n2) - Sqr(n) * (1 - Sqr(vDotN));
1077  if (x <= 0)
1078  x = 0; // total reflection
1079  else
1080  x = (vDotN > 0) ? -sqrt(x) : sqrt(x);
1081  paraMag = fPhotonPolarization.dot(alpha);
1082  beta = fPhotonMomentum.cross(alpha); // should be unit vector
1083  perpMag = fPhotonPolarization.dot(beta);
1084  perpComp = perpMag / (n * vDotN - x);
1085  paraComp = paraMag / (n2 * vDotN - (n * x / n2));
1086  transmission = -4 * n * vDotN * x * (Sqr(perpComp) + Sqr(paraComp));
1087  reflected = (G4UniformRand() >= transmission);
1088  if (x == 0)
1089  reflected = true;
1090  if (flag == INWARD) {
1091  if (reflected) {
1092  // reflected back into tank
1093  fPhotonMomentum += 2 * vDotN * normal;
1094  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1095  beta = fPhotonMomentum.cross(alpha);
1096  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1098  fPhotonPosition *= 1.00001; // Fudge to avoid rounding errors
1099  fPhotonPosition += fPMTPos[pmtId];
1100  return false; // so we continue tracking in Tank
1101  } else {
1102  // transmitted in to dome
1103  fPhotonMomentum *= n;
1104  fPhotonMomentum += (n * vDotN + x) * normal;
1105  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1106  beta = fPhotonMomentum.cross(alpha);
1107  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1109  dead = PropagateInDome<pmtId>(flag); // flag needs to be altered in PiD
1110  if (dead)
1111  return true;
1112  }
1113  } else {
1114  // reflected off inside of dome - remains in dome
1115  if (reflected) {
1116  fPhotonMomentum += 2 * vDotN * normal;
1117  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1118  beta = fPhotonMomentum.cross(alpha);
1119  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1121  flag = INWARD;
1122  dead = PropagateInDome<pmtId>(flag);
1123  if (dead)
1124  return true;
1125  } else {
1126  // transmitted from dome back into tank
1127  fPhotonMomentum *= n;
1128  fPhotonMomentum += (n * vDotN + x) * normal;
1129  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1130  beta = fPhotonMomentum.cross(alpha);
1131  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1133  fPhotonPosition *= 1.00001; // Fudge to avoid rounding errors
1134  fPhotonPosition += fPMTPos[pmtId];
1135  return false;
1136  }
1137  }
1138  }
1139  } while (true);
1140  }
1141 
1142 
1143  template<int pmtId>
1144  G4bool
1146  {
1147  G4bool dead;
1148  if (flag == IN_INTERFACE_1 ||
1149  flag == IN_INTERFACE_2 ||
1150  flag == IN_INTERFACE_3 ||
1151  flag == IN_INTERFACE_4) {
1152  dead = TransitionToInterface<pmtId>(flag);
1153  if (dead)
1154  return true;
1155  }
1156 
1157  while (pmtId == 4) {
1158  // We are inside the SPMT dome, that is above the water level
1159  // So the z-component of the photon position relative to the tank roof is > 0
1160  G4double distanceToWater = (fRoofPos[pmtId] - fPhotonPosition.z()) / fPhotonMomentum.z();
1161  G4double distanceToInterface = (fRoofPos[pmtId] + fDomeThick_SPMT - fPhotonPosition.z()) / fPhotonMomentum.z();
1162 
1163  G4ThreeVector displacement;
1164  G4double distance;
1165 
1166  if (flag == INWARD) {
1167 
1168  if (distanceToInterface < 0) {
1169  const std::string err = "Photon tracking inside SPMT dome run into an impossible condition : INWARD & distToInterface<0";
1170  ERROR(err);
1171  throw utl::OutOfBoundException(err);
1172  }
1173 
1174  displacement = fPhotonPosition + distanceToInterface * fPhotonMomentum;
1175  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fInterfaceRadius_SPMT_Sq;
1176  if (distance < 1) {
1177  dead = (G4UniformRand() > exp(-distanceToInterface / fDomeAbsLength.Y(fSampledMomentum)));
1178  if (dead)
1179  return true;
1180  fPhotonPosition += distanceToInterface * fPhotonMomentum;
1181  fPhotonTime += distanceToInterface * fDomeRIndex.Y(fSampledMomentum) / c_light;
1182  dead = TransitionToInterface<pmtId>(flag);
1183  if (dead)
1184  return true;
1185  } else
1186  return true; // photon killed
1187 
1188  } else {
1189 
1190  if (distanceToWater < 0) {
1191  const std::string err = "Photon tracking inside SPMT dome run into an impossible condition : OUTWARD & distToWater<0";
1192  ERROR(err);
1193  throw utl::OutOfBoundException(err);
1194  }
1195 
1196  displacement = fPhotonPosition + distanceToWater * fPhotonMomentum;
1197  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fDomeRadius_SPMT_Sq;
1198  if (distance < 1) {
1199  dead = (G4UniformRand() > exp(-distanceToWater / fDomeAbsLength.Y(fSampledMomentum)));
1200  if (dead)
1201  return true;
1202  fPhotonPosition += distanceToWater * fPhotonMomentum;
1203  fPhotonTime += distanceToWater * fDomeRIndex.Y(fSampledMomentum) / c_light;
1204  return false;
1205  } else
1206  return true; // photon killed
1207  }
1208 
1209  }
1210 
1211  G4double distanceThis, distanceOther, distanceToRoof;
1212  // we start at one dome surface, so only one non-zero solution exists
1213  do {
1214  distanceToRoof = (fRoofPos[pmtId] - fPhotonPosition.z()) / fPhotonMomentum.z();
1215  if (distanceToRoof < 0)
1216  distanceToRoof = 1e99;
1217  if (flag == INWARD) {
1220  if (distanceThis < distanceOther) {
1221  if (distanceToRoof < distanceThis)
1222  return true; // photon killed
1223  dead = (G4UniformRand() > exp(-distanceThis / fDomeAbsLength.Y(fSampledMomentum)));
1224  if (dead)
1225  return true;
1226  fPhotonPosition += distanceThis * fPhotonMomentum;
1227  fPhotonTime += distanceThis * fDomeRIndex.Y(fSampledMomentum) / c_light;
1228  flag = OUTWARD;
1229  return false;
1230  } else {
1231  if (distanceToRoof < distanceOther)
1232  return true; // photon killed
1233  dead = (G4UniformRand() > exp(-distanceOther / fDomeAbsLength.Y(fSampledMomentum)));
1234  if (dead)
1235  return true;
1236  fPhotonPosition += distanceOther * fPhotonMomentum;
1237  fPhotonTime += distanceOther * fDomeRIndex.Y(fSampledMomentum) / c_light;
1238  dead = TransitionToInterface<pmtId>(flag);
1239  if (dead)
1240  return true;
1241  }
1242  } else {
1244  if (distanceToRoof < distanceOther)
1245  return true; // photon killed
1246  dead = (G4UniformRand() > exp(-distanceOther / fDomeAbsLength.Y(fSampledMomentum)));
1247  if (dead)
1248  return true;
1249  fPhotonPosition += distanceOther * fPhotonMomentum;
1250  fPhotonTime += distanceOther * fDomeRIndex.Y(fSampledMomentum) / c_light;
1251  flag = OUTWARD;
1252  return false;
1253  }
1254  } while (true);
1255  }
1256 
1257 
1258  template<int pmtId>
1259  G4bool
1261  {
1262  G4ThreeVector alpha, beta, delta, normal;
1263  G4bool reflected, dead;
1264  G4double n, n2, transmission, vDotN;
1265  G4double paraComp, perpComp, paraMag, perpMag, x;
1266  G4ThreeVector paraPolarization, perpPolarization;
1267 
1268  if (flag == IN_INTERFACE_1 ||
1269  flag == IN_INTERFACE_2 ||
1270  flag == IN_INTERFACE_3 ||
1271  flag == IN_INTERFACE_4) {
1272  dead = PropagateInInterface<pmtId>(flag); // flag needs to be altered in PiD
1273  if (dead)
1274  return true;
1275  }
1276 
1277  do {
1278  if (flag == INWARD) {
1281  } else {
1284  }
1285  normal.set(
1289  );
1290  normal *= 1/normal.mag();
1291  if (pmtId == 4)
1292  normal.set(0, 0, -1);
1293  vDotN = -normal.dot(fPhotonMomentum); // is actually cos(theta)
1294  if (vDotN > 0.999999) {
1295  transmission = 4 * n * n2 / pow(n + n2, 2);
1296  reflected = (G4UniformRand() > transmission);
1297  if (reflected) {
1300  if (flag == INWARD) {
1301  flag = OUTWARD;
1302  return false;
1303  }
1304  flag = INWARD;
1305  dead = PropagateInInterface<pmtId>(flag);
1306  if (dead)
1307  return true;
1308  } else {
1309  if (flag == OUTWARD)
1310  return true;
1311  dead = PropagateInInterface<pmtId>(flag);
1312  if (dead)
1313  return true;
1314  }
1315  } else {
1316  alpha = normal.cross(fPhotonMomentum);
1317  alpha *= 1/alpha.mag();
1318  x = Sqr(n2) - Sqr(n) * (1 - Sqr(vDotN));
1319  if (x <= 0)
1320  x = 0; // total reflection
1321  else
1322  x = (vDotN > 0) ? -sqrt(x) : sqrt(x);
1323  paraMag = fPhotonPolarization.dot(alpha);
1324  beta = fPhotonMomentum.cross(alpha); // should be unit vector
1325  perpMag = fPhotonPolarization.dot(beta);
1326  perpComp = perpMag / (n * vDotN - x);
1327  paraComp = paraMag / (n2 * vDotN - n * x / n2);
1328  transmission = -4 * n * vDotN * x * (Sqr(perpComp) + Sqr(paraComp));
1329  reflected = (G4UniformRand() >= transmission);
1330  if (x == 0)
1331  reflected = true;
1332  if (flag == INWARD) {
1333  if (reflected) {
1334  // reflected back into dome
1335  fPhotonMomentum += 2 * vDotN * normal;
1336  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1337  beta = fPhotonMomentum.cross(alpha);
1338  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1340  flag = OUTWARD;
1341  return false;
1342  } else {
1343  // transmitted back into tank
1344  fPhotonMomentum *= n;
1345  fPhotonMomentum += (n * vDotN + x) * normal;
1346  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1347  beta = fPhotonMomentum.cross(alpha);
1348  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1350  dead = PropagateInInterface<pmtId>(flag);
1351  if (dead)
1352  return true;
1353  }
1354  } else {
1355  // reflected off inside of dome - remains in interface
1356  if (reflected) {
1357  fPhotonMomentum += 2 * vDotN * normal;
1358  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1359  beta = fPhotonMomentum.cross(alpha);
1360  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1362  flag = INWARD;
1363  dead = PropagateInInterface<pmtId>(flag);
1364  if (dead)
1365  return true;
1366  } else {
1367  // transmitted from interface back into dome
1368  fPhotonMomentum *= n;
1369  fPhotonMomentum += (n * vDotN + x) * normal;
1370  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1371  beta = fPhotonMomentum.cross(alpha);
1372  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1374  return false;
1375  }
1376  }
1377  }
1378  } while (true);
1379  }
1380 
1381 
1382  template<int pmtId>
1383  G4bool
1385  {
1386  G4bool dead;
1387 
1388  while (pmtId == 4) {
1389  // We are inside the SPMT interface, that is above the dome level
1390  // So the z-component of the photon position relative to
1391  // the upper part of the SPMT dome is > 0
1392  G4double distanceToDome = (fRoofPos[pmtId] + fDomeThick_SPMT - fPhotonPosition.z()) / fPhotonMomentum.z();
1393  G4double distanceToPMT = (fRoofPos[pmtId] + fDomeThick_SPMT + fInterfaceThick_SPMT - fPhotonPosition.z()) / fPhotonMomentum.z();
1394 
1395  G4ThreeVector displacement;
1396  G4double distance;
1397 
1398  if (flag == INWARD) {
1399 
1400  if (distanceToPMT < 0) {
1401  const std::string err = "Photon tracking inside SPMT interface run into an impossible condition : INWARD & distToPMT<0";
1402  ERROR(err);
1403  throw utl::OutOfBoundException(err);
1404  }
1405 
1406  displacement = fPhotonPosition + distanceToPMT * fPhotonMomentum;
1407  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fPMTRadius_SPMT_Sq;
1408  if (distance < 1) {
1409  dead = (G4UniformRand() > exp(-distanceToPMT / fInterfaceAbsLength.Y(fSampledMomentum)));
1410  if (dead)
1411  return true;
1412  fPhotonPosition += distanceToPMT * fPhotonMomentum;
1413  fPhotonTime += distanceToPMT * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1414 
1415  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fPMTActiveRadius_SPMT_Sq;
1416  if (distance < 1) {
1417  const G4double eff = fQE_SPMT.Y(fSampledMomentum);
1418  if (fPhotonMaxQE * G4UniformRand() <= eff)
1420  return true; // photon absorbed, so is now dead
1421  }
1422 
1423  G4double maxDistanceInsidePMT = fPMTThick_SPMT / fPhotonMomentum.z();
1424  displacement = fPhotonPosition + maxDistanceInsidePMT * fPhotonMomentum;
1425  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fPMTActiveRadius_SPMT_Sq;
1426  if (distance < 1) {
1427  const G4double eff = fQE_SPMT.Y(fSampledMomentum);
1428  if (fPhotonMaxQE * G4UniformRand() <= eff)
1430  }
1431  return true; // photon killed
1432 
1433  } else
1434  return true; // photon killed
1435 
1436  } else {
1437 
1438  if (distanceToDome < 0) {
1439  const std::string err = "Photon tracking inside SPMT interface run into an impossible condition : OUTWARD & distToDome<0";
1440  ERROR(err);
1441  throw utl::OutOfBoundException(err);
1442  }
1443 
1444  displacement = fPhotonPosition + distanceToDome * fPhotonMomentum;
1445  distance = (Sqr(displacement.x()) + Sqr(displacement.y())) / fInterfaceRadius_SPMT_Sq;
1446  if (distance < 1) {
1447  dead = (G4UniformRand() > exp(-distanceToDome / fInterfaceAbsLength.Y(fSampledMomentum)));
1448  if (dead)
1449  return true;
1450  fPhotonPosition += distanceToDome * fPhotonMomentum;
1451  fPhotonTime += distanceToDome * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1452  return false;
1453  } else
1454  return true; // photon killed
1455  }
1456  }
1457 
1458  G4double distanceToRoof = (fRoofPos[pmtId] - fPhotonPosition.z()) / fPhotonMomentum.z();
1459  if (distanceToRoof < 0)
1460  distanceToRoof = 1e99;
1461 
1462  // we start at interface surface, so only one non-zero solution exists
1463  //DistanceThis = -2.0 * fPhotonPosition.dot(fPhotonMomentum);
1465  const G4double distanceOther = GetEllipsoidIntersect(fPhotonPosition, fPMTRadius, fPMTRadiusz);
1466 
1467  if (distanceThis < distanceOther) {
1468  if (distanceToRoof < distanceThis)
1469  return true; // photon killed
1470  dead = (G4UniformRand() > exp(-distanceThis / fInterfaceAbsLength.Y(fSampledMomentum)));
1471  if (dead)
1472  return true;
1473  fPhotonPosition += distanceThis * fPhotonMomentum;
1474  fPhotonTime += distanceThis * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1475  flag = OUTWARD;
1476  return false;
1477  } else {
1478  if (distanceToRoof < distanceOther)
1479  return true; // photon killed
1480  dead = (G4UniformRand() > exp(-distanceOther / fInterfaceAbsLength.Y(fSampledMomentum)));
1481  if (dead)
1482  return true;
1483  fPhotonTime += distanceOther * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1484 
1485  if ((-fPhotonPosition.z()) > fHeightz) {
1486  if (fHasSPMT) {
1487  const G4double eff = fQE.Y(fSampledMomentum);
1488  if (fPhotonMaxQE * G4UniformRand() <= eff)
1490  } else
1492  }
1493  return true; // photon absorbed, so is now dead
1494  }
1495  }
1496 
1497 
1498  // Scattering routines
1499 
1500  G4bool
1502  {
1503  const G4ThreeVector normal(0, 0, -1);
1504  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1505  G4double r = G4UniformRand();
1506  if (r > reflectivity)
1507  return true; // absorbed
1508  r /= reflectivity; // range 0..1
1509  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1510  if (r <= lobe) {
1511  LobeScatterHorizontal(normal);
1512  return false;
1513  }
1514  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1515  if (r <= spike) {
1516  SpikeScatter(normal);
1517  return false;
1518  }
1519  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1520  if (r <= back) {
1521  BackScatter();
1522  return false;
1523  }
1524  DiffuseScatterHorizontal(normal);
1525  return false;
1526  }
1527 
1528 
1529  G4bool
1531  {
1532  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1533  G4double r = G4UniformRand();
1534  if (r > reflectivity)
1535  return true; // absorbed
1536  r /= reflectivity; // range 0..1
1537  const G4double x = fPhotonPosition.x();
1538  const G4double y = fPhotonPosition.y();
1539  const G4double w = 1 / sqrt(Sqr(x) + Sqr(y));
1540  const G4ThreeVector normal(-x * w, -y * w, 0);
1541  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1542  if (r <= lobe) {
1543  LobeScatterVertical(normal);
1544  return false;
1545  }
1546  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1547  if (r <= spike) {
1548  SpikeScatter(normal);
1549  return false;
1550  }
1551  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1552  if (r <= back) {
1553  BackScatter();
1554  return false;
1555  }
1556  DiffuseScatterVertical(normal);
1557  return false;
1558  }
1559 
1560 
1561  G4bool
1563  {
1564  const G4ThreeVector normal(0, 0, 1);
1565  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1566  G4double r = G4UniformRand();
1567  if (r > reflectivity)
1568  return true; // absorbed
1569  r /= reflectivity; // range 0..1
1570  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1571  if (r <= lobe) {
1572  LobeScatterHorizontal(normal);
1573  return false;
1574  }
1575  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1576  if (r <= spike) {
1577  SpikeScatter(normal);
1578  return false;
1579  }
1580  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1581  if (r <= back) {
1582  BackScatter();
1583  return false;
1584  }
1585  DiffuseScatterHorizontal(normal);
1586  return false;
1587  }
1588 
1589 
1590  void
1592  {
1593  //fPhotonMomentum.set(-fPhotonMomentum.x(), -fPhotonMomentum.y(), -fPhotonMomentum.z());
1594  fPhotonMomentum *= -1;
1595  // can leave polarisation as it is
1596  }
1597 
1598 
1599  void
1601  {
1602  // for scattering off the tank walls
1603  const G4double rand = G4UniformRand();
1604  const G4double cosTheta = sqrt(1 - rand);
1605  const G4double sinTheta = sqrt(rand);
1606  const G4double phi = 2 * utl::kPi * G4UniformRand();
1607  const G4double sinThetaCosPhi = sinTheta * cos(phi);
1608  const G4double sinThetaSinPhi = sinTheta * sin(phi);
1609  const G4double x = normal.x();
1610  const G4double y = normal.y();
1611  const G4double w = sqrt(Sqr(x) + Sqr(y));
1612  const G4double vx = x * cosTheta - y * sinThetaSinPhi / w;
1613  const G4double vy = y * cosTheta + x * sinThetaSinPhi / w;
1614  const G4double vz = -w * sinThetaCosPhi;
1615  fPhotonMomentum.set(vx, vy, vz);
1616  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1619  }
1620 
1621 
1622  void
1624  {
1625  // for scattering off tank floor or roof
1626  const G4double rand = G4UniformRand();
1627  const G4double cosTheta = sqrt(1 - rand);
1628  const G4double sinTheta = sqrt(rand);
1629  const G4double phi = 2 * utl::kPi * G4UniformRand();
1630  const G4double vx = sinTheta * cos(phi);
1631  const G4double vy = sinTheta * sin(phi);
1632  const G4double vz = normal.z() * cosTheta; // Normal.z() = +/- 1
1633  fPhotonMomentum.set(vx, vy, vz);
1634  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1637  }
1638 
1639 
1640  void
1641  G4StationFastCerenkov::SpikeScatter(const G4ThreeVector& normal)
1642  {
1643  G4double dotProduct = fPhotonMomentum.dot(normal); // less than zero;
1644  fPhotonMomentum = fPhotonMomentum - (2 * dotProduct) * normal;
1645  dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1647  fPhotonMomentum /= fPhotonMomentum.mag();
1649  }
1650 
1651 
1652  void
1653  G4StationFastCerenkov::LobeScatterVertical(const G4ThreeVector& normal)
1654  {
1655  const G4double fMax = (fSigmaAlpha < 0.25) ? 4 * fSigmaAlpha : 1;
1656  const G4double x = normal.x();
1657  const G4double y = normal.y();
1658  const G4double w = sqrt(Sqr(x) + Sqr(y));
1659  G4ThreeVector trialMomentum;
1660  do {
1661  G4ThreeVector facetNormal;
1662  G4double dotProduct;
1663  do {
1664  G4double alpha;
1665  G4double sinAlpha;
1666  do {
1667  alpha = G4RandGauss::shoot(0, fSigmaAlpha);
1668  sinAlpha = sin(alpha);
1669  } while (G4UniformRand()*fMax > sinAlpha || alpha >= 0.5*utl::kPi);
1670  const G4double phi = 2*utl::kPi * G4UniformRand();
1671  const G4double cosAlpha = cos(alpha);
1672  const G4double sinAlphaSinPhi = sinAlpha * sin(phi);
1673  const G4double sinAlphaCosPhi = sinAlpha * cos(phi);
1674  const G4double vx = x * cosAlpha - y * sinAlphaSinPhi / w;
1675  const G4double vy = y * cosAlpha + x * sinAlphaSinPhi / w;
1676  const G4double vz = -w * sinAlphaCosPhi;
1677  facetNormal.set(vx, vy, vz);
1678  dotProduct = fPhotonMomentum.dot(facetNormal);
1679  } while (dotProduct >= 0);
1680  trialMomentum = fPhotonMomentum - (2 * dotProduct) * facetNormal;
1681  } while (trialMomentum.dot(normal) <= 0);
1682 
1683  fPhotonMomentum = trialMomentum;
1684  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1686  fPhotonMomentum /= fPhotonMomentum.mag();
1688  }
1689 
1690 
1691  void
1693  {
1694  const G4double fMax = (fSigmaAlpha < 0.25) ? 4 * fSigmaAlpha : 1;
1695  G4ThreeVector trialMomentum;
1696  do {
1697  G4ThreeVector facetNormal;
1698  G4double dotProduct;
1699  do {
1700  G4double alpha;
1701  G4double sinAlpha;
1702  do {
1703  alpha = G4RandGauss::shoot(0, fSigmaAlpha);
1704  sinAlpha = sin(alpha);
1705  } while (G4UniformRand()*fMax > sinAlpha || alpha >= 0.5*utl::kPi);
1706  const G4double phi = 2*utl::kPi * G4UniformRand();
1707  const G4double cosAlpha = cos(alpha);
1708  const G4double vx = sinAlpha * cos(phi);
1709  const G4double vy = sinAlpha * sin(phi);
1710  const G4double vz = normal.z() * cosAlpha; // Normal.z() = +/- 1
1711  facetNormal.set(vx, vy, vz);
1712  dotProduct = fPhotonMomentum.dot(facetNormal);
1713  } while (dotProduct >= 0);
1714  trialMomentum = fPhotonMomentum - (2 * dotProduct) * facetNormal;
1715  } while (trialMomentum.dot(normal) <= 0);
1716 
1717  fPhotonMomentum = trialMomentum;
1718  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1720  fPhotonMomentum /= fPhotonMomentum.mag();
1722  }
1723 
1724 
1725  // Utility routines
1726 
1727  G4double
1729  const G4double r)
1730  const
1731  {
1732  const G4double b = -pos.dot(fPhotonMomentum);
1733  const G4double c = pos.dot(pos) - Sqr(r);
1734  G4double d = Sqr(b) - c;
1735  if (d < 0)
1736  return 1e99; // no intersection with sphere at all
1737 
1738  d = sqrt(d);
1739  const G4double distA = b + d;
1740  const G4double distB = b - d;
1741  if (distA > 0) {
1742  if (distB < distA) {
1743  if (distB > 0)
1744  return distB;
1745  return distA; // inside sphere - B is behind us, A ahead
1746  }
1747  return distA;
1748  } else {
1749  if (distB > 0)
1750  return distB; // inside sphere - A is behind us, B ahead
1751  }
1752  // if we get here, both are < 0 - intersections are both behind us already
1753  return 1e99;
1754  }
1755 
1756 
1757  G4double
1759  const G4double r1, const G4double r2)
1760  const
1761  {
1762  // The equation of the line with the photon direction is given by Pos+a*fPhotonMomentum being a the
1763  // distance from the photon position since fPhotonMomentum is a unitary vector in the direction of
1764  // the photon
1765 
1766  const double b[3] = { Sqr(fPhotonMomentum.x()), Sqr(fPhotonMomentum.y()), Sqr(fPhotonMomentum.z()) };
1767  const double c[3] = { Sqr(pos.x()), Sqr(pos.y()), Sqr(pos.z()) };
1768  const double d[3] = { fPhotonMomentum.x() * pos.x(), fPhotonMomentum.y() * pos.y(), fPhotonMomentum.z() * pos.z() };
1769 
1770  const double r12 = Sqr(r1);
1771  const double r22 = Sqr(r2);
1772  const double e = (b[0] + b[1]) / r12 + b[2] / r22;
1773  const double f = 2*((d[0] + d[1]) / r12 + d[2] / r22);
1774  const double g = (c[0] + c[1]) / r12 + c[2] / r22 - 1;
1775 
1776  const double dis = Sqr(f) - 4*e*g;
1777  // if no solution of the quadratic equation there is no intersection
1778  if (dis < 0)
1779  return 1e99;
1780 
1781  const double sq = sqrt(dis);
1782  // take smallest positive value of the solution
1783  const double i2e = 1 / (2*e);
1784  const double a1 = i2e * (-f + sq);
1785  const double a2 = i2e * (-f - sq);
1786  if (a2 > 1e-4) // to avoid almost 0 but positive a2 for current photon position
1787  return a2;
1788 
1789  if (a1 > 0)
1790  return a1;
1791 
1792  return 1e99;
1793  }
1794 
1795 }
void SpikeScatter(const G4ThreeVector &normal)
void AddPhoton(const int nPMT, const double peTime) const
peTime in Auger units!
const double eV
Definition: GalacticUnits.h:35
unsigned int GetNPoints() const
constexpr double eV
Definition: AugerUnits.h:185
constexpr T Sqr(const T &x)
Detector description interface for Station-related data.
ArrayConstReference XBack() const
read-only reference to back of array of X
G4VParticleChange * AlongStepDoIt(const G4Track &track, const G4Step &step) override
const double eplus
Definition: GalacticUnits.h:37
bool HasSmallPMT() const
void DiffuseScatterVertical(const G4ThreeVector &normal)
static utl::TabulatedFunction fInterfaceAbsLength
Class to hold collection (x,y) points and provide interpolation between them.
#define OUTWARD
const double meter
Definition: GalacticUnits.h:29
const PMT & GetPMT(const int id) const
Get specified PMT by id.
static utl::TabulatedFunction fLinerReflectivity
#define IN_INTERFACE_3
static utl::TabulatedFunction fgLinerSPECULARLOBECONSTANT
double pow(const double x, const unsigned int i)
void LobeScatterVertical(const G4ThreeVector &normal)
#define TARGET_PMT3
Exception for reporting variable out of valid range.
constexpr double MeV
Definition: AugerUnits.h:184
#define max(a, b)
ArrayConstReference XFront() const
read-only reference to front of array of X
constexpr double kPi
Definition: MathConstants.h:24
G4double GetAverageNumberOfPhotons(const G4DynamicParticle *, const G4Material *, G4MaterialPropertyVector *) const
G4double GetContinuousStepLimit(const G4Track &, G4double, G4double, G4double &) override
constexpr double m3
Definition: AugerUnits.h:123
const utl::Point & GetPosition() const
PMT position.
constexpr double meter
Definition: AugerUnits.h:81
void DiffuseScatterHorizontal(const G4ThreeVector &normal)
Auger Software Run Control.
Definition: RunController.h:26
utl::CoordinateSystemPtr GetLocalCoordinateSystem() const
Get the Auger reference system centered on the tank.
constexpr double g
Definition: AugerUnits.h:200
#define WARNING(message)
Macro for logging warning messages.
Definition: ErrorLogger.h:163
#define TARGET_PMT1
G4double GetSphereIntersect(const G4ThreeVector &, const G4double) const
G4double GetEllipsoidIntersect(const G4ThreeVector &, const G4double, const G4double) const
constexpr double kelvin
Definition: AugerUnits.h:259
#define TARGET_WALL
static utl::TabulatedFunction fgLinerBACKSCATTERCONSTANT
#define TARGET_SPMT
#define DOWN_IN_TANK
G4ThreeVector ToG4Vector(const V &v, const utl::CoordinateSystemPtr &cs, const double unitConversion)
Definition: G4Utils.h:15
#define IN_INTERFACE_2
static utl::TabulatedFunction fgLinerREFLECTIVITY
class that handles Geant4 SD Station simulation adopted from G4TankSimulator
constexpr double cm
Definition: AugerUnits.h:117
struct particle_info particle[80]
static utl::TabulatedFunction fLinerSpecularSpike
void LobeScatterHorizontal(const G4ThreeVector &normal)
#define IN_TANK
static utl::TabulatedFunction fgLinerSPECULARSPIKECONSTANT
#define IN_INTERFACE_4
#define INWARD
static utl::TabulatedFunction fLinerSpecularLobe
double Y(const double x) const
Get or interpolate the Y value that corresponds to parameter x.
#define IN_DOME_4
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
static utl::TabulatedFunction fgPmtdomeABSORPTION
#define IN_INTERFACE_1
#define IN_DOME_2
#define TARGET_PMT2
static utl::TabulatedFunction fgInterfaceABSORPTION
#define IN_DOME_3
#define IN_DOME_1
#define TARGET_FLOOR

, generated on Tue Sep 26 2023.