G4StationSimulator/G4TankFastCerenkov.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 "G4TankFastCerenkov.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_INTERFACE_1 21
58 #define IN_INTERFACE_2 22
59 #define IN_INTERFACE_3 23
60 #define INWARD 31
61 #define OUTWARD 32
62 #define TARGET_PMT1 0
63 #define TARGET_PMT2 1
64 #define TARGET_PMT3 2
65 #define TARGET_WALL 3
66 #define TARGET_FLOOR 4
67 #define BOUNCED 10
68 
69 
70 namespace G4StationSimulatorOG {
71 
72  double G4TankFastCerenkov::fTankHeight;
73  double G4TankFastCerenkov::fTankHalfHeight;
74  double G4TankFastCerenkov::fTankThickness;
75  double G4TankFastCerenkov::fTankRadius;
76  double G4TankFastCerenkov::fTankRadiusSq;
77  double G4TankFastCerenkov::fDomeRadius;
78  double G4TankFastCerenkov::fDomeRadiusz;
79  double G4TankFastCerenkov::fInterfaceRadius;
80  double G4TankFastCerenkov::fInterfaceRadiusz;
81  double G4TankFastCerenkov::fPMTRadius;
82  double G4TankFastCerenkov::fPMTRadiusz;
83  double G4TankFastCerenkov::fDomeRadiusSq;
84  double G4TankFastCerenkov::fDomeRadiuszSq;
85  double G4TankFastCerenkov::fInterfaceRadiusSq;
86  double G4TankFastCerenkov::fInterfaceRadiuszSq;
87  double G4TankFastCerenkov::fPMTRadiusSq;
88  double G4TankFastCerenkov::fPMTRadiuszSq;
89  double G4TankFastCerenkov::fHeightz;
90 
91  double G4TankFastCerenkov::fSigmaAlpha;
92 
93  utl::TabulatedFunction G4TankFastCerenkov::fQE;
94  utl::TabulatedFunction G4TankFastCerenkov::fWaterAbsLength;
95 
96  utl::TabulatedFunction G4TankFastCerenkov::fInterfaceRIndex;
97  utl::TabulatedFunction G4TankFastCerenkov::fInterfaceAbsLength;
98 
99  utl::TabulatedFunction G4TankFastCerenkov::fDomeRIndex;
100  utl::TabulatedFunction G4TankFastCerenkov::fDomeAbsLength;
101 
102  utl::TabulatedFunction G4TankFastCerenkov::fLinerReflectivity;
103  utl::TabulatedFunction G4TankFastCerenkov::fLinerSpecularLobe;
104  utl::TabulatedFunction G4TankFastCerenkov::fLinerSpecularSpike;
105  utl::TabulatedFunction G4TankFastCerenkov::fLinerBackscatter;
106  G4ThreeVector G4TankFastCerenkov::fPMTPos[4];
107 
108  double G4TankFastCerenkov::fRoofPos[4];
109 
110 
111  G4TankFastCerenkov::G4TankFastCerenkov(const G4String& processName) :
112  G4VContinuousProcess(processName),
113  fG4StationSimulator(
114  dynamic_cast<G4StationSimulator&>(RunController::GetInstance().GetModule("G4StationSimulatorOG"))
115  )
116  {
117  if (verboseLevel > 0)
118  G4cerr << GetProcessName() << " is created " << G4endl;
120 
121  // Rayleigh Scattering data taken from G4OpRayleigh.cc
122  // isothermal compressibility of water
123  const G4double betat = 7.658e-23 * m3 / MeV;
124  // K Boltzman
125  const G4double kboltz = 8.61739e-11 * MeV / kelvin;
126  // Temperature of water is 10 degrees celsius
127  const G4double temp = 283.15 * kelvin;
128  const G4double hc4 = pow(h_Planck * c_light, 4); // constants defined
129  // in G4PhysicalConstants
130  fRayleighConst = 27 * hc4 / (8 * pow(utl::kPi, 3) * betat * temp * kboltz);
131  }
132 
133 
135  {
136  if (fPhysicsTable) {
137  fPhysicsTable->clearAndDestroy();
138  delete fPhysicsTable;
139  fPhysicsTable = nullptr;
140  }
141  }
142 
143 
144  void
146  {
151 
154 
159 
164 
167 
173 
175 
176  const auto& pmt = dStation.GetPMT(1);
177  fQE = pmt.GetQuantumEfficiency();
178 
179  const double collectionEfficiency = pmt.GetCollectionEfficiency();
180 
181  for (auto& qe : fQE.YRange())
182  qe *= collectionEfficiency;
183 
184  for (auto& en : fQE.XRange())
185  en *= CLHEP::eV / utl::eV;
186 
187  const auto& cs = dStation.GetLocalCoordinateSystem();
188  for (int i = 1; i <= 3; ++i) {
189  fPMTPos[i] = ToG4Vector(dStation.GetPMT(i).GetPosition(), cs, CLHEP::meter/utl::meter);
190  fRoofPos[i] = fTankHeight + fTankThickness - fPMTPos[i].z();
191  }
192 
193  // checked above - PMTs are in expected positions
201  }
202 
203 
204  void
206  const
207  {
208  for (int i = 0, n = fPhysicsTable->entries(); i < n; ++i)
209  static_cast<G4PhysicsOrderedFreeVector*>((*fPhysicsTable)[i])->DumpValues();
210  }
211 
212 
213  G4VParticleChange*
214  G4TankFastCerenkov::AlongStepDoIt(const G4Track& track, const G4Step& step)
215  {
216  aParticleChange.Initialize(track);
217  const G4DynamicParticle* particle = track.GetDynamicParticle();
218  const G4Material* const material = track.GetMaterial();
219  G4StepPoint* const preStepPoint = step.GetPreStepPoint();
220  G4StepPoint* const postStepPoint = step.GetPostStepPoint();
221 
222  const G4ThreeVector x0 = preStepPoint->GetPosition();
223  const G4ThreeVector p0 = step.GetDeltaPosition().unit();
224  const G4double t0 = preStepPoint->GetGlobalTime();
225 
226  if (t0 > 1e6)
227  return G4VContinuousProcess::AlongStepDoIt(track, step);
228 
229  G4ThreeVector primaryDirection = preStepPoint->GetMomentumDirection();
230 
231  const G4double cosAlpha = primaryDirection.z();
232  const G4double sinAlpha = sqrt(1 - Sqr(cosAlpha));
233  const G4double cosBeta = primaryDirection.x() / sinAlpha;
234  const G4double sinBeta = primaryDirection.y() / sinAlpha;
235 
236  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
237 
238  if (!materialPropertiesTable)
239  return G4VContinuousProcess::AlongStepDoIt(track, step);
240 
241  G4MaterialPropertyVector* const rIndex = materialPropertiesTable->GetProperty("RINDEX");
242 
243  if (!rIndex)
244  return G4VContinuousProcess::AlongStepDoIt(track, step);
245 
246  G4double meanNumPhotons = GetAverageNumberOfPhotons(particle, material, rIndex);
247  if (meanNumPhotons <= 0) {
248  aParticleChange.SetNumberOfSecondaries(0);
249  return G4VContinuousProcess::AlongStepDoIt(track, step);
250  }
251 
252  meanNumPhotons *= step.GetStepLength();
253 
254  const G4int numPhotons = static_cast<G4int>(G4Poisson(meanNumPhotons));
255 
256  // we don't want to produce secondaries within Geant4, since they will
257  // be handled within this dedicated Cerenkov routine for Auger.
258  aParticleChange.SetNumberOfSecondaries(0);
259  if (numPhotons <= 0)
260  return G4VContinuousProcess::AlongStepDoIt(track, step);
261 
262  const G4double pMin = rIndex->GetMinLowEdgeEnergy();
263  const G4double pMax = rIndex->GetMaxLowEdgeEnergy();
264  const G4double nMax = rIndex->GetMaxValue();
265 
266  const G4double dp = pMax - pMin;
267 
268  const G4double betaInverse = particle->GetTotalEnergy() / particle->GetTotalMomentum();
269 
270  const G4double maxCos = betaInverse / nMax;
271  const G4double maxSin2 = 1 - maxCos * maxCos;
272 
273  const double minQEMomentum = fQE.XFront();
274  const double maxQEMomentum = fQE.XBack();
275 
276  // leave in stuff for dealing with change of refractive index with
277  // wavelength - even if the current setup ignores dispersion in water...
278 
279  for (G4int i = 0; i < numPhotons; ++i) {
280  G4double rand = 0;
281  G4double sin2Theta = 0;
282  G4double cosTheta = 0;
283  do {
284  rand = G4UniformRand();
285  fSampledMomentum = pMin + rand * dp;
286  fSampledRI = rIndex->Value(fSampledMomentum);
287  cosTheta = betaInverse / fSampledRI;
288  sin2Theta = 1 - cosTheta * cosTheta;
289  rand = G4UniformRand();
290  } while (rand * maxSin2 > sin2Theta);
291 
292  // Kill according to quantum efficiency of PMT and collection efficiency
293 
294  if (fSampledMomentum < minQEMomentum || fSampledMomentum > maxQEMomentum)
295  continue;
296 
297  if (G4UniformRand() > fQE.Y(fSampledMomentum))
298  continue; // Photon is ignored
299 
300  // calculate absorption and Rayleigh scattering constants for this photon
301  int iFirstValidBin_Wat = 0;
302  int iLastValidBin_Wat = fWaterAbsLength.GetNPoints() - 1;
303 
304  if (fSampledMomentum < fWaterAbsLength[iFirstValidBin_Wat].X() || fSampledMomentum > fWaterAbsLength[iLastValidBin_Wat].X())
305  continue;
306 
307  const double absorptionMFP = fWaterAbsLength.Y(fSampledMomentum); // CHECK
308 
309  const G4double sampledRI_Sq = fSampledRI * fSampledRI;
310  const G4double rIConst = (sampledRI_Sq + 2) * (sampledRI_Sq - 1);
311 
312  // does this need to be member function?
313  const double rayleighMFP = fRayleighConst / (pow(rIConst, 2) * pow(fSampledMomentum, 4));
314 
315  const G4double invSumMFP = 1 / (absorptionMFP + rayleighMFP);
316 
317  fInvMFP = 1 / absorptionMFP + 1 / rayleighMFP;
318  fRayleighFrac = absorptionMFP * invSumMFP;
319  fAbsorbFrac = rayleighMFP * invSumMFP;
320 
321  // calculate photon vectors (direction, polarisation)
322  const G4double sinTheta = sqrt(sin2Theta);
323  rand = G4UniformRand();
324 
325  const G4double phi = 2 * utl::kPi * rand;
326  const G4double sinThetaSinPhi = sin(phi) * sinTheta;
327  const G4double sinThetaCosPhi = cos(phi) * sinTheta;
328 
329  const G4double px = cosAlpha * cosBeta * sinThetaCosPhi - sinBeta * sinThetaSinPhi + cosBeta * sinAlpha * cosTheta;
330  const G4double py = cosAlpha * sinBeta * sinThetaCosPhi + cosBeta * sinThetaSinPhi + sinBeta * sinAlpha * cosTheta;
331  const G4double pz = cosAlpha * cosTheta - sinAlpha * sinThetaCosPhi;
332 
333  fPhotonMomentum.set(px, py, pz); // This is a momentum direction
334  fPhotonPolarization = primaryDirection - cosTheta * fPhotonMomentum;
336 
337  rand = G4UniformRand();
338  const G4double delta = rand * step.GetStepLength();
339  const G4double deltaTime = delta / (0.5 * (preStepPoint->GetVelocity() + postStepPoint->GetVelocity()));
340  fPhotonTime = t0 + deltaTime;
341  fPhotonPosition = x0 + rand * step.GetDeltaPosition();
342 
343  const G4double radiusTest = fPhotonPosition.x() * fPhotonPosition.x() + fPhotonPosition.y() * fPhotonPosition.y() - fTankRadiusSq;
344 
345  // if Cerenkov photon is inside tank, then track it
346  if (radiusTest < 0 &&
350  }
351 
352  if (verboseLevel > 0) {
353  G4cerr << "\n Exiting from G4Cerenkov::DoIt -- NumberOfSecondaries = "
354  << aParticleChange.GetNumberOfSecondaries() << G4endl;
355  }
356 
357  return G4VContinuousProcess::AlongStepDoIt(track, step);
358  }
359 
360 
361  void
363  {
364  if (fPhysicsTable)
365  return;
366 
367  const G4MaterialTable* const materialTable = G4Material::GetMaterialTable();
368  const G4int numOfMaterials = G4Material::GetNumberOfMaterials();
369  fPhysicsTable = new G4PhysicsTable(numOfMaterials);
370 
371  for (G4int i = 0; i < numOfMaterials; ++i) {
372 
373  G4PhysicsOrderedFreeVector* const physicsOrderedFreeVector = new G4PhysicsOrderedFreeVector();
374  G4Material* const material = (*materialTable)[i];
375  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
376 
377  if (materialPropertiesTable) {
378 
379  G4MaterialPropertyVector* const rIndexVector = materialPropertiesTable->GetProperty("RINDEX");
380 
381  if (rIndexVector) {
382 
383  G4double currentRI = (*rIndexVector)[0];
384 
385  if (currentRI > 1) {
386 
387  G4double currentPM = rIndexVector->Energy(0);
388 
389  G4double currentCAI = 0;
390  physicsOrderedFreeVector->InsertValues(currentPM , currentCAI);
391  G4double prevRI = currentRI;
392  G4double prevPM = currentPM;
393  G4double prevCAI = currentCAI;
394 
395  for (size_t ii = 1; ii < rIndexVector->GetVectorLength(); ++ii) {
396 
397  currentRI = (*rIndexVector)[ii];
398  currentPM = rIndexVector->Energy(ii);
399  currentCAI = 0.5 * (1 / (prevRI * prevRI) + 1 / (currentRI * currentRI));
400  currentCAI = prevCAI + (currentPM - prevPM) * currentCAI;
401  physicsOrderedFreeVector->InsertValues(currentPM, currentCAI);
402  prevPM = currentPM;
403  prevCAI = currentCAI;
404  prevRI = currentRI;
405 
406  }
407 
408  }
409 
410  }
411 
412  }
413 
414  fPhysicsTable->insertAt(i, physicsOrderedFreeVector);
415 
416  }
417  }
418 
419 
420  G4double
421  G4TankFastCerenkov::GetContinuousStepLimit(const G4Track& track, G4double, G4double, G4double&)
422  {
423  if (fMaxPhotons <= 0)
424  return DBL_MAX;
425 
426  const G4Material* const material = track.GetMaterial();
427  G4MaterialPropertiesTable* const materialPropertiesTable = material->GetMaterialPropertiesTable();
428 
429  if (!materialPropertiesTable)
430  return DBL_MAX;
431 
432  G4MaterialPropertyVector* const rIndex = materialPropertiesTable->GetProperty("RINDEX");
433 
434  if (!rIndex)
435  return DBL_MAX;
436 
437  const G4DynamicParticle* const particle = track.GetDynamicParticle();
438  const G4double meanNumPhotons = GetAverageNumberOfPhotons(particle, material, rIndex);
439  if (meanNumPhotons <= 0)
440  return DBL_MAX;
441 
442  const G4double stepLimit = fMaxPhotons / meanNumPhotons;
443  return stepLimit;
444  }
445 
446 
447  G4double
449  const G4Material* const material,
450  G4MaterialPropertyVector* const rIndex)
451  const
452  {
453  const G4double rFact = 369.81 / (eV * cm);
454 
455  if (particle->GetTotalMomentum() <= 0)
456  return 0;
457 
458  const G4double betaInverse = particle->GetTotalEnergy() / particle->GetTotalMomentum();
459 
460  const G4int materialIndex = material->GetIndex();
461 
462  G4PhysicsOrderedFreeVector* const cerenkovAngleIntegrals =
463  (G4PhysicsOrderedFreeVector*)((*fPhysicsTable)(materialIndex));
464 
465  if (!cerenkovAngleIntegrals->IsFilledVectorExist())
466  return 0;
467 
468  G4double pMin = rIndex->GetMinLowEdgeEnergy();
469  const G4double pMax = rIndex->GetMaxLowEdgeEnergy();
470  const G4double nMin = rIndex->GetMinValue();
471  const G4double nMax = rIndex->GetMaxValue();
472 
473  const G4double cAImax = cerenkovAngleIntegrals->GetMaxValue();
474  G4double dp = 0;
475  G4double ge = 0;
476 
477  if (nMax < betaInverse) {
478 
479  dp = 0;
480  ge = 0;
481 
482  } else if (nMin > betaInverse) {
483 
484  dp = pMax - pMin;
485  ge = cAImax;
486 
487  } else {
488 
489  pMin = rIndex->GetEnergy(betaInverse);
490  dp = pMax - pMin;
491  G4bool isOutRange = false;
492  const G4double cAImin = cerenkovAngleIntegrals->GetValue(pMin, isOutRange);
493  ge = cAImax - cAImin;
494 
495  if (verboseLevel > 0) {
496  G4cerr << "CAImin = " << cAImin << G4endl;
497  G4cerr << "ge = " << ge << G4endl;
498  }
499 
500  }
501 
502  const G4double charge = particle->GetDefinition()->GetPDGCharge();
503  const G4double numPhotons = rFact * pow(charge / eplus, 2) * (dp - ge * betaInverse*betaInverse);
504 
505  return numPhotons;
506  }
507 
508 
509  //#include "Propagate.icc" // Contains the code for propogating photons -
510  // part of the same class and namespace, but put in a different file
511  // for clarity
512 
513  void
515  {
516  const G4double rand = 2 * G4UniformRand() - 1;
517  const G4double cosTheta = (rand < 0) ? -pow(-rand, 1 / 3.) : pow(rand, 1 / 3.);
518  const G4double sinTheta = sqrt(1 - cosTheta * cosTheta);
519  const G4double phi = 2 * utl::kPi * G4UniformRand();
520  const G4double cosPhi = cos(phi);
521  const G4double sinPhi = sin(phi);
522  const G4double a = fPhotonPolarization.x();
523  const G4double b = fPhotonPolarization.y();
524  const G4double c = fPhotonPolarization.z();
525  const G4double k = 1 / sqrt(1 - c * c);
526  const G4double R11 = k * b;
527  const G4double R12 = k * a * c;
528  const G4double R21 = -k * a;
529  const G4double R22 = k * b * c;
530  const G4double R32 = -1 / k;
531 
532  G4double x = 0;
533  G4double y = 0;
534  G4double z = 0;
535 
536  if (G4UniformRand() < 0.5) {
537 
538  x = sinTheta * cosPhi;
539  y = sinTheta * sinPhi;
540  z = cosTheta;
541  fPhotonPolarization.set((R11 * x + R12 * y + a * z),
542  (R21 * x + R22 * y + b * z),
543  (R32 * y + c * z));
544  x = cosTheta * cosPhi;
545  y = cosTheta * sinPhi;
546  z = -sinTheta;
547  fPhotonMomentum.set((R11 * x + R12 * y + a * z),
548  (R21 * x + R22 * y + b * z),
549  (R32 * y + c * z));
550 
551  } else {
552 
553  x = sinTheta * cosPhi;
554  y = sinTheta * sinPhi;
555  z = cosTheta;
556  fPhotonPolarization.set((R11 * x + R12 * y + a * z),
557  (R21 * x + R22 * y + b * z),
558  (R32 * y + c * z));
559  x = -cosTheta * cosPhi;
560  y = -cosTheta * sinPhi;
561  z = sinTheta;
562 
563  fPhotonMomentum.set((R11 * x + R12 * y + a * z),
564  (R21 * x + R22 * y + b * z),
565  (R32 * y + c * z));
566  }
567  }
568 
569 
570  // included into the G4TankFastCerenkov class for handling cerenkov photons
571  // in the Geant4 Auger tank simulation
572 
573  void
575  {
576  // This routine makes use of knowledge about the design of the Auger
577  // tanks that it shouldn't have (numbers not obtained through official
578  // routes, but hard-coded). That's life, if you want to make a program
579  // run faster.
580 
581  // The actual tracking is done in PropagateInTank and other routines -
582  // this one just determines which volume we start in, and gets us to
583  // the correct starting point in the nested methods. Once we return
584  // to this routine we fall straight back down to the level below,
585  // and move on to the next photon. Any detection of the photon has to
586  // done in the routines called from this one.
587 
588  if (fPhotonMomentum.z() < 0 &&
591  return;
592  }
593 
594  G4double closest = 0;
595  G4ThreeVector displacement;
596 
597  // loop over the PMTs and return whenever a PMT is hit
598  for (unsigned int i = 1; i <= 3; ++i) {
599  displacement = fPhotonPosition - fPMTPos[i];
600  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fDomeRadiusSq + Sqr(displacement.z()) / fDomeRadiuszSq;
601  if (closest < 1) {
602  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fInterfaceRadiusSq + Sqr(displacement.z()) / fInterfaceRadiuszSq;
603  if (closest < 1) {
604  closest = (Sqr(displacement.x()) + Sqr(displacement.y())) / fPMTRadiusSq + Sqr(displacement.z()) / fPMTRadiuszSq;
605  if (closest < 1 && -displacement.z() > fHeightz) { // z < fHeightz : insensitive area
607  return;
608  }
610  return;
611  }
612  PropagateInTank(IN_DOME_1 + i - 1);
613  return;
614  }
615  }
616 
618  }
619 
620 
621  void
623  {
624  G4bool dead;
625  if (flag > DOWN_IN_TANK) {
626  if (flag == IN_DOME_1 || flag == IN_INTERFACE_1) {
627  dead = TransitionToDome<1>(flag);
628  if (dead)
629  return;
630  } else if (flag == IN_DOME_2 || flag == IN_INTERFACE_2) {
631  dead = TransitionToDome<2>(flag);
632  if (dead)
633  return;
634  } else { // IN_DOME_3 or IN_INTERFACE_3
635  dead = TransitionToDome<3>(flag);
636  if (dead)
637  return;
638  }
639  if (fPhotonMomentum.z() < -0.0463)
640  flag = DOWN_IN_TANK;
641  // A photon emerging anywhere from a dome with z component of
642  // direction below this cannot possibly hit another dome
643  }
644 
645  G4double distanceToFloor, distanceToWall, distanceTravelled, distance;
646  G4double distanceToRoof, distanceToPMT1, distanceToPMT2, distanceToPMT3;
647  G4double alpha, beta, gamma;
648  G4double testVal;
649  G4bool repeat;
650  G4double closest1, closest2, closest3;
651  G4ThreeVector closest, displacement1, displacement2, displacement3;
652  G4double dotProduct, rand, distanceToPMT;
653  G4int hitPMT, targetPMT = -1, target;
654 
655  do {
656  repeat = false;
657  if (flag == DOWN_IN_TANK) {
658  // photon cannot hit domes, so only check for base and wall
659 
660  if (!fPhotonMomentum.z()) {
661  WARNING("The z component of the momentul is 0, skipping photon.");
662  return;
663  }
664 
665  distanceToFloor = -(fPhotonPosition.z() - fTankThickness) / fPhotonMomentum.z();
666 
667  gamma = 1 / (Sqr(fPhotonMomentum.x()) + Sqr(fPhotonMomentum.y()));
668  alpha = (fPhotonMomentum.x()*fPhotonPosition.x() + fPhotonMomentum.y()*fPhotonPosition.y()) * gamma;
669  beta = (Sqr(fPhotonPosition.x()) + Sqr(fPhotonPosition.y()) - fTankRadiusSq) * gamma;
670 
671  const double distance = Sqr(alpha) - beta;
672  if (distance < 0) {
673  WARNING("Distance to wall is not finite, skipping photon");
674  return;
675  }
676 
677  distanceToWall = sqrt(distance) - alpha;
678 
679  // check for Rayleigh scattering or absorption en route
680  rand = G4UniformRand();
681  testVal = 1 - exp(-min(distanceToWall, distanceToFloor) * fInvMFP);
682 
683  if (rand < testVal) {
684  // some interaction has occurred before reaching destination
685  if (rand < testVal * fRayleighFrac) {
686  // we have a Rayleigh scatter
687  distanceTravelled = -log(1 - rand/fRayleighFrac) / fInvMFP;
688  fPhotonPosition += distanceTravelled * fPhotonMomentum;
689  fPhotonTime += distanceTravelled * fSampledRI / c_light;
690  DoRayleighScatter(); // to pick new polarisation and direction
691  if (fPhotonMomentum.z() < 0 &&
693  flag = DOWN_IN_TANK;
694  else
695  flag = IN_TANK;
696  repeat = true;
697  } else
698  return; // we have absorption
699  }
700  if (!repeat) {
701  // no absorption or scattering - we reach the destination surface
702  if (distanceToWall < distanceToFloor) {
703  // we hit the wall
704  fPhotonPosition += distanceToWall * fPhotonMomentum;
705  fPhotonTime += distanceToWall * fSampledRI / c_light;
706  dead = ScatterOffWall();
707  } else {
708  // we hit the floor
709  fPhotonPosition += distanceToFloor * fPhotonMomentum;
710  fPhotonTime += distanceToFloor * fSampledRI / c_light;
711  dead = ScatterOffFloor();
712  }
713  if (dead)
714  return;
715  repeat = true;
716  }
717  } else {
718  hitPMT = 0;
719  distanceToRoof =
720  (fPhotonMomentum.z() > 0) ?
722  -(fPhotonPosition.z() - fTankThickness) / fPhotonMomentum.z(); // floor
723 
724  gamma = 1 / (Sqr(fPhotonMomentum.x()) + Sqr(fPhotonMomentum.y()));
725  alpha = (fPhotonMomentum.x()*fPhotonPosition.x() + fPhotonMomentum.y()*fPhotonPosition.y()) * gamma;
726  beta = (Sqr(fPhotonPosition.x()) + Sqr(fPhotonPosition.y()) - fTankRadiusSq) * gamma;
727  distanceToWall = sqrt(Sqr(alpha) - beta) - alpha;
728 
729  displacement1 = fPhotonPosition - fPMTPos[1];
730  dotProduct = displacement1.dot(fPhotonMomentum);
731  closest = displacement1 - dotProduct * fPhotonMomentum;
732  closest1 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
733  if (closest1 < 1)
734  hitPMT += 1;
735 
736  displacement2 = fPhotonPosition - fPMTPos[2];
737  dotProduct = displacement2.dot(fPhotonMomentum);
738  closest = displacement2 - dotProduct * fPhotonMomentum;
739  closest2 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
740  if (closest2 < 1)
741  hitPMT += 2;
742 
743  displacement3 = fPhotonPosition - fPMTPos[3];
744  dotProduct = displacement3.dot(fPhotonMomentum);
745  closest = displacement3 - dotProduct * fPhotonMomentum;
746  closest3 = (Sqr(closest.x()) + Sqr(closest.y())) / fDomeRadiusSq + Sqr(closest.z()) / fDomeRadiuszSq;
747  if (closest3 < 1)
748  hitPMT += 4;
749 
750  switch (hitPMT) {
751  case 0:
752  distanceToPMT = 1e99;
753  break;
754  case 1:
755  distanceToPMT = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
756  targetPMT = TARGET_PMT1;
757  break;
758  case 2:
759  distanceToPMT = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
760  targetPMT = TARGET_PMT2;
761  break;
762  case 3:
763  distanceToPMT1 = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
764  distanceToPMT2 = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
765  if (distanceToPMT1 < distanceToPMT2) {
766  targetPMT = TARGET_PMT1;
767  distanceToPMT = distanceToPMT1;
768  } else {
769  targetPMT = TARGET_PMT2;
770  distanceToPMT = distanceToPMT2;
771  }
772  break;
773  case 4:
774  distanceToPMT = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
775  targetPMT = TARGET_PMT3;
776  break;
777  case 5:
778  distanceToPMT1 = GetEllipsoidIntersect(displacement1, fDomeRadius, fDomeRadiusz);
779  distanceToPMT3 = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
780  if (distanceToPMT1 < distanceToPMT3) {
781  targetPMT = TARGET_PMT1;
782  distanceToPMT = distanceToPMT1;
783  } else {
784  targetPMT = TARGET_PMT3;
785  distanceToPMT = distanceToPMT3;
786  }
787  break;
788  case 6:
789  distanceToPMT2 = GetEllipsoidIntersect(displacement2, fDomeRadius, fDomeRadiusz);
790  distanceToPMT3 = GetEllipsoidIntersect(displacement3, fDomeRadius, fDomeRadiusz);
791  if (distanceToPMT2 < distanceToPMT3) {
792  targetPMT = TARGET_PMT2;
793  distanceToPMT = distanceToPMT2;
794  } else {
795  targetPMT = TARGET_PMT3;;
796  distanceToPMT = distanceToPMT3;
797  }
798  break;
799  default:
800  // hit all 3 pmts - error
801  WARNING("Error determining next photon intersection in PropagateInTank()");
802  return;
803  }
804 
805  if (distanceToPMT < distanceToWall) {
806  if (distanceToPMT < distanceToRoof) {
807  target = targetPMT;
808  distance = distanceToPMT;
809  } else {
810  target = TARGET_FLOOR;
811  distance = distanceToRoof;
812  }
813  } else {
814  if (distanceToRoof < distanceToWall) {
815  target = TARGET_FLOOR;
816  distance = distanceToRoof;
817  } else {
818  target = TARGET_WALL;
819  distance = distanceToWall;
820  }
821  }
822  // check for Rayleigh scattering or absorption en route
823  rand = G4UniformRand();
824  testVal = 1 - exp(-distance * fInvMFP);
825  if (rand < testVal) {
826  // some interaction has occurred before reaching destination
827  if (rand >= testVal * fRayleighFrac)
828  return; // absorption
829  // if we get here we have a Rayleigh scatter
830  distanceTravelled = -log(1 - rand/fRayleighFrac) / fInvMFP;
831  fPhotonPosition += distanceTravelled * fPhotonMomentum;
832  fPhotonTime += distanceTravelled * fSampledRI / c_light;
833  DoRayleighScatter(); // to pick new polarisation and direction
834  if (fPhotonMomentum.z() < 0 &&
836  flag = DOWN_IN_TANK;
837  else
838  flag = IN_TANK;
839  repeat = true;
840  } else {
841  fPhotonPosition += distance * fPhotonMomentum;
842  fPhotonTime += distance * fSampledRI / c_light;
843  switch (target) {
844  case TARGET_WALL:
845  dead = ScatterOffWall();
846  if (dead)
847  return;
848  break;
849  case TARGET_FLOOR:
850  if (fPhotonMomentum.z() < 0)
851  dead = ScatterOffFloor();
852  else
853  dead = ScatterOffRoof();
854  if (dead)
855  return;
856  break;
857  case TARGET_PMT1:
858  dead = TransitionToDome<1>(INWARD);
859  if (dead)
860  return;
861  break;
862  case TARGET_PMT2:
863  dead = TransitionToDome<2>(INWARD);
864  if (dead)
865  return;
866  break;
867  case TARGET_PMT3:
868  dead = TransitionToDome<3>(INWARD);
869  if (dead)
870  return;
871  break;
872  default:
873  WARNING("Error finding target in PropagateInTank");
874  return;
875  }
876  repeat = true;
877  }
878  }
879 
880  // quick check if we can ignore PMTs on next track
882  fPhotonMomentum.z() < 0)
883  flag = DOWN_IN_TANK;
884  else
885  flag = IN_TANK;
886 
887  } while (repeat);
888 
889  // never get here - we either get detected or absorbed inside the loop
890  const std::string err = "Tank photon tracking ran into an impossible condition";
891  ERROR(err);
892  throw utl::OutOfBoundException(err);
893  }
894 
895 
896  // PMT ROUTINES
897 
898  template<int pmtId>
899  G4bool
901  {
902  fPhotonPosition -= fPMTPos[pmtId];
903  G4ThreeVector alpha, beta, delta, normal;
904  G4bool reflected, dead;
905  G4double n, n2, transmission, vDotN;
906  G4double paraComp, perpComp, paraMag, perpMag, x;
907  G4ThreeVector paraPolarization, perpPolarization;
908 
909  if (flag == IN_INTERFACE_1 ||
910  flag == IN_DOME_1 ||
911  flag == IN_INTERFACE_2 ||
912  flag == IN_DOME_2 ||
913  flag == IN_INTERFACE_3 ||
914  flag == IN_DOME_3) {
915  dead = PropagateInDome<pmtId>(flag); // flag needs to be altered in PiD
916  if (dead)
917  return true;
918  }
919 
920  do {
921  if (flag == INWARD) {
922  n = fSampledRI;
924  } else {
926  n2 = fSampledRI;
927  }
928  normal.set(
929  2 * fPhotonPosition.x() / fDomeRadiusSq,
930  2 * fPhotonPosition.y() / fDomeRadiusSq,
932  );
933  normal *= 1/normal.mag();
934  vDotN = -normal.dot(fPhotonMomentum); // is actually cos(theta)
935  if (vDotN > 0.999999) {
936  transmission = 4 * n * n2 / pow(n + n2, 2);
937  reflected = (G4UniformRand() > transmission);
938  if (reflected) {
941  if (flag == INWARD) {
942  fPhotonPosition += fPMTPos[pmtId];
943  return false;
944  }
945  flag = INWARD;
946  dead = PropagateInDome<pmtId>(flag);
947  if (dead)
948  return true;
949  } else {
950  if (flag == OUTWARD) {
951  fPhotonPosition += fPMTPos[pmtId];
952  return false;
953  }
954  dead = PropagateInDome<pmtId>(flag);
955  if (dead)
956  return true;
957  }
958  } else {
959  alpha = normal.cross(fPhotonMomentum);
960  alpha *= 1/alpha.mag();
961  x = Sqr(n2) - Sqr(n) * (1 - Sqr(vDotN));
962  if (x <= 0)
963  x = 0; // total reflection
964  else
965  x = (vDotN > 0) ? -sqrt(x) : sqrt(x);
966  paraMag = fPhotonPolarization.dot(alpha);
967  beta = fPhotonMomentum.cross(alpha); // should be unit vector
968  perpMag = fPhotonPolarization.dot(beta);
969  perpComp = perpMag / (n * vDotN - x);
970  paraComp = paraMag / (n2 * vDotN - (n * x / n2));
971  transmission = -4 * n * vDotN * x * (Sqr(perpComp) + Sqr(paraComp));
972  reflected = (G4UniformRand() >= transmission);
973  if (x == 0)
974  reflected = true;
975  if (flag == INWARD) {
976  if (reflected) {
977  // reflected back into tank
978  fPhotonMomentum += 2 * vDotN * normal;
979  fPhotonMomentum *= 1/fPhotonMomentum.mag();
980  beta = fPhotonMomentum.cross(alpha);
981  fPhotonPolarization = paraComp * alpha + perpComp * beta;
983  fPhotonPosition *= 1.00001; // Fudge to avoid rounding errors
984  fPhotonPosition += fPMTPos[pmtId];
985  return false; // so we continue tracking in Tank
986  } else {
987  // transmitted in to dome
988  fPhotonMomentum *= n;
989  fPhotonMomentum += (n * vDotN + x) * normal;
990  fPhotonMomentum *= 1/fPhotonMomentum.mag();
991  beta = fPhotonMomentum.cross(alpha);
992  fPhotonPolarization = paraComp * alpha + perpComp * beta;
994  dead = PropagateInDome<pmtId>(flag); // flag needs to be altered in PiD
995  if (dead)
996  return true;
997  }
998  } else {
999  // reflected off inside of dome - remains in dome
1000  if (reflected) {
1001  fPhotonMomentum += 2 * vDotN * normal;
1002  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1003  beta = fPhotonMomentum.cross(alpha);
1004  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1006  flag = INWARD;
1007  dead = PropagateInDome<pmtId>(flag);
1008  if (dead)
1009  return true;
1010  } else {
1011  // transmitted from dome back into tank
1012  fPhotonMomentum *= n;
1013  fPhotonMomentum += (n * vDotN + x) * normal;
1014  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1015  beta = fPhotonMomentum.cross(alpha);
1016  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1018  fPhotonPosition *= 1.00001; // Fudge to avoid rounding errors
1019  fPhotonPosition += fPMTPos[pmtId];
1020  return false;
1021  }
1022  }
1023  }
1024  } while (true);
1025  }
1026 
1027 
1028  template<int pmtId>
1029  G4bool
1031  {
1032  G4bool dead;
1033  if (flag == IN_INTERFACE_1 ||
1034  flag == IN_INTERFACE_2 ||
1035  flag == IN_INTERFACE_3) {
1036  dead = TransitionToInterface<pmtId>(flag);
1037  if (dead)
1038  return true;
1039  }
1040 
1041  G4double distanceThis, distanceOther, distanceToRoof;
1042  // we start at one dome surface, so only one non-zero solution exists
1043  do {
1044  distanceToRoof = (fRoofPos[pmtId] - fPhotonPosition.z()) / fPhotonMomentum.z();
1045  if (distanceToRoof < 0)
1046  distanceToRoof = 1e99;
1047  if (flag == INWARD) {
1050  if (distanceThis < distanceOther) {
1051  if (distanceToRoof < distanceThis)
1052  return true; // photon killed
1053  dead = (G4UniformRand() > exp(-distanceThis / fDomeAbsLength.Y(fSampledMomentum)));
1054  if (dead)
1055  return true;
1056  fPhotonPosition += distanceThis * fPhotonMomentum;
1057  fPhotonTime += distanceThis * fDomeRIndex.Y(fSampledMomentum) / c_light;
1058  flag = OUTWARD;
1059  return false;
1060  } else {
1061  if (distanceToRoof < distanceOther)
1062  return true; // photon killed
1063  dead = (G4UniformRand() > exp(-distanceOther / fDomeAbsLength.Y(fSampledMomentum)));
1064  if (dead)
1065  return true;
1066  fPhotonPosition += distanceOther * fPhotonMomentum;
1067  fPhotonTime += distanceOther * fDomeRIndex.Y(fSampledMomentum) / c_light;
1068  dead = TransitionToInterface<pmtId>(flag);
1069  if (dead)
1070  return true;
1071  }
1072  } else {
1074  if (distanceToRoof < distanceOther)
1075  return true; // photon killed
1076  dead = (G4UniformRand() > exp(-distanceOther / fDomeAbsLength.Y(fSampledMomentum)));
1077  if (dead)
1078  return true;
1079  fPhotonPosition += distanceOther * fPhotonMomentum;
1080  fPhotonTime += distanceOther * fDomeRIndex.Y(fSampledMomentum) / c_light;
1081  flag = OUTWARD;
1082  return false;
1083  }
1084  } while (true);
1085  }
1086 
1087 
1088  template<int pmtId>
1089  G4bool
1091  {
1092  G4ThreeVector alpha, beta, delta, normal;
1093  G4bool reflected, dead;
1094  G4double n, n2, transmission, vDotN;
1095  G4double paraComp, perpComp, paraMag, perpMag, x;
1096  G4ThreeVector paraPolarization, perpPolarization;
1097 
1098  if (flag == IN_INTERFACE_1 ||
1099  flag == IN_INTERFACE_2 ||
1100  flag == IN_INTERFACE_3) {
1101  dead = PropagateInInterface<pmtId>(flag); // flag needs to be altered in PiD
1102  if (dead)
1103  return true;
1104  }
1105 
1106  do {
1107  if (flag == INWARD) {
1110  } else {
1113  }
1114  normal.set(
1118  );
1119  normal *= 1/normal.mag();
1120  vDotN = -normal.dot(fPhotonMomentum); // is actually cos(theta)
1121  if (vDotN > 0.999999) {
1122  transmission = 4 * n * n2 / pow(n + n2, 2);
1123  reflected = (G4UniformRand() > transmission);
1124  if (reflected) {
1127  if (flag == INWARD) {
1128  flag = OUTWARD;
1129  return false;
1130  }
1131  flag = INWARD;
1132  dead = PropagateInInterface<pmtId>(flag);
1133  if (dead)
1134  return true;
1135  } else {
1136  if (flag == OUTWARD)
1137  return true;
1138  dead = PropagateInInterface<pmtId>(flag);
1139  if (dead)
1140  return true;
1141  }
1142  } else {
1143  alpha = normal.cross(fPhotonMomentum);
1144  alpha *= 1/alpha.mag();
1145  x = Sqr(n2) - Sqr(n) * (1 - Sqr(vDotN));
1146  if (x <= 0)
1147  x = 0; // total reflection
1148  else
1149  x = (vDotN > 0) ? -sqrt(x) : sqrt(x);
1150  paraMag = fPhotonPolarization.dot(alpha);
1151  beta = fPhotonMomentum.cross(alpha); // should be unit vector
1152  perpMag = fPhotonPolarization.dot(beta);
1153  perpComp = perpMag / (n * vDotN - x);
1154  paraComp = paraMag / (n2 * vDotN - n * x / n2);
1155  transmission = -4 * n * vDotN * x * (Sqr(perpComp) + Sqr(paraComp));
1156  reflected = (G4UniformRand() >= transmission);
1157  if (x == 0)
1158  reflected = true;
1159  if (flag == INWARD) {
1160  if (reflected) {
1161  // reflected back into dome
1162  fPhotonMomentum += 2 * vDotN * normal;
1163  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1164  beta = fPhotonMomentum.cross(alpha);
1165  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1167  flag = OUTWARD;
1168  return false;
1169  } else {
1170  // transmitted back into tank
1171  fPhotonMomentum *= n;
1172  fPhotonMomentum += (n * vDotN + x) * normal;
1173  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1174  beta = fPhotonMomentum.cross(alpha);
1175  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1177  dead = PropagateInInterface<pmtId>(flag);
1178  if (dead)
1179  return true;
1180  }
1181  } else {
1182  // reflected off inside of dome - remains in interface
1183  if (reflected) {
1184  fPhotonMomentum += 2 * vDotN * normal;
1185  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1186  beta = fPhotonMomentum.cross(alpha);
1187  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1189  flag = INWARD;
1190  dead = PropagateInInterface<pmtId>(flag);
1191  if (dead)
1192  return true;
1193  } else {
1194  // transmitted from interface back into dome
1195  fPhotonMomentum *= n;
1196  fPhotonMomentum += (n * vDotN + x) * normal;
1197  fPhotonMomentum *= 1/fPhotonMomentum.mag();
1198  beta = fPhotonMomentum.cross(alpha);
1199  fPhotonPolarization = paraComp * alpha + perpComp * beta;
1201  return false;
1202  }
1203  }
1204  }
1205  } while (true);
1206  }
1207 
1208 
1209  template<int pmtId>
1210  G4bool
1212  {
1213  G4double distanceToRoof = (fRoofPos[pmtId] - fPhotonPosition.z()) / fPhotonMomentum.z();
1214  if (distanceToRoof < 0)
1215  distanceToRoof = 1e99;
1216 
1217  // we start at interface surface, so only one non-zero solution exists
1218  //DistanceThis = -2.0 * fPhotonPosition.dot(fPhotonMomentum);
1220  const G4double distanceOther = GetEllipsoidIntersect(fPhotonPosition, fPMTRadius, fPMTRadiusz);
1221 
1222  if (distanceThis < distanceOther) {
1223  if (distanceToRoof < distanceThis)
1224  return true; // photon killed
1225  const G4bool dead = (G4UniformRand() > exp(-distanceThis / fInterfaceAbsLength.Y(fSampledMomentum)));
1226  if (dead)
1227  return true;
1228  fPhotonPosition += distanceThis * fPhotonMomentum;
1229  fPhotonTime += distanceThis * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1230  flag = OUTWARD;
1231  return false;
1232  } else {
1233  if (distanceToRoof < distanceOther)
1234  return true; // photon killed
1235  const G4bool dead = (G4UniformRand() > exp(-distanceOther / fInterfaceAbsLength.Y(fSampledMomentum)));
1236  if (dead)
1237  return true;
1238  fPhotonTime += distanceOther * fInterfaceRIndex.Y(fSampledMomentum) / c_light;
1239 
1240  if ((-fPhotonPosition.z()) > fHeightz)
1242  return true; // photon absorbed, so is now dead
1243  }
1244  }
1245 
1246 
1247  // Scattering routines
1248 
1249  G4bool
1251  {
1252  const G4ThreeVector normal(0, 0, -1);
1253  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1254  G4double r = G4UniformRand();
1255  if (r > reflectivity)
1256  return true; // absorbed
1257  r /= reflectivity; // range 0..1
1258  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1259  if (r <= lobe) {
1260  LobeScatterHorizontal(normal);
1261  return false;
1262  }
1263  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1264  if (r <= spike) {
1265  SpikeScatter(normal);
1266  return false;
1267  }
1268  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1269  if (r <= back) {
1270  BackScatter();
1271  return false;
1272  }
1273  DiffuseScatterHorizontal(normal);
1274  return false;
1275  }
1276 
1277 
1278  G4bool
1280  {
1281  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1282  G4double r = G4UniformRand();
1283  if (r > reflectivity)
1284  return true; // absorbed
1285  r /= reflectivity; // range 0..1
1286  const G4double x = fPhotonPosition.x();
1287  const G4double y = fPhotonPosition.y();
1288  const G4double w = 1 / sqrt(Sqr(x) + Sqr(y));
1289  const G4ThreeVector normal(-x * w, -y * w, 0);
1290  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1291  if (r <= lobe) {
1292  LobeScatterVertical(normal);
1293  return false;
1294  }
1295  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1296  if (r <= spike) {
1297  SpikeScatter(normal);
1298  return false;
1299  }
1300  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1301  if (r <= back) {
1302  BackScatter();
1303  return false;
1304  }
1305  DiffuseScatterVertical(normal);
1306  return false;
1307  }
1308 
1309 
1310  G4bool
1312  {
1313  const G4ThreeVector normal(0, 0, 1);
1314  const G4double reflectivity = fLinerReflectivity.Y(fSampledMomentum);
1315  G4double r = G4UniformRand();
1316  if (r > reflectivity)
1317  return true; // absorbed
1318  r /= reflectivity; // range 0..1
1319  const G4double lobe = fLinerSpecularLobe.Y(fSampledMomentum);
1320  if (r <= lobe) {
1321  LobeScatterHorizontal(normal);
1322  return false;
1323  }
1324  const G4double spike = lobe + fLinerSpecularSpike.Y(fSampledMomentum);
1325  if (r <= spike) {
1326  SpikeScatter(normal);
1327  return false;
1328  }
1329  const G4double back = spike + fLinerBackscatter.Y(fSampledMomentum);
1330  if (r <= back) {
1331  BackScatter();
1332  return false;
1333  }
1334  DiffuseScatterHorizontal(normal);
1335  return false;
1336  }
1337 
1338 
1339  void
1341  {
1342  //fPhotonMomentum.set(-fPhotonMomentum.x(), -fPhotonMomentum.y(), -fPhotonMomentum.z());
1343  fPhotonMomentum *= -1;
1344  // can leave polarisation as it is
1345  }
1346 
1347 
1348  void
1349  G4TankFastCerenkov::DiffuseScatterVertical(const G4ThreeVector& normal)
1350  {
1351  // for scattering off the tank walls
1352  const G4double rand = G4UniformRand();
1353  const G4double cosTheta = sqrt(1 - rand);
1354  const G4double sinTheta = sqrt(rand);
1355  const G4double phi = 2 * utl::kPi * G4UniformRand();
1356  const G4double sinThetaCosPhi = sinTheta * cos(phi);
1357  const G4double sinThetaSinPhi = sinTheta * sin(phi);
1358  const G4double x = normal.x();
1359  const G4double y = normal.y();
1360  const G4double w = sqrt(Sqr(x) + Sqr(y));
1361  const G4double vx = x * cosTheta - y * sinThetaSinPhi / w;
1362  const G4double vy = y * cosTheta + x * sinThetaSinPhi / w;
1363  const G4double vz = -w * sinThetaCosPhi;
1364  fPhotonMomentum.set(vx, vy, vz);
1365  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1368  }
1369 
1370 
1371  void
1373  {
1374  // for scattering off tank floor or roof
1375  const G4double rand = G4UniformRand();
1376  const G4double cosTheta = sqrt(1 - rand);
1377  const G4double sinTheta = sqrt(rand);
1378  const G4double phi = 2 * utl::kPi * G4UniformRand();
1379  const G4double vx = sinTheta * cos(phi);
1380  const G4double vy = sinTheta * sin(phi);
1381  const G4double vz = normal.z() * cosTheta; // Normal.z() = +/- 1
1382  fPhotonMomentum.set(vx, vy, vz);
1383  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1386  }
1387 
1388 
1389  void
1390  G4TankFastCerenkov::SpikeScatter(const G4ThreeVector& normal)
1391  {
1392  G4double dotProduct = fPhotonMomentum.dot(normal); // less than zero;
1393  fPhotonMomentum = fPhotonMomentum - (2 * dotProduct) * normal;
1394  dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1396  fPhotonMomentum /= fPhotonMomentum.mag();
1398  }
1399 
1400 
1401  void
1402  G4TankFastCerenkov::LobeScatterVertical(const G4ThreeVector& normal)
1403  {
1404  const G4double fMax = (fSigmaAlpha < 0.25) ? 4 * fSigmaAlpha : 1;
1405  const G4double x = normal.x();
1406  const G4double y = normal.y();
1407  const G4double w = sqrt(Sqr(x) + Sqr(y));
1408  G4ThreeVector trialMomentum;
1409  do {
1410  G4ThreeVector facetNormal;
1411  G4double dotProduct;
1412  do {
1413  G4double alpha;
1414  G4double sinAlpha;
1415  do {
1416  alpha = G4RandGauss::shoot(0, fSigmaAlpha);
1417  sinAlpha = sin(alpha);
1418  } while (G4UniformRand()*fMax > sinAlpha || alpha >= 0.5*utl::kPi);
1419  const G4double phi = 2*utl::kPi * G4UniformRand();
1420  const G4double cosAlpha = cos(alpha);
1421  const G4double sinAlphaSinPhi = sinAlpha * sin(phi);
1422  const G4double sinAlphaCosPhi = sinAlpha * cos(phi);
1423  const G4double vx = x * cosAlpha - y * sinAlphaSinPhi / w;
1424  const G4double vy = y * cosAlpha + x * sinAlphaSinPhi / w;
1425  const G4double vz = -w * sinAlphaCosPhi;
1426  facetNormal.set(vx, vy, vz);
1427  dotProduct = fPhotonMomentum.dot(facetNormal);
1428  } while (dotProduct >= 0);
1429  trialMomentum = fPhotonMomentum - (2 * dotProduct) * facetNormal;
1430  } while (trialMomentum.dot(normal) <= 0);
1431 
1432  fPhotonMomentum = trialMomentum;
1433  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1435  fPhotonMomentum /= fPhotonMomentum.mag();
1437  }
1438 
1439 
1440  void
1441  G4TankFastCerenkov::LobeScatterHorizontal(const G4ThreeVector& normal)
1442  {
1443  const G4double fMax = (fSigmaAlpha < 0.25) ? 4 * fSigmaAlpha : 1;
1444  G4ThreeVector trialMomentum;
1445  do {
1446  G4ThreeVector facetNormal;
1447  G4double dotProduct;
1448  do {
1449  G4double alpha;
1450  G4double sinAlpha;
1451  do {
1452  alpha = G4RandGauss::shoot(0, fSigmaAlpha);
1453  sinAlpha = sin(alpha);
1454  } while (G4UniformRand()*fMax > sinAlpha || alpha >= 0.5*utl::kPi);
1455  const G4double phi = 2*utl::kPi * G4UniformRand();
1456  const G4double cosAlpha = cos(alpha);
1457  const G4double vx = sinAlpha * cos(phi);
1458  const G4double vy = sinAlpha * sin(phi);
1459  const G4double vz = normal.z() * cosAlpha; // Normal.z() = +/- 1
1460  facetNormal.set(vx, vy, vz);
1461  dotProduct = fPhotonMomentum.dot(facetNormal);
1462  } while (dotProduct >= 0);
1463  trialMomentum = fPhotonMomentum - (2 * dotProduct) * facetNormal;
1464  } while (trialMomentum.dot(normal) <= 0);
1465 
1466  fPhotonMomentum = trialMomentum;
1467  const G4double dotProduct = fPhotonMomentum.dot(fPhotonPolarization);
1469  fPhotonMomentum /= fPhotonMomentum.mag();
1471  }
1472 
1473 
1474  // Utility routines
1475 
1476  G4double
1477  G4TankFastCerenkov::GetSphereIntersect(const G4ThreeVector& pos,
1478  const G4double r)
1479  const
1480  {
1481  const G4double b = -pos.dot(fPhotonMomentum);
1482  const G4double c = pos.dot(pos) - Sqr(r);
1483  G4double d = Sqr(b) - c;
1484  if (d < 0)
1485  return 1e99; // no intersection with sphere at all
1486 
1487  d = sqrt(d);
1488  const G4double distA = b + d;
1489  const G4double distB = b - d;
1490  if (distA > 0) {
1491  if (distB < distA) {
1492  if (distB > 0)
1493  return distB;
1494  return distA; // inside sphere - B is behind us, A ahead
1495  }
1496  return distA;
1497  } else {
1498  if (distB > 0)
1499  return distB; // inside sphere - A is behind us, B ahead
1500  }
1501  // if we get here, both are < 0 - intersections are both behind us already
1502  return 1e99;
1503  }
1504 
1505 
1506  G4double
1508  const G4double r1, const G4double r2)
1509  const
1510  {
1511  // The equation of the line with the photon direction is given by Pos+a*fPhotonMomentum being a the
1512  // distance from the photon position since fPhotonMomentum is a unitary vector in the direction of
1513  // the photon
1514 
1515  const double b[3] = { Sqr(fPhotonMomentum.x()), Sqr(fPhotonMomentum.y()), Sqr(fPhotonMomentum.z()) };
1516  const double c[3] = { Sqr(pos.x()), Sqr(pos.y()), Sqr(pos.z()) };
1517  const double d[3] = { fPhotonMomentum.x() * pos.x(), fPhotonMomentum.y() * pos.y(), fPhotonMomentum.z() * pos.z() };
1518 
1519  const double r12 = Sqr(r1);
1520  const double r22 = Sqr(r2);
1521  const double e = (b[0] + b[1]) / r12 + b[2] / r22;
1522  const double f = 2*((d[0] + d[1]) / r12 + d[2] / r22);
1523  const double g = (c[0] + c[1]) / r12 + c[2] / r22 - 1;
1524 
1525  const double dis = Sqr(f) - 4*e*g;
1526  // if no solution of the quadratic equation there is no intersection
1527  if (dis < 0)
1528  return 1e99;
1529 
1530  const double sq = sqrt(dis);
1531  // take smallest positive value of the solution
1532  const double i2e = 1 / (2*e);
1533  const double a1 = i2e * (-f + sq);
1534  const double a2 = i2e * (-f - sq);
1535  if (a2 > 1e-4) // to avoid almost 0 but positive a2 for current photon position
1536  return a2;
1537 
1538  if (a1 > 0)
1539  return a1;
1540 
1541  return 1e99;
1542  }
1543 
1544 }
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
const double eplus
Definition: GalacticUnits.h:37
Class to hold collection (x,y) points and provide interpolation between them.
const double meter
Definition: GalacticUnits.h:29
G4double GetSphereIntersect(const G4ThreeVector &, const G4double) const
const PMT & GetPMT(const int id) const
Get specified PMT by id.
static utl::TabulatedFunction fgLinerSPECULARLOBECONSTANT
double pow(const double x, const unsigned int i)
Exception for reporting variable out of valid range.
constexpr double MeV
Definition: AugerUnits.h:184
ArrayConstReference XFront() const
read-only reference to front of array of X
constexpr double kPi
Definition: MathConstants.h:24
constexpr double m3
Definition: AugerUnits.h:123
const utl::Point & GetPosition() const
PMT position.
constexpr double meter
Definition: AugerUnits.h:81
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
constexpr double kelvin
Definition: AugerUnits.h:259
static utl::TabulatedFunction fgLinerBACKSCATTERCONSTANT
G4ThreeVector ToG4Vector(const V &v, const utl::CoordinateSystemPtr &cs, const double unitConversion)
Definition: G4Utils.h:15
static utl::TabulatedFunction fgLinerREFLECTIVITY
class that handles Geant4 SD Station simulation adopted from G4TankSimulator
G4double GetAverageNumberOfPhotons(const G4DynamicParticle *, const G4Material *, G4MaterialPropertyVector *) const
constexpr double cm
Definition: AugerUnits.h:117
struct particle_info particle[80]
static utl::TabulatedFunction fgLinerSPECULARSPIKECONSTANT
G4VParticleChange * AlongStepDoIt(const G4Track &track, const G4Step &step) override
double Y(const double x) const
Get or interpolate the Y value that corresponds to parameter x.
#define ERROR(message)
Macro for logging error messages.
Definition: ErrorLogger.h:165
static utl::TabulatedFunction fgPmtdomeABSORPTION
G4double GetContinuousStepLimit(const G4Track &, G4double, G4double, G4double &) override
static utl::TabulatedFunction fgInterfaceABSORPTION
G4double GetEllipsoidIntersect(const G4ThreeVector &, const G4double, const G4double) const

, generated on Tue Sep 26 2023.