LArSoft  v10_04_05
Liquid Argon Software toolkit - https://larsoft.org/
SemiAnalyticalModel.cxx
Go to the documentation of this file.
1 #include "SemiAnalyticalModel.h"
3 
4 // LArSoft Libraries
12 
14 
15 // support libraries
16 #include "cetlib_except/exception.h"
17 #include "fhiclcpp/ParameterSet.h"
19 
20 #include "TMath.h"
21 
22 #include <iostream>
23 #include <vector>
24 
25 #include "boost/math/special_functions/ellint_1.hpp"
26 #include "boost/math/special_functions/ellint_3.hpp"
27 
28 namespace phot {
29 
30  // constructor
32  const fhicl::ParameterSet& VISHitsParams,
33  const bool doReflectedLight,
34  const bool includeAnodeReflections,
35  const bool useXeAbsorption)
36  : fGeom{*(lar::providerFrom<geo::Geometry>())}
38  , fISTPC{fGeom}
39  , fNTPC(fGeom.NTPC())
42  fActiveVolumes[0].CenterY(),
43  fActiveVolumes[0].CenterZ()}
44  , fanode_centre{fChannelMap.Plane({0, 0, 0}).GetCenter().X(),
45  fActiveVolumes[0].CenterY(),
46  fActiveVolumes[0].CenterZ()}
49  , fDoReflectedLight(doReflectedLight)
50  , fIncludeAnodeReflections(includeAnodeReflections)
51  , fUseXeAbsorption(useXeAbsorption)
52  {
53  mf::LogInfo("SemiAnalyticalModel") << "Initializing Semi-analytical model." << std::endl;
54 
55  // Load Gaisser-Hillas corrections for VUV semi-analytic hits
56  mf::LogInfo("SemiAnalyticalModel") << "Using VUV visibility parameterization";
57 
58  fIsFlatPDCorr = VUVHitsParams.get<bool>("FlatPDCorr", false);
59  fIsFlatPDCorrLat = VUVHitsParams.get<bool>("FlatPDCorrLat", false);
60  fIsDomePDCorr = VUVHitsParams.get<bool>("DomePDCorr", false);
61  fdelta_angulo_vuv = VUVHitsParams.get<double>("delta_angulo_vuv", 10);
62  fradius = VUVHitsParams.get<double>("PMT_radius", 10.16);
63  fApplyFieldCageTransparency = VUVHitsParams.get<bool>("ApplyFieldCageTransparency", false);
64  fFieldCageTransparencyLateral = VUVHitsParams.get<double>("FieldCageTransparencyLateral", 1.0);
65  fFieldCageTransparencyCathode = VUVHitsParams.get<double>("FieldCageTransparencyCathode", 1.0);
66  fMaxPDDistance = VUVHitsParams.get<double>(
67  "MaxPDDistance",
68  1000); // max distance between scintillation and PD to evaluate light, default 10m
69 
70  if (!fIsFlatPDCorr && !fIsDomePDCorr && !fIsFlatPDCorrLat) {
71  throw cet::exception("SemiAnalyticalModel")
72  << "Both isFlatPDCorr/isFlatPDCorrLat and isDomePDCorr parameters are "
73  "false, at least one type of parameterisation is required for the "
74  "semi-analytic light simulation."
75  << "\n";
76  }
77  if (fIsFlatPDCorr) {
78  fGHvuvpars_flat = VUVHitsParams.get<std::vector<std::vector<double>>>("GH_PARS_flat");
79  fborder_corr_angulo_flat = VUVHitsParams.get<std::vector<double>>("GH_border_angulo_flat");
80  fborder_corr_flat = VUVHitsParams.get<std::vector<std::vector<double>>>("GH_border_flat");
81  }
82  if (fIsFlatPDCorrLat) {
84  VUVHitsParams.get<std::vector<std::vector<double>>>("GH_PARS_flat_lateral");
86  VUVHitsParams.get<std::vector<double>>("GH_border_angulo_flat_lateral");
88  VUVHitsParams.get<std::vector<std::vector<double>>>("GH_border_flat_lateral");
89  }
90  if (fIsDomePDCorr) {
91  fGHvuvpars_dome = VUVHitsParams.get<std::vector<std::vector<double>>>("GH_PARS_dome");
92  fborder_corr_angulo_dome = VUVHitsParams.get<std::vector<double>>("GH_border_angulo_dome");
93  fborder_corr_dome = VUVHitsParams.get<std::vector<std::vector<double>>>("GH_border_dome");
94  }
95 
96  // Load corrections for VIS semi-analytic hits
97  if (fDoReflectedLight) {
98  mf::LogInfo("SemiAnalyticalModel") << "Using VIS (reflected) visibility parameterization";
99  fdelta_angulo_vis = VISHitsParams.get<double>("delta_angulo_vis");
100 
101  if (fIsFlatPDCorr) {
102  fvis_distances_x_flat = VISHitsParams.get<std::vector<double>>("VIS_distances_x_flat");
103  fvis_distances_r_flat = VISHitsParams.get<std::vector<double>>("VIS_distances_r_flat");
104  fvispars_flat =
105  VISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_flat");
106  }
107  if (fIsDomePDCorr) {
108  fvis_distances_x_dome = VISHitsParams.get<std::vector<double>>("VIS_distances_x_dome");
109  fvis_distances_r_dome = VISHitsParams.get<std::vector<double>>("VIS_distances_r_dome");
110  fvispars_dome =
111  VISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_dome");
112  }
113 
114  // set cathode plane struct for solid angle function
115  fcathode_plane.h = fActiveVolumes[0].SizeY();
116  fcathode_plane.w = fActiveVolumes[0].SizeZ();
118  }
119 
120  // Load corrections for Anode reflections configuration
122  mf::LogInfo("SemiAnalyticalModel") << "Using anode reflections parameterization";
123  fdelta_angulo_vis = VISHitsParams.get<double>("delta_angulo_vis");
124  fAnodeReflectivity = VISHitsParams.get<double>("AnodeReflectivity");
125 
126  if (fIsFlatPDCorr) {
127  fvis_distances_x_flat = VISHitsParams.get<std::vector<double>>("VIS_distances_x_flat");
128  fvis_distances_r_flat = VISHitsParams.get<std::vector<double>>("VIS_distances_r_flat");
129  fvispars_flat =
130  VISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_flat");
131  }
132 
133  if (fIsFlatPDCorrLat) {
135  VISHitsParams.get<std::vector<double>>("VIS_distances_x_flat_lateral");
137  VISHitsParams.get<std::vector<double>>("VIS_distances_r_flat_lateral");
138  fvispars_flat_lateral = VISHitsParams.get<std::vector<std::vector<std::vector<double>>>>(
139  "VIS_correction_flat_lateral");
140  }
141 
142  // set anode plane struct for solid angle function
143  fanode_plane.h = fActiveVolumes[0].SizeY();
144  fanode_plane.w = fActiveVolumes[0].SizeZ();
146  }
147 
148  // determine drift distance
150  // for multiple TPCs, use second TPC to skip small volume at edges of detector (DUNE)
151  if (fNTPC > 1 && fDriftDistance < 50)
152  fDriftDistance = fGeom.TPC(geo::TPCID{0, 1}).DriftDistance();
153 
154  // set absorption length
156  mf::LogInfo("SemiAnalyticalModel")
157  << "Setting absorption length to: " << fvuv_absorption_length << std::endl;
158 
159  mf::LogInfo("SemiAnalyticalModel") << "Semi-analytical model initialized." << std::endl;
160  }
161 
163  {
164  // determine LAr absorption length in cm
165  std::map<double, double> abs_length_spectrum =
166  lar::providerFrom<detinfo::LArPropertiesService>()->AbsLengthSpectrum();
167  std::vector<double> x_v, y_v;
168  for (auto elem : abs_length_spectrum) {
169  x_v.push_back(elem.first);
170  y_v.push_back(elem.second);
171  }
172  double vuv_absorption_length;
173  if (fUseXeAbsorption)
174  vuv_absorption_length =
175  interpolate(x_v, y_v, 7.1, false); // 7.1 eV: peak of Xe VUV emission spectrum
176  else
177  vuv_absorption_length =
178  interpolate(x_v, y_v, 9.7, false); // 9.7 eV: peak of Ar VUV emission spectrum
179  if (vuv_absorption_length <= 0) {
180  throw cet::exception("SemiAnalyticalModel")
181  << "Error: VUV Absorption Length is 0 or negative.\n";
182  }
183  return vuv_absorption_length;
184  }
185 
186  //......................................................................
187  // VUV semi-analytical model visibility calculation
188  void SemiAnalyticalModel::detectedDirectVisibilities(std::vector<double>& DetectedVisibilities,
189  geo::Point_t const& ScintPoint) const
190  {
191  DetectedVisibilities.resize(fNOpDets);
192  for (size_t const OpDet : util::counter(fNOpDets)) {
193  // check OpDet in same drift volume
194  if (!isOpDetInSameTPC(ScintPoint, fOpDetector[OpDet].center)) {
195  DetectedVisibilities[OpDet] = 0.;
196  continue;
197  }
198  // check distance smaller than maximum distance
199  geo::Vector_t const relative = ScintPoint - fOpDetector[OpDet].center;
200  const double distance = relative.R();
201  if (distance > fMaxPDDistance) {
202  DetectedVisibilities[OpDet] = 0.;
203  continue;
204  }
205 
206  DetectedVisibilities[OpDet] = VUVVisibility(ScintPoint, fOpDetector[OpDet]);
207  }
208  }
209 
211  OpticalDetector const& opDet) const
212  {
213  // distance and angle between ScintPoint and OpDet center
214  geo::Vector_t const relative = ScintPoint - opDet.center;
215  const double distance = relative.R();
216  double cosine;
217  if (opDet.orientation == 2)
218  cosine = std::abs(relative.Z()) / distance;
219  else if (opDet.orientation == 1)
220  cosine = std::abs(relative.Y()) / distance;
221  else
222  cosine = std::abs(relative.X()) / distance;
223  const double theta = fast_acos(cosine) * 180. / CLHEP::pi;
224 
225  double solid_angle = 0.;
226  // ARAPUCAS/Bars (rectangle)
227  if (opDet.type == 0) {
228  // get scintillation point coordinates relative to arapuca window centre
229  geo::Vector_t const abs_relative{
230  std::abs(relative.X()), std::abs(relative.Y()), std::abs(relative.Z())};
231  solid_angle = Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, abs_relative, opDet.orientation);
232  }
233  // PMTs (dome)
234  else if (opDet.type == 1) {
235  solid_angle = Omega_Dome_Model(distance, theta);
236  }
237  // PMTs (disk)
238  else if (opDet.type == 2) {
239  const double zy_offset = std::sqrt(relative.Y() * relative.Y() + relative.Z() * relative.Z());
240  const double x_distance = std::abs(relative.X());
241  solid_angle = Disk_SolidAngle(zy_offset, x_distance, fradius);
242  }
243  else {
244  throw cet::exception("SemiAnalyticalModel")
245  << "Error: Invalid optical detector shape requested - configuration "
246  "error in semi-analytical model, only rectangular, dome or disk "
247  "optical detectors are supported."
248  << "\n";
249  }
250 
251  // calculate visibility by geometric acceptance
252  // accounting for solid angle and LAr absorbtion length
253  double visibility_geo =
254  std::exp(-1. * distance / fvuv_absorption_length) * (solid_angle / (4 * CLHEP::pi));
255 
256  // apply Gaisser-Hillas correction for Rayleigh scattering distance
257  // and angular dependence offset angle bin
258  const size_t j = (theta / fdelta_angulo_vuv);
259 
260  // determine GH parameters, accounting for border effects
261  // radial distance from centre of detector (Y-Z standard / X-Z laterals)
262  double r = 0;
263  if (opDet.orientation == 2)
264  r = std::hypot(ScintPoint.X() - fcathode_centre[0], ScintPoint.Y() - fcathode_centre[1]);
265  else if (opDet.orientation == 1)
266  r = std::hypot(ScintPoint.X() - fcathode_centre[0], ScintPoint.Z() - fcathode_centre[2]);
267  else
268  r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
269 
270  double pars_ini[4] = {0, 0, 0, 0};
271  double s1 = 0;
272  double s2 = 0;
273  double s3 = 0;
274  // flat PDs
275  if ((opDet.type == 0 || opDet.type == 2) && (fIsFlatPDCorr || fIsFlatPDCorrLat)) {
276  if ((opDet.orientation == 1 && fIsFlatPDCorrLat) ||
277  (opDet.orientation == 2 && fIsFlatPDCorrLat)) { // laterals
278  pars_ini[0] = fGHvuvpars_flat_lateral[0][j];
279  pars_ini[1] = fGHvuvpars_flat_lateral[1][j];
280  pars_ini[2] = fGHvuvpars_flat_lateral[2][j];
281  pars_ini[3] = fGHvuvpars_flat_lateral[3][j];
282  s1 =
284  s2 =
286  s3 =
288  }
289  else if (opDet.orientation == 0 && fIsFlatPDCorr) { // cathode/anode
290  pars_ini[0] = fGHvuvpars_flat[0][j];
291  pars_ini[1] = fGHvuvpars_flat[1][j];
292  pars_ini[2] = fGHvuvpars_flat[2][j];
293  pars_ini[3] = fGHvuvpars_flat[3][j];
297  }
298  else {
299  throw cet::exception("SemiAnalyticalModel")
300  << "Error: flat optical detectors are found, but parameters are "
301  "missing - configuration error in semi-analytical model."
302  << "\n";
303  }
304  }
305  // dome PDs
306  else if (opDet.type == 1 && fIsDomePDCorr) {
307  pars_ini[0] = fGHvuvpars_dome[0][j];
308  pars_ini[1] = fGHvuvpars_dome[1][j];
309  pars_ini[2] = fGHvuvpars_dome[2][j];
310  pars_ini[3] = fGHvuvpars_dome[3][j];
314  }
315  else {
316  throw cet::exception("SemiAnalyticalModel")
317  << "Error: Invalid optical detector shape requested or corrections are "
318  "missing - configuration error in semi-analytical model."
319  << "\n";
320  }
321 
322  // add border correction to parameters
323  pars_ini[0] = pars_ini[0] + s1 * r;
324  pars_ini[1] = pars_ini[1] + s2 * r;
325  pars_ini[2] = pars_ini[2] + s3 * r;
326  pars_ini[3] = pars_ini[3];
327 
328  // calculate correction
329  double GH_correction = Gaisser_Hillas(distance, pars_ini);
330 
331  // check sensible GH correction values, GH correction should never be large
332  // catches occasional issues caused by overflow
333  if (!std::isfinite(GH_correction) || GH_correction < 0 || GH_correction > 10) GH_correction = 0;
334 
335  // determine corrected visibility of photo-detector
336  return GH_correction * visibility_geo / cosine;
337  }
338 
339  //......................................................................
340  // VIS semi-analytical model visibility calculation
342  std::vector<double>& ReflDetectedVisibilities,
343  geo::Point_t const& ScintPoint,
344  bool AnodeMode) const
345  {
346  // 1). calculate visibility of VUV photons on
347  // reflective foils via solid angle + Gaisser-Hillas
348  // corrections:
349 
350  // get scintpoint coords relative to centre of cathode plane and set plane
351  // dimensions
352  geo::Vector_t ScintPoint_relative;
353  Dims plane_dimensions;
354  double plane_depth;
355  if (AnodeMode) {
356  plane_dimensions = fanode_plane;
357  plane_depth = fanode_plane_depth;
358  ScintPoint_relative.SetCoordinates(std::abs(ScintPoint.X() - fanode_plane_depth),
359  std::abs(ScintPoint.Y() - fanode_centre[1]),
360  std::abs(ScintPoint.Z() - fanode_centre[2]));
361  }
362  else {
363  plane_dimensions = fcathode_plane;
364  plane_depth = ScintPoint.X() < 0. ? -fplane_depth : fplane_depth;
365  ScintPoint_relative.SetCoordinates(std::abs(ScintPoint.X() - plane_depth),
366  std::abs(ScintPoint.Y() - fcathode_centre[1]),
367  std::abs(ScintPoint.Z() - fcathode_centre[2]));
368  }
369 
370  // calculate solid angle of anode/cathode from the scintillation point,
371  // orientation always = 0 (anode/cathode)
372  double solid_angle_cathode = Rectangle_SolidAngle(plane_dimensions, ScintPoint_relative, 0);
373 
374  // calculate distance and angle between ScintPoint and hotspot
375  // vast majority of hits in hotspot region directly infront of scintpoint,
376  // therefore consider attenuation for this distance and on axis GH instead of
377  // for the centre coordinate
378  double distance_cathode = std::abs(plane_depth - ScintPoint.X());
379  // calculate hits on cathode plane via geometric acceptance
380  double cathode_visibility_geo = std::exp(-1. * distance_cathode / fvuv_absorption_length) *
381  (solid_angle_cathode / (4. * CLHEP::pi));
382 
383  // determine Gaisser-Hillas correction including border effects
384  // use flat correction
385  double r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
386  double pars_ini[4] = {0, 0, 0, 0};
387  double s1 = 0;
388  double s2 = 0;
389  double s3 = 0;
390  if (fIsFlatPDCorr) {
391  pars_ini[0] = fGHvuvpars_flat[0][0];
392  pars_ini[1] = fGHvuvpars_flat[1][0];
393  pars_ini[2] = fGHvuvpars_flat[2][0];
394  pars_ini[3] = fGHvuvpars_flat[3][0];
398  }
399  else {
400  throw cet::exception("SemiAnalyticalModel")
401  << "Error: flat optical detector VUV correction required for reflected "
402  "semi-analytic hits. - configuration error in semi-analytical model."
403  << "\n";
404  }
405 
406  // add border correction
407  pars_ini[0] = pars_ini[0] + s1 * r;
408  pars_ini[1] = pars_ini[1] + s2 * r;
409  pars_ini[2] = pars_ini[2] + s3 * r;
410  pars_ini[3] = pars_ini[3];
411 
412  // calculate corrected number of hits
413  double GH_correction = Gaisser_Hillas(distance_cathode, pars_ini);
414 
415  // check sensible GH correction values, GH correction should never be large
416  // catches occasional issues caused by overflow
417  if (!std::isfinite(GH_correction) || GH_correction < 0 || GH_correction > 10) GH_correction = 0;
418 
419  const double cathode_visibility_rec = GH_correction * cathode_visibility_geo;
420 
421  // 2). detemine visibility of each PD
422  const geo::Point_t hotspot = {plane_depth, ScintPoint.Y(), ScintPoint.Z()};
423  ReflDetectedVisibilities.resize(fNOpDets);
424  for (size_t const OpDet : util::counter(fNOpDets)) {
425  if (!isOpDetInSameTPC(ScintPoint, fOpDetector[OpDet].center)) {
426  ReflDetectedVisibilities[OpDet] = 0.;
427  continue;
428  }
429 
430  ReflDetectedVisibilities[OpDet] =
431  VISVisibility(ScintPoint, fOpDetector[OpDet], cathode_visibility_rec, hotspot, AnodeMode);
432  }
433  }
434 
436  OpticalDetector const& opDet,
437  const double cathode_visibility,
438  geo::Point_t const& hotspot,
439  bool AnodeMode) const
440  {
441  // set correct plane_depth
442  double plane_depth;
443  if (AnodeMode)
444  plane_depth = fanode_plane_depth;
445  else
446  plane_depth = ScintPoint.X() < 0. ? -fplane_depth : fplane_depth;
447 
448  // calculate visibility of the optical
449  // detector from the hotspot using solid angle:
450 
451  geo::Vector_t const emission_relative = hotspot - opDet.center;
452 
453  // calculate distances and angles for application of corrections
454  // distance from hotspot to optical detector
455  const double distance_vis = emission_relative.R();
456  // angle between hotspot and optical detector
457  double cosine_vis;
458  if (opDet.orientation == 2) { // lateral fixed at z
459  cosine_vis = std::abs(emission_relative.Z()) / distance_vis;
460  }
461  else if (opDet.orientation == 1) { // lateral fixed at y
462  cosine_vis = std::abs(emission_relative.Y()) / distance_vis;
463  }
464  else { // anode/cathode (default)
465  cosine_vis = std::abs(emission_relative.X()) / distance_vis;
466  }
467  const double theta_vis = fast_acos(cosine_vis) * 180. / CLHEP::pi;
468 
469  // calculate solid angle of optical channel
470  double solid_angle_detector = 0.;
471  // ARAPUCAS/Bars (rectangle)
472  if (opDet.type == 0) {
473  // get hotspot coordinates relative to opDet
474  geo::Vector_t const abs_emission_relative{std::abs(emission_relative.X()),
475  std::abs(emission_relative.Y()),
476  std::abs(emission_relative.Z())};
477  solid_angle_detector =
478  Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, abs_emission_relative, opDet.orientation);
479  }
480  // PMTS (dome)
481  else if (opDet.type == 1) {
482  solid_angle_detector = Omega_Dome_Model(distance_vis, theta_vis);
483  }
484  // PMTs (disk)
485  else if (opDet.type == 2) {
486  const double zy_offset = std::sqrt(emission_relative.Y() * emission_relative.Y() +
487  emission_relative.Z() * emission_relative.Z());
488  const double x_distance = std::abs(emission_relative.X());
489  solid_angle_detector = Disk_SolidAngle(zy_offset, x_distance, fradius);
490  }
491  else {
492  throw cet::exception("SemiAnalyticalModel")
493  << "Error: Invalid optical detector shape requested - configuration "
494  "error in semi-analytical model, only rectangular, dome or disk "
495  "optical detectors are supported."
496  << "\n";
497  }
498 
499  // calculate number of hits via geometeric acceptance
500  double visibility_geo = (solid_angle_detector / (2. * CLHEP::pi)) *
501  cathode_visibility; // 2*pi due to presence of reflective foils
502 
503  // determine correction factor, depending on PD type
504  const size_t k = (theta_vis / fdelta_angulo_vis); // off-set angle bin
505  double r;
506  if ((opDet.orientation == 1 && fIsFlatPDCorrLat) ||
507  (opDet.orientation == 2 && fIsFlatPDCorrLat))
508  r = std::hypot(ScintPoint.X() - fcathode_centre[0], ScintPoint.Z() - fcathode_centre[2]);
509  else
510  r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
511  double d_c = std::abs(ScintPoint.X() - plane_depth); // distance to cathode
512  double border_correction = 0;
513  // flat PDs
514  if ((opDet.type == 0 || opDet.type == 2) && (fIsFlatPDCorr || fIsFlatPDCorrLat)) {
515  // cathode/anode case
516  if (opDet.orientation == 0 && fIsFlatPDCorr) {
517 
518  border_correction =
520  }
521  // laterals case
522  else if ((opDet.orientation == 1 && fIsFlatPDCorrLat) ||
523  (opDet.orientation == 2 && fIsFlatPDCorrLat)) {
524  border_correction = interpolate2(fvis_distances_x_flat_lateral,
527  d_c,
528  r,
529  k);
530  }
531  else {
532  throw cet::exception("SemiAnalyticalModel")
533  << "Error: Invalid optical detector shape requested or corrections "
534  "are missing - configuration error in semi-analytical model."
535  << "\n";
536  }
537  }
538  // dome PDs
539  else if (opDet.type == 1 && fIsDomePDCorr) {
540  border_correction =
542  }
543  else {
544  throw cet::exception("SemiAnalyticalModel")
545  << "Error: Invalid optical detector shape requested or corrections are "
546  "missing - configuration error in semi-analytical model."
547  << "\n";
548  }
549 
550  // apply anode reflectivity factor
551  if (AnodeMode) border_correction *= fAnodeReflectivity;
552 
553  return border_correction * visibility_geo / cosine_vis;
554  }
555 
556  //......................................................................
557  // Gaisser-Hillas function definition
558  double SemiAnalyticalModel::Gaisser_Hillas(const double x, const double* par) const
559  {
560  double X_mu_0 = par[3];
561  double Normalization = par[0];
562  double Diff = par[1] - X_mu_0;
563  double Term = std::pow((x - X_mu_0) / Diff, Diff / par[2]);
564  double Exponential = std::exp((par[1] - x) / par[2]);
565 
566  return (Normalization * Term * Exponential);
567  }
568 
569  //......................................................................
570  // solid angle of circular aperture
571  double SemiAnalyticalModel::Disk_SolidAngle(const double d, const double h, const double b) const
572  {
573  if (b <= 0. || d < 0. || h <= 0.) return 0.;
574  const double leg2 = (b + d) * (b + d);
575  const double aa = std::sqrt(h * h / (h * h + leg2));
576  if (isApproximatelyZero(d)) { return 2. * CLHEP::pi * (1. - aa); }
577  double bb = 2. * std::sqrt(b * d / (h * h + leg2));
578  double cc = 4. * b * d / leg2;
579 
580  if (isDefinitelyGreaterThan(d, b)) {
581  try {
582  return 2. * aa *
583  (std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()) -
584  boost::math::ellint_1(bb, noLDoublePromote()));
585  }
586  catch (std::domain_error& e) {
587  if (isApproximatelyEqual(d, b, 1e-9)) {
588  mf::LogWarning("SemiAnalyticalModel")
589  << "Elliptic Integral in Disk_SolidAngle() given parameters "
590  "outside domain."
591  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
592  << "\nRelax condition and carry on.";
593  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
594  }
595  else {
596  mf::LogError("SemiAnalyticalModel")
597  << "Elliptic Integral inside Disk_SolidAngle() given parameters "
598  "outside domain.\n"
599  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
600  return 0.;
601  }
602  }
603  }
604  if (isDefinitelyLessThan(d, b)) {
605  try {
606  return 2. * CLHEP::pi -
607  2. * aa *
608  (boost::math::ellint_1(bb, noLDoublePromote()) +
609  std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()));
610  }
611  catch (std::domain_error& e) {
612  if (isApproximatelyEqual(d, b, 1e-9)) {
613  mf::LogWarning("SemiAnalyticalModel")
614  << "Elliptic Integral in Disk_SolidAngle() given parameters "
615  "outside domain."
616  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
617  << "\nRelax condition and carry on.";
618  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
619  }
620  else {
621  mf::LogError("SemiAnalyticalModel")
622  << "Elliptic Integral inside Disk_SolidAngle() given parameters "
623  "outside domain.\n"
624  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
625  return 0.;
626  }
627  }
628  }
629  if (isApproximatelyEqual(d, b)) {
630  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
631  }
632  return 0.;
633  }
634 
635  //......................................................................
636  // solid angle of rectangular aperture
638  const double b,
639  const double d) const
640  {
641  double aa = a / (2. * d);
642  double bb = b / (2. * d);
643  double aux = (1. + aa * aa + bb * bb) / ((1. + aa * aa) * (1. + bb * bb));
644  return 4. * fast_acos(std::sqrt(aux));
645  }
646 
648  geo::Vector_t const& v,
649  int OpDetOrientation) const
650  {
651  // v is the position of the track segment with respect to
652  // the center position of the arapuca window
653 
654  // solid angle calculation depends on orientation of PD, set correct distances
655  // to use
656  double d1;
657  double d2;
658  if (OpDetOrientation == 2) {
659  // lateral PD, arapuca plane fixed in z direction
660  d1 = std::abs(v.X());
661  d2 = std::abs(v.Z());
662  }
663  else if (OpDetOrientation == 1) {
664  // lateral PD, arapuca plane fixed in y direction
665  d1 = std::abs(v.X());
666  d2 = std::abs(v.Y());
667  }
668  else {
669  // anode/cathode PD, arapuca plane fixed in x direction [default]
670  d1 = std::abs(v.Y());
671  d2 = std::abs(v.X());
672  }
673  // arapuca plane fixed in x direction
674  if (isApproximatelyZero(d1) && isApproximatelyZero(v.Z())) {
675  return Rectangle_SolidAngle(o.h, o.w, d2);
676  }
677  if (isDefinitelyGreaterThan(d1, o.h * .5) &&
678  isDefinitelyGreaterThan(std::abs(v.Z()), o.w * .5)) {
679  double A = d1 - o.h * .5;
680  double B = std::abs(v.Z()) - o.w * .5;
681  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (B + o.w), d2) -
682  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), d2) -
683  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, d2) +
684  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
685  .25;
686  return to_return;
687  }
688  if ((d1 <= o.h * .5) && (std::abs(v.Z()) <= o.w * .5)) {
689  double A = -d1 + o.h * .5;
690  double B = -std::abs(v.Z()) + o.w * .5;
691  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (o.w - B), d2) +
692  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), d2) +
693  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, d2) +
694  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
695  .25;
696  return to_return;
697  }
698  if (isDefinitelyGreaterThan(d1, o.h * .5) && (std::abs(v.Z()) <= o.w * .5)) {
699  double A = d1 - o.h * .5;
700  double B = -std::abs(v.Z()) + o.w * .5;
701  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (o.w - B), d2) -
702  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), d2) +
703  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, d2) -
704  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
705  .25;
706  return to_return;
707  }
708  if ((d1 <= o.h * .5) && isDefinitelyGreaterThan(std::abs(v.Z()), o.w * .5)) {
709  double A = -d1 + o.h * .5;
710  double B = std::abs(v.Z()) - o.w * .5;
711  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (B + o.w), d2) -
712  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, d2) +
713  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), d2) -
714  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
715  .25;
716  return to_return;
717  }
718 
719  return 0.;
720  }
721 
722  //......................................................................
723  // solid angle of dome aperture
724  double SemiAnalyticalModel::Omega_Dome_Model(const double distance, const double theta) const
725  {
726  // this function calculates the solid angle of a semi-sphere of radius b,
727  // as a correction to the analytic formula of the on-axix solid angle,
728  // as we move off-axis an angle theta. We have used 9-angular bins
729  // with delta_theta width.
730 
731  // par0 = Radius correction close
732  // par1 = Radius correction far
733  // par2 = breaking distance betwween "close" and "far"
734 
735  constexpr double par0[9] = {0., 0., 0., 0., 0., 0.597542, 1.00872, 1.46993, 2.04221};
736  constexpr double par1[9] = {
737  0., 0., 0.19569, 0.300449, 0.555598, 0.854939, 1.39166, 2.19141, 2.57732};
738  const double delta_theta = 10.;
739  int j = int(theta / delta_theta);
740  // PMT radius
741  const double b = fradius; // cm
742  // distance form which the model parameters break (empirical value)
743  const double d_break = 5 * b; // par2
744 
745  if (distance >= d_break) {
746  double R_apparent_far = b - par1[j];
747  double ratio_square = (R_apparent_far * R_apparent_far) / (distance * distance);
748  return (2 * CLHEP::pi * (1 - std::sqrt(1 - ratio_square)));
749  }
750  else {
751  double R_apparent_close = b - par0[j];
752  double ratio_square = (R_apparent_close * R_apparent_close) / (distance * distance);
753  return (2 * CLHEP::pi * (1 - std::sqrt(1 - ratio_square)));
754  }
755  }
756 
757  //......................................................................
758  // checks photo-detector is in same TPC/argon volume as scintillation
760  geo::Point_t const& OpDetPoint) const
761  {
762  // check optical channel is in same TPC as scintillation light, if not doesn't see light
763  // temporary method, needs to be replaced with geometry service
764  // working for SBND, uBooNE, DUNE HD 1x2x6, DUNE HD 10kt and DUNE VD subset
765 
766  // special case for SBND = 2 TPCs
767  // check x coordinate has same sign or is close to zero
768  if (fNTPC == 2 && ((ScintPoint.X() < 0.) != (OpDetPoint.X() < 0.)) &&
769  std::abs(OpDetPoint.X()) > 10.) {
770  return false;
771  }
772 
773  // special case for DUNE-HD 10kt = 300 TPCs
774  // check whether distance in drift direction > 1 drift distance
775  if (fNTPC == 300 && std::abs(ScintPoint.X() - OpDetPoint.X()) > fDriftDistance) {
776  return false;
777  }
778 
779  // not needed for DUNE HD 1x2x6, DUNE VD subset, uBooNE
780 
781  return true;
782  }
783 
784  std::vector<SemiAnalyticalModel::OpticalDetector> SemiAnalyticalModel::opticalDetectors() const
785  {
786  std::vector<SemiAnalyticalModel::OpticalDetector> opticalDetector;
787  for (size_t const i : util::counter(fNOpDets)) {
788  geo::OpDetGeo const& opDet = fGeom.OpDetGeoFromOpDet(i);
789  geo::Point_t center = opDet.GetCenter();
790  double height;
791  double length;
792  int type;
793  int orientation;
794  if (opDet.isSphere()) { // dome PMTs
795  type = 1; // dome
796  orientation = 0; // anode/cathode (default)
797  length = -1;
798  height = -1;
799  }
800  else if (opDet.isBar()) {
801  type = 0; // (X)Arapucas/Bars
802  // determine orientation to get correction OpDet dimensions
803  length = opDet.Length();
804  height = opDet.Height();
805  if (opDet.Width() > opDet.Length()) { // laterals along x-y plane, Z dimension smallest
806  orientation = 2;
807  length = opDet.Width();
808  }
809  else if (opDet.Width() > opDet.Height()) { // laterals along x-z plane, Y dimension smallest
810  orientation = 1;
811  height = opDet.Width();
812  }
813  else { // anode/cathode (default), X dimension smallest
814  orientation = 0;
815  }
816  }
817  else {
818  type = 2; // disk PMTs
819  orientation = 0; // anode/cathode (default)
820  length = -1;
821  height = -1;
822  }
823  opticalDetector.emplace_back(
824  SemiAnalyticalModel::OpticalDetector{height, length, center, type, orientation});
825  }
826  return opticalDetector;
827  }
828 
829 } // namespace phot
Float_t x
Definition: compare.C:6
std::vector< double > fvis_distances_x_dome
TRandom r
Definition: spectrum.C:23
std::vector< std::vector< double > > fborder_corr_flat_lateral
Point_t GetCathodeCenter() const
Returns the expected drift direction based on geometry.
Definition: TPCGeo.cxx:139
geo::WireReadoutGeom const & fChannelMap
static std::vector< geo::BoxBoundedGeo > extractActiveLArVolume(geo::GeometryCore const &geom)
Definition: ISTPC.cxx:46
Utilities related to art service access.
double VISVisibility(geo::Point_t const &ScintPoint, OpticalDetector const &opDet, const double cathode_visibility, geo::Point_t const &hotspot, bool AnodeMode=false) const
Encapsulate the construction of a single cyostat .
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:160
bool isOpDetInSameTPC(geo::Point_t const &ScintPoint, geo::Point_t const &OpDetPoint) const
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
unsigned int NTPC(CryostatID const &cryoid=details::cryostat_zero) const
Returns the total number of TPCs in the specified cryostat.
Definition: GeometryCore.h:416
bool isBar() const
Returns whether the detector shape is a bar (TGeoBBox).
Definition: OpDetGeo.h:167
Point_t const & GetCenter() const
Definition: OpDetGeo.h:71
double VUVVisibility(geo::Point_t const &ScintPoint, OpticalDetector const &opDet) const
double Rectangle_SolidAngle(const double a, const double b, const double d) const
constexpr auto abs(T v)
Returns the absolute value of the argument.
std::vector< std::vector< std::vector< double > > > fvispars_dome
static constexpr bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
cout<< "Opened file "<< fin<< " ixs= "<< ixs<< endl;if(ixs==0) hhh=(TH1F *) fff-> Get("h1")
Definition: AddMC.C:8
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
std::vector< std::vector< double > > fGHvuvpars_flat_lateral
std::vector< double > fvis_distances_r_flat
const std::vector< geo::BoxBoundedGeo > fActiveVolumes
std::vector< std::vector< double > > fborder_corr_dome
bool isSphere() const
Returns whether the detector shape is a hemisphere (TGeoSphere).
Definition: OpDetGeo.h:170
Access the description of the physical detector geometry.
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:292
geo::GeometryCore const & fGeom
bool isfinite(Vector const &v)
Returns whether all components of the vector are finite.
SemiAnalyticalModel(const fhicl::ParameterSet &VUVHits, const fhicl::ParameterSet &VISHits, const bool doReflectedLight=false, const bool includeAnodeReflections=false, const bool useXeAbsorption=false)
double fast_acos(double xin)
std::vector< double > fborder_corr_angulo_flat
double Length() const
Definition: OpDetGeo.h:77
double interpolate2(const std::vector< double > &xDistances, const std::vector< double > &rDistances, const std::vector< std::vector< std::vector< double >>> &parameters, const double x, const double r, const size_t k)
std::vector< double > fvis_distances_r_dome
Float_t d
Definition: plot.C:235
std::vector< double > fborder_corr_angulo_dome
std::vector< std::vector< std::vector< double > > > fvispars_flat_lateral
Test of util::counter and support utilities.
The data type to uniquely identify a TPC.
Definition: geo_types.h:306
double Gaisser_Hillas(const double x, const double *par) const
std::vector< double > fvis_distances_x_flat_lateral
unsigned int NOpDets() const
Number of OpDets in the whole detector.
Encapsulate the geometry of an optical detector.
ROOT::Math::PositionVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Point_t
Type for representation of position in physical 3D space.
Definition: geo_vectors.h:180
General LArSoft Utilities.
std::vector< std::vector< double > > fGHvuvpars_flat
void detectedDirectVisibilities(std::vector< double > &DetectedVisibilities, geo::Point_t const &ScintPoint) const
double DriftDistance() const
Drift distance is defined as the distance between the anode and the cathode, in centimeters.
Definition: TPCGeo.cxx:165
double Omega_Dome_Model(const double distance, const double theta) const
std::vector< std::vector< double > > fGHvuvpars_dome
double Disk_SolidAngle(const double d, const double h, const double b) const
static constexpr bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
std::vector< double > fvis_distances_x_flat
void detectedReflectedVisibilities(std::vector< double > &ReflDetectedVisibilities, geo::Point_t const &ScintPoint, bool AnodeMode=false) const
static constexpr bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
std::vector< std::vector< std::vector< double > > > fvispars_flat
std::vector< double > fvis_distances_r_flat_lateral
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
constexpr T pi()
Returns the constant pi (up to 35 decimal digits of precision)
boost::math::policies::policy< boost::math::policies::promote_double< false > > noLDoublePromote
std::vector< OpticalDetector > opticalDetectors() const
const std::vector< OpticalDetector > fOpDetector
double Width() const
Definition: OpDetGeo.h:78
PlaneGeo const & Plane(TPCID const &tpcid, View_t view) const
Returns the specified wire.
std::vector< std::vector< double > > fborder_corr_flat
TPCGeo const & TPC(TPCID const &tpcid=details::tpc_zero) const
Returns the specified TPC.
Definition: GeometryCore.h:448
double interpolate(const std::vector< double > &xData, const std::vector< double > &yData, const double x, const bool extrapolate, size_t i)
Float_t e
Definition: plot.C:35
double Height() const
Definition: OpDetGeo.h:79
static constexpr bool isApproximatelyZero(TReal a, TReal tolerance=std::numeric_limits< TReal >::epsilon())
art framework interface to geometry description
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
OpDetGeo const & OpDetGeoFromOpDet(unsigned int OpDet) const
Returns the geo::OpDetGeo object for the given detector number.
std::vector< double > fborder_corr_angulo_flat_lateral