SemiAnalyticalModel.cxx
Go to the documentation of this file.
1 #include "SemiAnalyticalModel.h"
2 
3 // LArSoft Libraries
8 
9 // support libraries
10 #include "cetlib_except/exception.h"
12 
13 #include "TMath.h"
14 
15 #include <vector>
16 #include <iostream>
17 
18 #include "boost/math/special_functions/ellint_1.hpp"
19 #include "boost/math/special_functions/ellint_3.hpp"
20 
21 // constructor
22 SemiAnalyticalModel::SemiAnalyticalModel(fhicl::ParameterSet VUVHits, fhicl::ParameterSet VISHits, bool doReflectedLight, bool includeAnodeReflections)
23  : fISTPC{*(lar::providerFrom<geo::Geometry>())}
24 {
25 
26  // set input parameters
27  fVUVHitsParams = VUVHits;
28  fDoReflectedLight = doReflectedLight;
29  fIncludeAnodeReflections = includeAnodeReflections;
30 
31  if (fDoReflectedLight || includeAnodeReflections) fVISHitsParams = VISHits;
32 
33  // initialise parameters and geometry
34  std::cout << "Semi-analytical model initialized." << std::endl;
36 }
37 
38 // initialization
40 {
41  // access geometry
42  geo::GeometryCore const& geom = *(lar::providerFrom<geo::Geometry>());
43 
44  // store information from geometry service
45  nOpDets = geom.NOpDets();
46  fNTPC = geom.NTPC();
47 
48  // get TPC information
50 
51  fcathode_centre = {geom.TPC(0, 0).GetCathodeCenter().X(),
52  fActiveVolumes[0].CenterY(),
53  fActiveVolumes[0].CenterZ()};
54 
55  fanode_centre = {geom.TPC(0, 0).FirstPlane().GetCenter().X(),
56  fActiveVolumes[0].CenterY(),
57  fActiveVolumes[0].CenterZ() };
58 
59  // get PDS information
60  fOpDetType.reserve(nOpDets); fOpDetOrientation.reserve(nOpDets);
61  fOpDetCenter.reserve(nOpDets); fOpDetLength.reserve(nOpDets); fOpDetHeight.reserve(nOpDets);
62 
63  for (size_t const i : util::counter(nOpDets)) {
64 
65  geo::OpDetGeo const& opDet = geom.OpDetGeoFromOpDet(i);
66  fOpDetCenter.push_back(opDet.GetCenter());
67 
68  if (opDet.isSphere()) { // dome PMTs
69  fOpDetType.push_back(1); // dome
70  fOpDetOrientation.push_back(0); // anode/cathode (default)
71  fOpDetLength.push_back(-1);
72  fOpDetHeight.push_back(-1);
73  }
74  else if (opDet.isBar()) {
75  fOpDetType.push_back(0); // (X)Arapucas/Bars
76  // determine orientation to get correction OpDet dimensions
77  fOpDetLength.push_back(opDet.Length());
78  if (opDet.Width() > opDet.Height()) { // laterals, Y dimension smallest
79  fOpDetOrientation.push_back(1);
80  fOpDetHeight.push_back(opDet.Width());
81  }
82  else { // anode/cathode (default), X dimension smallest
83  fOpDetOrientation.push_back(0);
84  fOpDetHeight.push_back(opDet.Height());
85  }
86  }
87  else {
88  fOpDetType.push_back(2); // disk PMTs
89  fOpDetOrientation.push_back(0); // anode/cathode (default)
90  fOpDetLength.push_back(-1);
91  fOpDetHeight.push_back(-1);
92  }
93  }
94 
95  // determine LAr absorption length in cm
96  std::map<double, double> abs_length_spectrum = lar::providerFrom<detinfo::LArPropertiesService>()->AbsLengthSpectrum();
97  std::vector<double> x_v, y_v;
98  for (auto elem : abs_length_spectrum) {
99  x_v.push_back(elem.first);
100  y_v.push_back(elem.second);
101  }
102  fL_abs_vuv = std::round(interpolate(x_v, y_v, 9.7, false)); // 9.7 eV: peak of VUV emission spectrum // TO DO UNHARDCODE FOR XENON
103 
104  // Load Gaisser-Hillas corrections for VUV semi-analytic hits
105  mf::LogInfo("SemiAnalyticalModel") << "Using VUV visibility parameterization";
106 
107  fIsFlatPDCorr = fVUVHitsParams.get<bool>("FlatPDCorr", false);
108  fIsFlatPDCorrLat = fVUVHitsParams.get<bool>("FlatPDCorrLat", false);
109  fIsDomePDCorr = fVUVHitsParams.get<bool>("DomePDCorr", false);
110  fdelta_angulo_vuv = fVUVHitsParams.get<double>("delta_angulo_vuv", 10);
111  fradius = fVUVHitsParams.get<double>("PMT_radius", 10.16);
112  fApplyFieldCageTransparency = fVUVHitsParams.get<bool>("ApplyFieldCageTransparency", false);
113  fFieldCageTransparencyLateral = fVUVHitsParams.get<double>("FieldCageTransparencyLateral", 1.0);
114  fFieldCageTransparencyCathode = fVUVHitsParams.get<double>("FieldCageTransparencyCathode", 1.0);
115 
116  if (!fIsFlatPDCorr && !fIsDomePDCorr && !fIsFlatPDCorrLat) {
117  throw cet::exception("SemiAnalyticalModel")
118  << "Both isFlatPDCorr/isFlatPDCorrLat and isDomePDCorr parameters are false, at least one type of parameterisation is required for the semi-analytic light simulation." << "\n";
119  }
120  if (fIsFlatPDCorr) {
121  fGHvuvpars_flat = fVUVHitsParams.get<std::vector<std::vector<double>>>("GH_PARS_flat");
122  fborder_corr_angulo_flat = fVUVHitsParams.get<std::vector<double>>("GH_border_angulo_flat");
123  fborder_corr_flat = fVUVHitsParams.get<std::vector<std::vector<double>>>("GH_border_flat");
124  }
125  if (fIsFlatPDCorrLat) {
126  fGHvuvpars_flat_lateral = fVUVHitsParams.get<std::vector<std::vector<std::vector<double>>>>("GH_PARS_flat_lateral");
127  fGH_distances_anode = fVUVHitsParams.get<std::vector<double>>("GH_distances_anode");
128  }
129  if (fIsDomePDCorr) {
130  fGHvuvpars_dome = fVUVHitsParams.get<std::vector<std::vector<double>>>("GH_PARS_dome");
131  fborder_corr_angulo_dome = fVUVHitsParams.get<std::vector<double>>("GH_border_angulo_dome");
132  fborder_corr_dome = fVUVHitsParams.get<std::vector<std::vector<double>>>("GH_border_dome");
133  }
134 
135  // Load corrections for VIS semi-analytic hits
136  if (fDoReflectedLight) {
137  mf::LogInfo("SemiAnalyticalModel") << "Using VIS (reflected) visibility parameterization";
138  fdelta_angulo_vis = fVISHitsParams.get<double>("delta_angulo_vis");
139 
140  if (fIsFlatPDCorr) {
141  fvis_distances_x_flat = fVISHitsParams.get<std::vector<double>>("VIS_distances_x_flat");
142  fvis_distances_r_flat = fVISHitsParams.get<std::vector<double>>("VIS_distances_r_flat");
143  fvispars_flat = fVISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_flat");
144  }
145  if (fIsDomePDCorr) {
146  fvis_distances_x_dome = fVISHitsParams.get<std::vector<double>>("VIS_distances_x_dome");
147  fvis_distances_r_dome = fVISHitsParams.get<std::vector<double>>("VIS_distances_r_dome");
148  fvispars_dome = fVISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_dome");
149  }
150 
151  // cathode dimensions
152  fcathode_ydimension = fActiveVolumes[0].SizeY();
153  fcathode_zdimension = fActiveVolumes[0].SizeZ();
154 
155  // set cathode plane struct for solid angle function
159  }
160 
161  // Load corrections for Anode reflections configuration
163  mf::LogInfo("SemiAnalyticalModel") << "Using anode reflections parameterization";
164  fdelta_angulo_vis = fVISHitsParams.get<double>("delta_angulo_vis");
165  fAnodeReflectivity = fVISHitsParams.get<double>("AnodeReflectivity");
166 
167  if (fIsFlatPDCorr) {
168  fvis_distances_x_flat = fVISHitsParams.get<std::vector<double>>("VIS_distances_x_flat");
169  fvis_distances_r_flat = fVISHitsParams.get<std::vector<double>>("VIS_distances_r_flat");
170  fvispars_flat = fVISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_flat");
171  }
172 
173  if (fIsFlatPDCorrLat) {
174  fvis_distances_x_flat_lateral = fVISHitsParams.get<std::vector<double>>("VIS_distances_x_flat_lateral");
175  fvis_distances_r_flat_lateral = fVISHitsParams.get<std::vector<double>>("VIS_distances_r_flat_lateral");
176  fvispars_flat_lateral = fVISHitsParams.get<std::vector<std::vector<std::vector<double>>>>("VIS_correction_flat_lateral");
177  }
178 
179  // anode dimensions
180  fanode_ydimension = fActiveVolumes[0].SizeY();
181  fanode_zdimension = fActiveVolumes[0].SizeZ();
182 
183  // set anode plane struct for solid angle function
187  }
188 
189 }
190 
191 //......................................................................
192 // VUV semi-analytical model visibility calculation
193 void
194 SemiAnalyticalModel::detectedDirectVisibilities(std::map<size_t, double>& DetectedVisibilities,
195  geo::Point_t const& ScintPoint)
196 {
197  for (size_t const OpDet : util::counter(nOpDets)) {
198  if (!isOpDetInSameTPC(ScintPoint, fOpDetCenter[OpDet])) continue;
199 
200  // set detector struct for solid angle function
202  fOpDetHeight[OpDet], fOpDetLength[OpDet],
203  fOpDetCenter[OpDet], fOpDetType[OpDet], fOpDetOrientation[OpDet]};
204 
205  double DetThis;
206  VUVVisibility(ScintPoint, op, DetThis);
207 
208  DetectedVisibilities[OpDet] = DetThis;
209  }
210 }
211 
212 void
213 SemiAnalyticalModel::VUVVisibility(geo::Point_t const& ScintPoint, OpticalDetector const& opDet, double &DetThis)
214 {
215  // distance and angle between ScintPoint and OpDetPoint
216  geo::Vector_t const relative = ScintPoint - opDet.OpDetPoint;
217  const double distance = relative.R();
218  double cosine;
219  if (opDet.orientation == 1) cosine = std::abs(relative.Y()) / distance;
220  else cosine = std::abs(relative.X()) / distance;
221  const double theta = fast_acos(cosine) * 180. / CLHEP::pi;
222 
223  double solid_angle = 0.;
224  // ARAPUCAS/Bars (rectangle)
225  if (opDet.type == 0) {
226  // get scintillation point coordinates relative to arapuca window centre
227  geo::Vector_t const abs_relative{std::abs(relative.X()), std::abs(relative.Y()), std::abs(relative.Z())};
228  solid_angle = Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, abs_relative, opDet.orientation);
229  }
230  // PMTs (dome)
231  else if (opDet.type == 1) {
232  solid_angle = Omega_Dome_Model(distance, theta);
233  }
234  // PMTs (disk)
235  else if (opDet.type == 2) {
236  const double zy_offset = std::sqrt(relative.Y() * relative.Y() + relative.Z() * relative.Z());
237  const double x_distance = std::abs(relative.X());
238  solid_angle = Disk_SolidAngle(zy_offset, x_distance, fradius);
239  }
240  else {
241  throw cet::exception("SemiAnalyticalModel")
242  << "Error: Invalid optical detector shape requested - configuration error in semi-analytical model, only rectangular, dome or disk optical detectors are supported." << "\n";
243  }
244 
245  // calculate visibility by geometric acceptance
246  // accounting for solid angle and LAr absorbtion length
247  double visibility_geo = std::exp(-1. * distance / fL_abs_vuv) * (solid_angle / (4 * CLHEP::pi));
248 
249  // apply Gaisser-Hillas correction for Rayleigh scattering distance
250  // and angular dependence offset angle bin
251  const size_t j = (theta / fdelta_angulo_vuv);
252 
253  // determine GH parameters, accounting for border effects
254  // radial distance from centre of detector (Y-Z)
255  double r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
256 
257  double pars_ini[4] = {0, 0, 0, 0};
258  double s1 = 0; double s2 = 0; double s3 = 0;
259  // flat PDs
260  if ((opDet.type == 0 || opDet.type == 2) && (fIsFlatPDCorr || fIsFlatPDCorrLat)){
261  if (opDet.orientation == 1 && fIsFlatPDCorrLat) { // laterals, alternate parameterisation method
262  // distance to anode plane
263  double d_anode = std::abs(fanode_centre[0] - ScintPoint.X());
264 
265  // build arrays for interpolation
266  int n_distances = fGH_distances_anode.size();
267  std::vector<double> p1, p2, p3, p4;
268  p1.reserve(n_distances); p2.reserve(n_distances); p3.reserve(n_distances); p4.reserve(n_distances);
269  for (int i = 0; i < n_distances; i++) {
270  p1.push_back(fGHvuvpars_flat_lateral[0][i][j]);
271  p2.push_back(fGHvuvpars_flat_lateral[1][i][j]);
272  p3.push_back(fGHvuvpars_flat_lateral[2][i][j]);
273  p4.push_back(fGHvuvpars_flat_lateral[3][i][j]);
274  }
275 
276  // interpolate in distance to anode
277  pars_ini[0] = interpolate(fGH_distances_anode, p1, d_anode, false);
278  pars_ini[1] = interpolate(fGH_distances_anode, p2, d_anode, false);
279  pars_ini[2] = interpolate(fGH_distances_anode, p3, d_anode, false);
280  pars_ini[3] = interpolate(fGH_distances_anode, p4, d_anode, false);
281 
282  }
283  else if (opDet.orientation == 0 && fIsFlatPDCorr) { // cathode/anode, default parameterisation method
284  pars_ini[0] = fGHvuvpars_flat[0][j];
285  pars_ini[1] = fGHvuvpars_flat[1][j];
286  pars_ini[2] = fGHvuvpars_flat[2][j];
287  pars_ini[3] = fGHvuvpars_flat[3][j];
291  }
292  else {
293  throw cet::exception("SemiAnalyticalModel")
294  << "Error: flat optical detectors are found, but parameters are missing - configuration error in semi-analytical model." << "\n";
295  }
296  }
297  // dome PDs
298  else if (opDet.type == 1 && fIsDomePDCorr) {
299  pars_ini[0] = fGHvuvpars_dome[0][j];
300  pars_ini[1] = fGHvuvpars_dome[1][j];
301  pars_ini[2] = fGHvuvpars_dome[2][j];
302  pars_ini[3] = fGHvuvpars_dome[3][j];
306  }
307  else {
308  throw cet::exception("SemiAnalyticalModel")
309  << "Error: Invalid optical detector shape requested or corrections are missing - configuration error in semi-analytical model." << "\n";
310  }
311 
312  // add border correction to parameters
313  pars_ini[0] = pars_ini[0] + s1 * r;
314  pars_ini[1] = pars_ini[1] + s2 * r;
315  pars_ini[2] = pars_ini[2] + s3 * r;
316  pars_ini[3] = pars_ini[3];
317 
318  // calculate correction
319  double GH_correction = Gaisser_Hillas(distance, pars_ini);
320 
321  // apply field cage transparency factor
323  if (opDet.orientation == 1) GH_correction = GH_correction * fFieldCageTransparencyLateral;
324  else if (opDet.orientation == 0) GH_correction = GH_correction * fFieldCageTransparencyCathode;
325  }
326 
327  // determine corrected visibility of photo-detector
328  DetThis = GH_correction * visibility_geo / cosine;
329 }
330 
331 //......................................................................
332 // VIS semi-analytical model visibility calculation
333 void
334 SemiAnalyticalModel::detectedReflectedVisibilities(std::map<size_t, double>& ReflDetectedVisibilities,
335  geo::Point_t const& ScintPoint,
336  bool AnodeMode)
337 {
338  // 1). calculate visibility of VUV photons on
339  // reflective foils via solid angle + Gaisser-Hillas
340  // corrections:
341 
342  // get scintpoint coords relative to centre of cathode plane and set plane dimensions
343  geo::Vector_t ScintPoint_relative;
344  Dims plane_dimensions;
345  double plane_depth;
346  if (AnodeMode) {
347  plane_dimensions = fanode_plane;
348  plane_depth = fanode_plane_depth;
349  ScintPoint_relative.SetCoordinates(std::abs(ScintPoint.X() - fanode_plane_depth), std::abs(ScintPoint.Y() - fanode_centre[1]), std::abs(ScintPoint.Z() - fanode_centre[2]));
350  }
351  else {
352  plane_dimensions = fcathode_plane;
353  plane_depth = ScintPoint.X() < 0. ? -fplane_depth : fplane_depth;
354  ScintPoint_relative.SetCoordinates(std::abs(ScintPoint.X() - plane_depth), std::abs(ScintPoint.Y() - fcathode_centre[1]), std::abs(ScintPoint.Z() - fcathode_centre[2]));
355  }
356 
357  // calculate solid angle of anode/cathode from the scintillation point, orientation always = 0 (anode/cathode)
358  double solid_angle_cathode = Rectangle_SolidAngle(plane_dimensions, ScintPoint_relative, 0);
359 
360  // calculate distance and angle between ScintPoint and hotspot
361  // vast majority of hits in hotspot region directly infront of scintpoint,
362  // therefore consider attenuation for this distance and on axis GH instead of for the centre coordinate
363  double distance_cathode = std::abs(plane_depth - ScintPoint.X());
364  // calculate hits on cathode plane via geometric acceptance
365  double cathode_visibility_geo = std::exp(-1. * distance_cathode / fL_abs_vuv) * (solid_angle_cathode / (4. * CLHEP::pi));
366 
367  // determine Gaisser-Hillas correction including border effects
368  // use flat correction
369  double r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
370  double pars_ini[4] = {0, 0, 0, 0};
371  double s1 = 0; double s2 = 0; double s3 = 0;
372  if(fIsFlatPDCorr) {
373  pars_ini[0] = fGHvuvpars_flat[0][0];
374  pars_ini[1] = fGHvuvpars_flat[1][0];
375  pars_ini[2] = fGHvuvpars_flat[2][0];
376  pars_ini[3] = fGHvuvpars_flat[3][0];
380  }
381  else {
382  throw cet::exception("SemiAnalyticalModel")
383  << "Error: flat optical detector VUV correction required for reflected semi-analytic hits. - configuration error in semi-analytical model." << "\n";
384  }
385 
386  // add border correction
387  pars_ini[0] = pars_ini[0] + s1 * r;
388  pars_ini[1] = pars_ini[1] + s2 * r;
389  pars_ini[2] = pars_ini[2] + s3 * r;
390  pars_ini[3] = pars_ini[3];
391 
392  // calculate corrected number of hits
393  double GH_correction = Gaisser_Hillas(distance_cathode, pars_ini);
394  const double cathode_visibility_rec = GH_correction * cathode_visibility_geo;
395 
396  // 2). detemine visibility of each PD
397  const geo::Point_t hotspot = {plane_depth, ScintPoint.Y(), ScintPoint.Z()};
398  for (size_t const OpDet : util::counter(nOpDets)) {
399  if (!isOpDetInSameTPC(ScintPoint, fOpDetCenter[OpDet])) continue;
400 
401  // set detector struct for solid angle function
402  const OpticalDetector op{
403  fOpDetHeight[OpDet], fOpDetLength[OpDet],
404  fOpDetCenter[OpDet], fOpDetType[OpDet], fOpDetOrientation[OpDet]};
405 
406  double ReflDetThis;
407  VISVisibility(ScintPoint, op, cathode_visibility_rec, hotspot, ReflDetThis, AnodeMode);
408 
409  ReflDetectedVisibilities[OpDet] = ReflDetThis;
410  }
411 }
412 
413 void
414 SemiAnalyticalModel::VISVisibility(geo::Point_t const& ScintPoint, OpticalDetector const& opDet, const double cathode_visibility,
415  geo::Point_t const& hotspot, double &ReflDetThis, bool AnodeMode)
416 {
417  // set correct plane_depth
418  double plane_depth;
419  if (AnodeMode) plane_depth = fanode_plane_depth;
420  else plane_depth = ScintPoint.X() < 0. ? -fplane_depth : fplane_depth;
421 
422  // calculate visibility of the optical
423  // detector from the hotspot using solid angle:
424 
425  geo::Vector_t const emission_relative = hotspot - opDet.OpDetPoint;
426 
427  // calculate distances and angles for application of corrections
428  // distance from hotspot to optical detector
429  const double distance_vis = emission_relative.R();
430  // angle between hotspot and optical detector
431  double cosine_vis;
432  if (opDet.orientation == 1) { // lateral
433  cosine_vis = std::abs(emission_relative.Y()) / distance_vis;
434  }
435  else { // anode/cathode (default)
436  cosine_vis = std::abs(emission_relative.X()) / distance_vis;
437  }
438  const double theta_vis = fast_acos(cosine_vis) * 180. / CLHEP::pi;
439 
440  // calculate solid angle of optical channel
441  double solid_angle_detector = 0.;
442  // ARAPUCAS/Bars (rectangle)
443  if (opDet.type == 0) {
444  // get hotspot coordinates relative to opDet
445  geo::Vector_t const abs_emission_relative{std::abs(emission_relative.X()),
446  std::abs(emission_relative.Y()),
447  std::abs(emission_relative.Z())};
448  solid_angle_detector = Rectangle_SolidAngle(Dims{opDet.h, opDet.w}, abs_emission_relative, opDet.orientation);
449  }
450  // PMTS (dome)
451  else if (opDet.type == 1) {
452  solid_angle_detector = Omega_Dome_Model(distance_vis, theta_vis);
453  }
454  // PMTs (disk)
455  else if (opDet.type == 2) {
456  const double zy_offset = std::sqrt(emission_relative.Y() * emission_relative.Y() +
457  emission_relative.Z() * emission_relative.Z());
458  const double x_distance = std::abs(emission_relative.X());
459  solid_angle_detector = Disk_SolidAngle(zy_offset, x_distance, fradius);
460  }
461  else {
462  throw cet::exception("SemiAnalyticalModel")
463  << "Error: Invalid optical detector shape requested - configuration error in semi-analytical model, only rectangular, dome or disk optical detectors are supported." << "\n";
464  }
465 
466  // calculate number of hits via geometeric acceptance
467  double visibility_geo = (solid_angle_detector / (2. * CLHEP::pi)) *
468  cathode_visibility; // 2*pi due to presence of reflective foils
469 
470  // determine correction factor, depending on PD type
471  const size_t k = (theta_vis / fdelta_angulo_vis); // off-set angle bin
472  double r = std::hypot(ScintPoint.Y() - fcathode_centre[1], ScintPoint.Z() - fcathode_centre[2]);
473  double d_c = std::abs(ScintPoint.X() - plane_depth); // distance to cathode
474  double border_correction = 0;
475  // flat PDs
476  if ((opDet.type == 0 || opDet.type == 2) && (fIsFlatPDCorr || fIsFlatPDCorrLat)) {
477  // cathode/anode case
478  if (opDet.orientation == 0 && fIsFlatPDCorr) border_correction = interpolate2(fvis_distances_x_flat, fvis_distances_r_flat, fvispars_flat, d_c, r, k);
479  // laterals case
481  else {
482  throw cet::exception("SemiAnalyticalModel")
483  << "Error: Invalid optical detector shape requested or corrections are missing - configuration error in semi-analytical model." << "\n";
484  }
485  }
486  // dome PDs
487  else if (opDet.type == 1 && fIsDomePDCorr) border_correction = interpolate2(fvis_distances_x_dome, fvis_distances_r_dome, fvispars_dome, d_c, r, k);
488  else {
489  throw cet::exception("SemiAnalyticalModel")
490  << "Error: Invalid optical detector shape requested or corrections are missing - configuration error in semi-analytical model." << "\n";
491  }
492 
493  // apply anode reflectivity factor
494  if (AnodeMode) border_correction = border_correction * fAnodeReflectivity;
495 
496  // apply field cage transparency factor
498  if (opDet.orientation == 1) border_correction = border_correction * fFieldCageTransparencyLateral;
499  else if (opDet.orientation == 0) border_correction = border_correction * fFieldCageTransparencyCathode;
500  }
501 
502  ReflDetThis = border_correction * visibility_geo / cosine_vis;
503 }
504 
505 //......................................................................
506 // Gaisser-Hillas function definition
507 double
508 SemiAnalyticalModel::Gaisser_Hillas(const double x, const double* par) const
509 {
510  double X_mu_0 = par[3];
511  double Normalization = par[0];
512  double Diff = par[1] - X_mu_0;
513  double Term = std::pow((x - X_mu_0) / Diff, Diff / par[2]);
514  double Exponential = std::exp((par[1] - x) / par[2]);
515 
516  return (Normalization * Term * Exponential);
517 }
518 
519 //......................................................................
520 // solid angle of circular aperture
521 double
522 SemiAnalyticalModel::Disk_SolidAngle(const double d, const double h, const double b) const
523 {
524  if (b <= 0. || d < 0. || h <= 0.) return 0.;
525  const double leg2 = (b + d) * (b + d);
526  const double aa = std::sqrt(h * h / (h * h + leg2));
527  if (isApproximatelyZero(d)) { return 2. * CLHEP::pi * (1. - aa); }
528  double bb = 2. * std::sqrt(b * d / (h * h + leg2));
529  double cc = 4. * b * d / leg2;
530 
531  if (isDefinitelyGreaterThan(d, b)) {
532  try {
533  return 2. * aa *
534  (std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()) -
535  boost::math::ellint_1(bb, noLDoublePromote()));
536  }
537  catch (std::domain_error& e) {
538  if (isApproximatelyEqual(d, b, 1e-9)) {
539  mf::LogWarning("SemiAnalyticalModel")
540  << "Elliptic Integral in Disk_SolidAngle() given parameters "
541  "outside domain."
542  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
543  << "\nRelax condition and carry on.";
544  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
545  }
546  else {
547  mf::LogError("SemiAnalyticalModel")
548  << "Elliptic Integral inside Disk_SolidAngle() given parameters "
549  "outside domain.\n"
550  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
551  return 0.;
552  }
553  }
554  }
555  if (isDefinitelyLessThan(d, b)) {
556  try {
557  return 2. * CLHEP::pi -
558  2. * aa *
559  (boost::math::ellint_1(bb, noLDoublePromote()) +
560  std::sqrt(1. - cc) * boost::math::ellint_3(bb, cc, noLDoublePromote()));
561  }
562  catch (std::domain_error& e) {
563  if (isApproximatelyEqual(d, b, 1e-9)) {
564  mf::LogWarning("SemiAnalyticalModel")
565  << "Elliptic Integral in Disk_SolidAngle() given parameters "
566  "outside domain."
567  << "\nbb: " << bb << "\ncc: " << cc << "\nException message: " << e.what()
568  << "\nRelax condition and carry on.";
569  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
570  }
571  else {
572  mf::LogError("SemiAnalyticalModel")
573  << "Elliptic Integral inside Disk_SolidAngle() given parameters "
574  "outside domain.\n"
575  << "\nbb: " << bb << "\ncc: " << cc << "Exception message: " << e.what();
576  return 0.;
577  }
578  }
579  }
580  if (isApproximatelyEqual(d, b)) {
581  return CLHEP::pi - 2. * aa * boost::math::ellint_1(bb, noLDoublePromote());
582  }
583  return 0.;
584 }
585 
586 //......................................................................
587 // solid angle of rectangular aperture
588 double
589 SemiAnalyticalModel::Rectangle_SolidAngle(const double a, const double b, const double d) const
590 {
591  double aa = a / (2. * d);
592  double bb = b / (2. * d);
593  double aux = (1. + aa * aa + bb * bb) / ((1. + aa * aa) * (1. + bb * bb));
594  return 4. * fast_acos(std::sqrt(aux));
595 }
596 
597 double
598 SemiAnalyticalModel::Rectangle_SolidAngle(Dims const& o, geo::Vector_t const& v, double OpDetOrientation) const
599 {
600  // v is the position of the track segment with respect to
601  // the center position of the arapuca window
602 
603  // solid angle calculation depends on orientation of PD, set correct distances to use
604  double d1;
605  double d2;
606  if (OpDetOrientation == 1) {
607  // lateral PD, arapuca plane fixed in y direction
608  d1 = std::abs(v.X());
609  d2 = std::abs(v.Y());
610  }
611  else {
612  // anode/cathode PD, arapuca plane fixed in x direction [default]
613  d1 = std::abs(v.Y());
614  d2 = std::abs(v.X());
615  }
616  // arapuca plane fixed in x direction
617  if (isApproximatelyZero(d1) && isApproximatelyZero(v.Z())) {
618  return Rectangle_SolidAngle(o.h, o.w, d2);
619  }
620  if (isDefinitelyGreaterThan(d1, o.h * .5) && isDefinitelyGreaterThan(std::abs(v.Z()), o.w * .5)) {
621  double A = d1 - o.h * .5;
622  double B = std::abs(v.Z()) - o.w * .5;
623  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (B + o.w), d2) -
624  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), d2) -
625  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, d2) +
626  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
627  .25;
628  return to_return;
629  }
630  if ((d1 <= o.h * .5) && (std::abs(v.Z()) <= o.w * .5)) {
631  double A = -d1 + o.h * .5;
632  double B = -std::abs(v.Z()) + o.w * .5;
633  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (o.w - B), d2) +
634  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), d2) +
635  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, d2) +
636  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
637  .25;
638  return to_return;
639  }
640  if (isDefinitelyGreaterThan(d1, o.h * .5) && (std::abs(v.Z()) <= o.w * .5)) {
641  double A = d1 - o.h * .5;
642  double B = -std::abs(v.Z()) + o.w * .5;
643  double to_return = (Rectangle_SolidAngle(2. * (A + o.h), 2. * (o.w - B), d2) -
644  Rectangle_SolidAngle(2. * A, 2. * (o.w - B), d2) +
645  Rectangle_SolidAngle(2. * (A + o.h), 2. * B, d2) -
646  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
647  .25;
648  return to_return;
649  }
650  if ((d1 <= o.h * .5) && isDefinitelyGreaterThan(std::abs(v.Z()), o.w * .5)) {
651  double A = -d1 + o.h * .5;
652  double B = std::abs(v.Z()) - o.w * .5;
653  double to_return = (Rectangle_SolidAngle(2. * (o.h - A), 2. * (B + o.w), d2) -
654  Rectangle_SolidAngle(2. * (o.h - A), 2. * B, d2) +
655  Rectangle_SolidAngle(2. * A, 2. * (B + o.w), d2) -
656  Rectangle_SolidAngle(2. * A, 2. * B, d2)) *
657  .25;
658  return to_return;
659  }
660 
661  return 0.;
662 }
663 
664 //......................................................................
665 // solid angle of dome aperture
666 double
667 SemiAnalyticalModel::Omega_Dome_Model(const double distance, const double theta) const
668 {
669  // this function calculates the solid angle of a semi-sphere of radius b,
670  // as a correction to the analytic formula of the on-axix solid angle,
671  // as we move off-axis an angle theta. We have used 9-angular bins
672  // with delta_theta width.
673 
674  // par0 = Radius correction close
675  // par1 = Radius correction far
676  // par2 = breaking distance betwween "close" and "far"
677 
678  double par0[9] = {0., 0., 0., 0., 0., 0.597542, 1.00872, 1.46993, 2.04221};
679  double par1[9] = {0, 0, 0.19569, 0.300449, 0.555598, 0.854939, 1.39166, 2.19141, 2.57732};
680  const double delta_theta = 10.;
681  int j = int(theta/delta_theta);
682  // PMT radius
683  const double b = fradius; // cm
684  // distance form which the model parameters break (empirical value)
685  const double d_break = 5*b; //par2
686 
687  if(distance >= d_break) {
688  double R_apparent_far = b - par1[j];
689  return (2*CLHEP::pi * (1 - std::sqrt(1 - std::pow(R_apparent_far/distance,2))));
690  }
691  else {
692  double R_apparent_close = b - par0[j];
693  return (2*CLHEP::pi * (1 - std::sqrt(1 - std::pow(R_apparent_close/distance,2))));
694  }
695 }
696 
697 //......................................................................
698 // checks photo-detector is in same TPC/argon volume as scintillation
699 bool
701  geo::Point_t const& OpDetPoint) const
702 {
703  // method working for SBND, uBooNE, DUNE-HD 1x2x6 and DUNE-VD 1x8x6
704  // will need to be replaced to work in full DUNE geometry, ICARUS geometry
705  // check x coordinate has same sign or is close to zero, otherwise return false
706  if (((ScintPoint.X() < 0.) != (OpDetPoint.X() < 0.)) &&
707  std::abs(OpDetPoint.X()) > 10. && fNTPC == 2) { // TODO: replace with geometry service
708  return false;
709  }
710  return true;
711 }
712 
713 //......................................................................
714 double
716 {
717  double negate = double(x < 0);
718  x = std::abs(x);
719  x -= double(x > 1.0) * (x - 1.0); // <- equivalent to min(1.0,x), but faster
720  double ret = -0.0187293;
721  ret = ret * x;
722  ret = ret + 0.0742610;
723  ret = ret * x;
724  ret = ret - 0.2121144;
725  ret = ret * x;
726  ret = ret + 1.5707288;
727  ret = ret * std::sqrt(1.0 - x);
728  ret = ret - 2. * negate * ret;
729  return negate * 3.14159265358979 + ret;
730 }
731 
732 //......................................................................
733 // Returns interpolated value at x from parallel arrays ( xData, yData )
734 // Assumes that xData has at least two elements, is sorted and is strictly
735 // monotonic increasing boolean argument extrapolate determines behaviour
736 // beyond ends of array (if needed)
737 double
738 SemiAnalyticalModel::interpolate(const std::vector<double>& xData,
739  const std::vector<double>& yData,
740  double x,
741  bool extrapolate,
742  size_t i) const
743 {
744  if (i == 0) {
745  size_t size = xData.size();
746  if (x >= xData[size - 2]) { // special case: beyond right end
747  i = size - 2;
748  }
749  else {
750  while (x > xData[i + 1])
751  i++;
752  }
753  }
754  double xL = xData[i];
755  double xR = xData[i + 1];
756  double yL = yData[i];
757  double yR = yData[i + 1]; // points on either side (unless beyond ends)
758  if (!extrapolate) { // if beyond ends of array and not extrapolating
759  if (x < xL) return yL;
760  if (x > xR) return yL;
761  }
762  const double dydx = (yR - yL) / (xR - xL); // gradient
763  return yL + dydx * (x - xL); // linear interpolation
764 }
765 
766 double
767 SemiAnalyticalModel::interpolate2(const std::vector<double>& xDistances,
768  const std::vector<double>& rDistances,
769  const std::vector<std::vector<std::vector<double>>>& parameters,
770  const double x,
771  const double r,
772  const size_t k) const
773 {
774  // interpolate in x for each r bin, for angle bin k
775  const size_t nbins_r = parameters[k].size();
776  std::vector<double> interp_vals(nbins_r, 0.0);
777  {
778  size_t idx = 0;
779  size_t size = xDistances.size();
780  if (x >= xDistances[size - 2])
781  idx = size - 2;
782  else {
783  while (x > xDistances[idx + 1])
784  idx++;
785  }
786  for (size_t i = 0; i < nbins_r; ++i) {
787  interp_vals[i] = interpolate(xDistances,
788  parameters[k][i],
789  x,
790  false,
791  idx);
792  }
793  }
794  // interpolate in r
795  double border_correction = interpolate(rDistances, interp_vals, r, false);
796  return border_correction;
797 }
std::vector< double > fvis_distances_r_dome
std::vector< std::vector< std::vector< double > > > fGHvuvpars_flat_lateral
std::vector< double > fvis_distances_r_flat_lateral
static constexpr bool isDefinitelyLessThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
double Gaisser_Hillas(const double x, const double *par) const
OpDetGeo const & OpDetGeoFromOpDet(unsigned int OpDet) const
Returns the geo::OpDetGeo object for the given detector number.
static std::vector< geo::BoxBoundedGeo > extractActiveLArVolume(geo::GeometryCore const &geom)
Definition: ISTPC.cxx:49
std::vector< std::vector< double > > fborder_corr_dome
Encapsulate the construction of a single cyostat.
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) const
std::vector< std::vector< std::vector< double > > > fvispars_flat_lateral
double Omega_Dome_Model(const double distance, const double theta) const
MaybeLogger_< ELseverityLevel::ELsev_info, false > LogInfo
void detectedDirectVisibilities(std::map< size_t, double > &DetectedVisibilities, geo::Point_t const &ScintPoint)
bool isBar() const
Returns whether the detector shape is a bar (TGeoBBox).
Definition: OpDetGeo.h:195
std::vector< double > fGH_distances_anode
std::vector< std::vector< double > > fGHvuvpars_flat
Point GetCathodeCenter() const
Definition: TPCGeo.h:298
std::vector< std::vector< double > > fGHvuvpars_dome
constexpr T pow(T x)
Definition: pow.h:72
std::vector< double > fvis_distances_r_flat
struct vector vector
geo::PlaneGeo const & FirstPlane() const
Returns the first wire plane (the closest to TPC center).
Definition: TPCGeo.h:245
std::vector< double > fOpDetHeight
void GetCenter(double *xyz, double localz=0.0) const
Definition: OpDetGeo.cxx:40
ROOT::Math::DisplacementVector3D< ROOT::Math::Cartesian3D< double >, ROOT::Math::GlobalCoordinateSystemTag > Vector_t
Type for representation of momenta in 3D space.
Definition: geo_vectors.h:164
void VUVVisibility(geo::Point_t const &ScintPoint, OpticalDetector const &opDet, double &DetThis)
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
std::vector< std::vector< std::vector< double > > > fvispars_flat
void detectedReflectedVisibilities(std::map< size_t, double > &ReflDetectedVisibilities, geo::Point_t const &ScintPoint, bool AnodeMode=false)
double fast_acos(double x) const
std::enable_if_t< std::is_arithmetic_v< T >, T > hypot(T x, T y)
Definition: hypot.h:60
bool isSphere() const
Returns whether the detector shape is a hemisphere (TGeoSphere).
Definition: OpDetGeo.h:198
decltype(auto) constexpr size(T &&obj)
ADL-aware version of std::size.
Definition: StdUtils.h:92
double Rectangle_SolidAngle(const double a, const double b, const double d) const
T abs(T value)
std::vector< geo::BoxBoundedGeo > fActiveVolumes
auto counter(T begin, T end)
Returns an object to iterate values from begin to end in a range-for loop.
Definition: counter.h:285
const double e
std::vector< double > fvis_distances_x_flat
boost::math::policies::policy< boost::math::policies::promote_double< false > > noLDoublePromote
const double a
T get(std::string const &key) const
Definition: ParameterSet.h:271
Point GetCenter() const
Returns the centre of the wire plane in world coordinates [cm].
Definition: PlaneGeo.h:479
double Length() const
Definition: OpDetGeo.h:81
double interpolate(const std::vector< double > &xData, const std::vector< double > &yData, double x, bool extrapolate, size_t i=0) const
Test of util::counter and support utilities.
std::vector< double > fvis_distances_x_flat_lateral
double distance(double x1, double y1, double z1, double x2, double y2, double z2)
std::vector< geo::Point_t > fOpDetCenter
std::vector< double > fOpDetLength
Description of geometry of one entire detector.
std::vector< std::vector< double > > fborder_corr_flat
unsigned int NOpDets() const
Number of OpDets in the whole detector.
unsigned int NTPC(unsigned int cstat=0) const
Returns the total number of TPCs in the specified cryostat.
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:184
Encapsulate the geometry of an optical detector.
static constexpr bool isApproximatelyEqual(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
void VISVisibility(geo::Point_t const &ScintPoint, OpticalDetector const &opDet, const double cathode_visibility, geo::Point_t const &hotspot, double &ReflDetThis, bool AnodeMode=false)
static constexpr bool isApproximatelyZero(TReal a, TReal tolerance=std::numeric_limits< TReal >::epsilon())
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
std::vector< int > fOpDetType
std::vector< double > fborder_corr_angulo_dome
bool isOpDetInSameTPC(geo::Point_t const &ScintPoint, geo::Point_t const &OpDetPoint) const
static bool * b
Definition: config.cpp:1043
std::vector< double > fvis_distances_x_dome
static constexpr bool isDefinitelyGreaterThan(TReal a, TReal b, TReal tolerance=std::numeric_limits< TReal >::epsilon())
list x
Definition: train.py:276
TPCGeo const & TPC(unsigned int const tpc=0, unsigned int const cstat=0) const
Returns the specified TPC.
float pi
Definition: units.py:11
double Width() const
Definition: OpDetGeo.h:82
double Disk_SolidAngle(const double d, const double h, const double b) const
fhicl::ParameterSet fVISHitsParams
SemiAnalyticalModel(fhicl::ParameterSet VUVHits, fhicl::ParameterSet VISHits, bool doReflectedLight=false, bool includeAnodeReflections=false)
double Height() const
Definition: OpDetGeo.h:83
std::vector< double > fborder_corr_angulo_flat
std::vector< int > fOpDetOrientation
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
QTextStream & endl(QTextStream &s)
fhicl::ParameterSet fVUVHitsParams
std::vector< std::vector< std::vector< double > > > fvispars_dome