gambit is hosted by Hepforge, IPPP Durham
GAMBIT  v1.5.0-2191-ga4742ac
a Global And Modular Bsm Inference Tool
Axions.cpp
Go to the documentation of this file.
1 
22 #include <algorithm>
23 #include <cmath>
24 #include <vector>
25 #include <string>
26 #include <iostream>
27 #include <sstream>
28 
30 #include <Eigen/Core>
32 
33 #include <gsl/gsl_math.h>
34 #include <gsl/gsl_sf.h>
35 #include <gsl/gsl_sf_trig.h>
36 #include <gsl/gsl_sf_erf.h>
37 #include <gsl/gsl_spline.h>
38 #include <gsl/gsl_interp2d.h>
39 #include <gsl/gsl_spline2d.h>
40 #include <gsl/gsl_histogram.h>
41 #include <gsl/gsl_roots.h>
42 #include <gsl/gsl_matrix.h>
43 #include <gsl/gsl_errno.h>
44 #include <gsl/gsl_odeiv2.h>
45 
53 
54 //#define AXION_DEBUG_MODE
55 //#define AXION_OMP_DEBUG_MODE
56 
57 namespace Gambit
58 {
59  namespace DarkBit
60  {
62  // //
63  // General Functions and Classes for Axions //
64  // //
66 
70  const double gagg_conversion = 1.0E-9;
71  const double gaee_conversion = 1.0E+13;
72 
74  // Auxillary functions and classes for interpolation //
76 
81  const std::map<InterpolationOptions1D, std::string> int_type_name = { { InterpolationOptions1D::linear, "linear" }, { InterpolationOptions1D::cspline, "cspline"} };
82  // AxionInterpolator class: Provides a general 1-D interpolation container based on the gsl library.
83  // Can be declared static for efficiency & easy one-time initialisation of interpolating functions.
84  class AxionInterpolator
85  {
86  public:
87  // Overloaded class creators for the AxionInterpolator class using the init function below.
88  AxionInterpolator();
89  AxionInterpolator(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type = InterpolationOptions1D::linear);
90  AxionInterpolator(std::string file, InterpolationOptions1D type = InterpolationOptions1D::linear);
91  AxionInterpolator& operator=(AxionInterpolator&&);
92  // Destructor.
93  ~AxionInterpolator();
94  // Delete copy constructor and assignment operator to avoid shallow copies.
95  AxionInterpolator(const AxionInterpolator&) = delete;
96  AxionInterpolator operator=(const AxionInterpolator&) = delete;
97  // Routine to access interpolated values.
98  double interpolate(double x);
99  // Routine to access upper and lower boundaries of available data.
100  double lower();
101  double upper();
102  private:
103  // Initialiser for the AxionInterpolator class.
104  void init(std::string file, InterpolationOptions1D type);
105  void init(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type);
106  // The gsl objects for the interpolating functions.
107  gsl_interp_accel *acc;
108  gsl_spline *spline;
109  // Upper and lower boundaries available for the interpolating function.
110  double lo;
111  double up;
112  };
113 
114  // Default constructor.
115  AxionInterpolator::AxionInterpolator()
116  {
117  acc = gsl_interp_accel_alloc();
118  spline = gsl_spline_alloc(gsl_interp_linear, 2);
119  }
120 
121  // Initialiser for the AxionInterpolator class.
122  void AxionInterpolator::init(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type)
123  {
124  int pts = x.size();
125  // Get first and last value of the "x" component.
126  lo = x.front();
127  up = x.back();
128  acc = gsl_interp_accel_alloc();
130  {
131  spline = gsl_spline_alloc(gsl_interp_cspline, pts);
132  }
133  else if (type == InterpolationOptions1D::linear)
134  {
135  spline = gsl_spline_alloc(gsl_interp_linear, pts);
136  }
137  else
138  {
139  DarkBit_error().raise(LOCAL_INFO, "ERROR! Interpolation type not known to class AxionInterpolator.");
140  }
141 
142  gsl_spline_init(spline, &x[0], &y[0], pts);
143  }
144 
145  AxionInterpolator::AxionInterpolator(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type) { init(x, y, type); }
146 
147  // Initialiser for the AxionInterpolator class.
148  void AxionInterpolator::init(std::string file, InterpolationOptions1D type)
149  {
150  // Check if file exists.
151  if (not(Utils::file_exists(file)))
152  {
153  DarkBit_error().raise(LOCAL_INFO, "ERROR! File '"+file+"' not found!");
154  } else {
155  logger() << LogTags::debug << "Reading data from file '"+file+"' and interpolating it with '"+int_type_name.at(type)+"' method." << EOM;
156  }
157  // Read numerical values from data file.
158  ASCIItableReader tab (file);
159  tab.setcolnames("x", "y");
160 
161  init(tab["x"], tab["y"], type);
162  }
163 
164  AxionInterpolator::AxionInterpolator(std::string file, InterpolationOptions1D type) { init(file, type); }
165 
166  // Move assignment operator
167  AxionInterpolator& AxionInterpolator::operator=(AxionInterpolator&& interp)
168  {
169  if(this != &interp)
170  {
171  std::swap(acc,interp.acc);
172  std::swap(spline,interp.spline);
173  std::swap(lo,interp.lo);
174  std::swap(up,interp.up);
175  }
176  return *this;
177  }
178 
179  // Destructor
180  AxionInterpolator::~AxionInterpolator()
181  {
182  gsl_spline_free(spline);
183  gsl_interp_accel_free(acc);
184  }
185 
186  // Routine to access interpolated values.
187  double AxionInterpolator::interpolate(double x) { return gsl_spline_eval(spline, x, acc); }
188 
189  // Routines to return upper and lower boundaries of interpolating function
190  double AxionInterpolator::lower() { return lo; }
191  double AxionInterpolator::upper() { return up; }
192 
193 
198  const std::map<InterpolationOptions2D, std::string> int_2d_type_name = { { InterpolationOptions2D::bilinear, "bilinear" }, { InterpolationOptions2D::bicubic, "bicubic"} };
199  // AxionInterpolator2D class: Provides a 2-D interpolation container based on the gsl library.
200  // Can be declared static for efficiency & easy one-time initialisation of interpolating functions.
201  class AxionInterpolator2D
202  {
203  public:
204  // Overloaded class creators for the AxionInterpolator class using the init function below.
205  AxionInterpolator2D();
206  AxionInterpolator2D(std::string file, InterpolationOptions2D type = InterpolationOptions2D::bilinear);
207  AxionInterpolator2D& operator=(AxionInterpolator2D&&);
208  // Destructor.
209  ~AxionInterpolator2D();
210  // Delete copy constructor and assignment operator to avoid shallow copies.
211  AxionInterpolator2D(const AxionInterpolator2D&) = delete;
212  AxionInterpolator2D operator=(const AxionInterpolator2D&) = delete;
213  // Routine to access interpolated values.
214  double interpolate(double x, double y);
215  // Routine to check if a point is inside the interpolating box.
216  bool is_inside_box(double x, double y);
217  private:
218  // Initialiser for the AxionInterpolator2D class.
219  void init(std::string file, InterpolationOptions2D type);
220  // The gsl objects for the interpolating functions that need to be available to the class routines.
221  gsl_interp_accel *x_acc;
222  gsl_interp_accel *y_acc;
223  gsl_spline2d *spline;
224  double* z;
225  // Upper and lower "x" and "y" values available to the interpolating function.
226  double x_lo, y_lo, x_up, y_up;
227  };
228 
229  // Move assignment operator
230  AxionInterpolator2D& AxionInterpolator2D::operator=(AxionInterpolator2D&& interp)
231  {
232  if(this != &interp)
233  {
234  std::swap(x_acc,interp.x_acc);
235  std::swap(y_acc,interp.y_acc);
236  std::swap(spline,interp.spline);
237  std::swap(x_lo,interp.x_lo);
238  std::swap(x_up,interp.x_up);
239  std::swap(y_lo,interp.y_lo);
240  std::swap(y_up,interp.y_up);
241  std::swap(z,interp.z);
242  }
243  return *this;
244  }
245 
246  // Destructor
247  AxionInterpolator2D::~AxionInterpolator2D()
248  {
249  gsl_spline2d_free (spline);
250  gsl_interp_accel_free (x_acc);
251  gsl_interp_accel_free (y_acc);
252  free(z);
253  }
254 
255  // Initialiser for the AxionInterpolator class.
256  void AxionInterpolator2D::init(std::string file, InterpolationOptions2D type)
257  {
258  // Check if file exists.
259  if (not(Utils::file_exists(file)))
260  {
261  DarkBit_error().raise(LOCAL_INFO, "ERROR! File '"+file+"' not found!");
262  } else {
263  logger() << LogTags::debug << "Reading data from file '"+file+"' and interpolating it with '"+int_2d_type_name.at(type)+"' method." << EOM;
264  }
265  // Read numerical values from data file.
266  ASCIItableReader tab (file);
267  tab.setcolnames("x", "y", "z");
268  // Initialise gsl interpolation routine.
269  // Get unique entries of "x" and "y" for the grid and grid size.
270  std::vector<double> x_vec = tab["x"];
271  sort(x_vec.begin(), x_vec.end());
272  x_vec.erase(unique(x_vec.begin(), x_vec.end()), x_vec.end());
273  int nx = x_vec.size();
274  std::vector<double> y_vec = tab["y"];
275  sort(y_vec.begin(), y_vec.end());
276  y_vec.erase(unique(y_vec.begin(), y_vec.end()), y_vec.end());
277  int ny = y_vec.size();
278  int n_grid_pts = tab["z"].size();
279 
280  if (nx*ny != n_grid_pts)
281  {
282  DarkBit_error().raise(LOCAL_INFO, "ERROR! The number of grid points ("+std::to_string(n_grid_pts)+") for AxionInterpolator2D does not equal the number of unique 'x' and 'y' values ("+std::to_string(nx)+" and "+std::to_string(ny)+")!\n Check formatting of the file: '"+file+"'.");
283  }
284 
285  const double* x = &x_vec[0];
286  const double* y = &y_vec[0];
287  // Allocate memory for "z" values array in gsl format
288  z = (double*) malloc(nx * ny * sizeof(double));
289 
291  {
292  spline = gsl_spline2d_alloc(gsl_interp2d_bicubic, nx, ny);
293  }
294  else if (type == InterpolationOptions2D::bilinear)
295  {
296  spline = gsl_spline2d_alloc(gsl_interp2d_bilinear, nx, ny);
297  }
298  else
299  {
300  DarkBit_error().raise(LOCAL_INFO, "ERROR! Interpolation type not known to class AxionInterpolator2D.");
301  }
302 
303  x_acc = gsl_interp_accel_alloc();
304  y_acc = gsl_interp_accel_alloc();
305 
306  // Determine first and last "x" and "y" values and grid step size.
307  x_lo = x_vec.front();
308  x_up = x_vec.back();
309  y_lo = y_vec.front();
310  y_up = y_vec.back();
311  double x_delta = (x_up-x_lo) / (nx-1);
312  double y_delta = (y_up-y_lo) / (ny-1);
313 
314  // Intialise grid.
315  for (int i = 0; i < n_grid_pts; i++)
316  {
317  // Determine appropriate indices for the grid points.
318  double temp = (tab["x"][i]-x_lo) / x_delta;
319  int ind_x = (int) (temp+0.5);
320  temp = (tab["y"][i]-y_lo) / y_delta;
321  int ind_y = (int) (temp+0.5);
322 
323  //std::cout << ind_x << "/" << nx-1 << " " << tab["x"][i] << " vs " << x[ind_x] << " " << ind_y << "/" << ny-1 << " " << tab["y"][i] << " vs " << y[ind_y] << std::endl;
324 
325  gsl_spline2d_set(spline, z, ind_x, ind_y, tab["z"][i]);
326  }
327  gsl_spline2d_init (spline, x, y, z, nx, ny);
328  }
329 
330  // Default creator with dummy entries for the objects w/ memory allocation
331  AxionInterpolator2D::AxionInterpolator2D()
332  {
333  x_acc = gsl_interp_accel_alloc();
334  y_acc = gsl_interp_accel_alloc();
335  spline = gsl_spline2d_alloc(gsl_interp2d_bilinear, 2, 2);
336  z = (double*) malloc(2 * 2 * sizeof(double));
337  }
338  // Overloaded class creators for the AxionInterpolator class using the init function above.
339  AxionInterpolator2D::AxionInterpolator2D(std::string file, InterpolationOptions2D type) { init(file, type); }
340 
341  // Routine to access interpolated values.
342  double AxionInterpolator2D::interpolate(double x, double y) { return gsl_spline2d_eval(spline, x, y, x_acc, y_acc); }
343 
344  // Routine to check if a point is inside the interpolating box.
345  bool AxionInterpolator2D::is_inside_box(double x, double y) { return ((x >= x_lo) && (x <= x_up) && (y >= y_lo) && (y <= y_up)); }
346 
350  // Auxillary function for a parabola (needed for H.E.S.S. likelihood approximation).
351  double parabola(double x, const double params[]) { return params[0]*x*x + params[1]*x + params[2]; }
352 
353  // Auxillary function to return the appropriate intersection between a parabola and a line (needed for H.E.S.S. likelihood).
354  double intersect_parabola_line(double a, double b, double sign, const double pparams[])
355  {
356  const double x1 = -3.673776;
357  const double y1 = 0.4;
358  double x2 = a - pparams[1];
359  double temp1 = a*a + 4.0*b*pparams[0] - 2.0*a*pparams[1] + pparams[1]*pparams[1] - 4.0*pparams[0]*pparams[2];
360  x2 = x2 - sign*sqrt(temp1);
361  x2 = x2/(2.0*pparams[0]);
362  double y2 = parabola(x2, pparams);
363  temp1 = x1 - x2;
364  double temp2 = y1 - y2;
365  return sqrt(temp1*temp1 + temp2*temp2);
366  }
367 
368  // HESS_Interpolator class: Provides a customised interpolation container for the H.E.S.S. likelihood.
369  class HESS_Interpolator
370  {
371  public:
372  // Class creator.
373  HESS_Interpolator(std::string file);
374  // Class destructor
375  ~HESS_Interpolator();
376  // Delete copy constructor and assignment operator to avoid shallow copies
377  HESS_Interpolator(const HESS_Interpolator&) = delete;
378  HESS_Interpolator operator=(const HESS_Interpolator&) = delete;
379  // Container for the tabulated data.
380  ASCIItableReader interp_lnL;
381  // Routine to return interpolated log-likelihood values.
382  double lnL(double epsilon, double gamma);
383  private:
384  gsl_interp_accel *acc[17];
385  gsl_spline *spline[17];
386  };
387 
388  // Class creator. Needs path to tabulated H.E.S.S. data.
389  HESS_Interpolator::HESS_Interpolator(std::string file)
390  {
391  // Initialise upper part of the likelihood interpolation (i.e. higher axion-photon coupling).
392  interp_lnL = ASCIItableReader(file);
393  interp_lnL.setcolnames("lnL16", "lnL15", "lnL14", "lnL13", "lnL12", "lnL11", "lnL10", "lnL9", "lnL8", "lnL7", "lnL6", "lnL5", "lnL4", "lnL3", "lnL2", "lnL1", "lnL0");
394  for (int i = 16; i >= 0; i--)
395  {
396  int pts = interp_lnL["lnL"+std::to_string(i)].size();
397  acc[i] = gsl_interp_accel_alloc ();
398  spline[i] = gsl_spline_alloc (gsl_interp_cspline, pts);
399  const double* epsvals = &interp_lnL["lnL"+std::to_string(i)][0];
400  if (pts==8) {
401  const double lnLvals [8] = {0., -2.30259, -2.99573, -4.60517, -4.60517, -2.99573, -2.30259, 0.};
402  gsl_spline_init (spline[i], epsvals, lnLvals, pts);
403  } else {
404  const double lnLvals [7] = {0., -2.30259, -2.99573, -4.60517, -2.99573, -2.30259, 0.};
405  gsl_spline_init (spline[i], epsvals, lnLvals, pts);
406  }
407  }
408  }
409 
410  // Destructor
411  HESS_Interpolator::~HESS_Interpolator()
412  {
413  for (auto spl : spline)
414  gsl_spline_free (spl);
415  for (auto ac : acc)
416  gsl_interp_accel_free (ac);
417  }
418 
419  // Rotuine to interpolate the H.E.S.S. log-likelihood values.
420  double HESS_Interpolator::lnL(double epsilon, double gamma)
421  {
422  // Parameters for the parabolae.
423  const double ppars00 [3] = {0.553040458173831, 3.9888540782199913, 6.9972958867687565};
424  const double ppars90 [3] = {1.2852894785722664, 9.42311266504736, 17.49643049277964};
425  const double ppars95 [3] = {1.4501115909461886, 10.647792383304218, 19.811978366687622};
426  double result = 0.0;
427 
428  // Check if we are inside the constrained region.
429  if ((gamma > parabola(epsilon, ppars00)) && (gamma < 1.2) && (epsilon > -4.64) && (epsilon < -2.57))
430  {
431  // Check if we are in the upper part (higher coupling; interpolation using linear and cubic splines).
432  if (gamma > 0.4)
433  {
434  // Cubic interpolation in Epsilon.
435  int index_lo = floor((gamma-0.4)/0.05);
436  int index_hi = index_lo + 1;
437  double z_lo = 0.0, z_hi = 0.0;
438  // Only use interpolating function where needed.
439  if ( (epsilon > interp_lnL["lnL"+std::to_string(index_lo)].front()) && (epsilon < interp_lnL["lnL"+std::to_string(index_lo)].back()) )
440  {
441  z_lo = gsl_spline_eval (spline[index_lo], epsilon, acc[index_lo]);
442  }
443 
444  if ( (epsilon > interp_lnL["lnL"+std::to_string(index_hi)].front()) && (epsilon < interp_lnL["lnL"+std::to_string(index_hi)].back()) )
445  {
446  z_hi = gsl_spline_eval (spline[index_hi], epsilon, acc[index_hi]);
447  }
448 
449  // Linear interpolation in Gamma.
450  double a = static_cast<double>(index_hi) - (gamma-0.4)/0.05;
451  double b = 1.0 - a;
452 
453  result = a*z_lo + b*z_hi;
454 
455  // If not in the upper part, we must be in the lower part.
456  } else {
457  const double loglikevals [4] = {-4.60517, -2.99573, -2.30259, 0.0};
458  // Gamma values belonging to the likelihood values along symmetry line (in terms of distance to 0.4).
459  double gammavals [4] = {0.0, 0.134006, 0.174898, 0.592678};
460  double distance = 0.4 - gamma;
461  // Check if point is on a vertical line with the 99% C.L. point
462  if (fabs(epsilon + 3.673776) > 1e-6)
463  {
464  // Calculate distance of point
465  double a = -3.673776 - epsilon;
466  double b = (-3.673776*gamma - 0.4*epsilon)/a;
467  double temp1 = distance;
468  distance = sqrt(a*a + temp1*temp1);
469  a = temp1/a;
470  temp1 = GSL_SIGN(-3.673776 - epsilon);
471  double temp2 = intersect_parabola_line(a, b, temp1, ppars00);
472  // CAVE: There used to be problems with distance > 1.0; these should be fixed now. Otherwise: replace by min(dist,1).
473  distance = distance/temp2;
474  gammavals[3] = 1.0;
475  gammavals[2] = intersect_parabola_line(a, b, temp1, ppars90)/temp2;
476  gammavals[1] = intersect_parabola_line(a, b, temp1, ppars95)/temp2;
477  }
478  gsl_interp_accel *acc = gsl_interp_accel_alloc ();
479  gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, 4);
480  gsl_spline_init (spline, gammavals, loglikevals, 4);
481  result = gsl_spline_eval (spline, distance, acc);
482  gsl_spline_free (spline);
483  gsl_interp_accel_free (acc);
484  }
485  }
486  // CAVE: There used to be a bug with log-likelihood > 0.0; this is fixed now, but still safeguard the result against roundoff errors.
487  return std::min(result,0.0);
488  }
489 
490 
492  // Solar model and integration routines to calculate the expected Helioscope signals //
494 
495  // SolarModel class: Provides a container to store a (tabulated) Solar model and functions to return its properties.
496  class SolarModel
497  {
498  public:
499  SolarModel();
500  SolarModel(std::string file);
501  ~SolarModel();
502  SolarModel& operator=(SolarModel&&);
503  // Delete copy constructor and assignment operator to avoid shallow copies
504  SolarModel(const SolarModel&) = delete;
505  SolarModel operator=(const SolarModel&) = delete;
506  // Min. and max. radius of the solar model file (distance r from the centre of the Sun in units of the solar radius)
507  double r_lo, r_hi;
508  // Routine to return the screening parameter kappa^2 (kappa^-1 = Debye-Hueckel radius).
509  double kappa_squared(double r);
510  // Routine to return the temperature in keV.
511  double temperature_in_keV(double r);
512  // Routine to return the plasma frequency squared.
513  double omega_pl_squared(double r);
514  private:
516  gsl_interp_accel *accel[3];
517  gsl_spline *linear_interp[3];
518  };
519 
520  SolarModel::SolarModel() {}
521  SolarModel::SolarModel(std::string file)
522  {
523  data = ASCIItableReader(file);
524  int pts = data.getnrow();
525  // Terminate GAMBIT is number of columns is wrong; i.e. the wrong solar model file format.
526  if (data.getncol() != 35)
527  {
528  DarkBit_error().raise(LOCAL_INFO, "ERROR! Solar model file '"+file+"' not compatible with GAMBIT!\n"
529  " See [arXiv:1810.07192] or example file in 'DarkBit/data/' for the correct format.");
530  }
531  data.setcolnames("mass", "radius", "temperature", "rho", "Pressure", "Luminosity", "X_H1", "X_He4", "X_He3", "X_C12", "X_C13", "X_N14", "X_N15", "X_O16", "X_O17", "X_O18", "X_Ne", "X_Na", "X_Mg", "X_Al", "X_Si", "X_P", "X_S", "X_Cl", "X_Ar",
532  "X_K", "X_Ca", "X_Sc", "X_Ti", "X_V", "X_Cr", "X_Mn", "X_Fe", "X_Co", "X_Ni");
533 
534  // Extract the radius from the files (in units of the solar radius).
535  const double* radius = &data["radius"][0];
536  r_lo = data["radius"][0];
537  r_hi = data["radius"][pts-1];
538 
539  // Extract the temperature from the files (has to be converted into keV) & calculate the screening scale kappa_s_squared.
540  // Initialise necessary variables for the screening scale calculation.
541  std::vector<double> temperature;
542  std::vector<double> kappa_s_sq;
543  std::vector<double> w_pl_sq;
544  // Multiplicative factor: (4 pi alpha_EM / atomic_mass_unit) x (1 g/cm^3) in units of keV^3
545  const double factor = 4.0*pi*alpha_EM*gsl_pow_3(1.0E+6*gev2cm)/((1.0E+9*eV2g)*atomic_mass_unit);
546  // Atomic weight of species i (exact weight if isotope is known OR estimate from average solar abundance from data if available OR estimate from natural terrestrial abundance).
547  const double A_vals [29] = {1.007825, 4.002603, 3.016029, 12.000000, 13.003355, 14.003074, 15.000109, 15.994915, 16.999132, 17.999160,
548  20.1312812, 22.989769, 24.3055, 26.9815385, 28.085, 30.973762, 32.0675, 35.4515, 36.275403, 39.0983, 40.078, 44.955908, 47.867, 50.9415, 51.9961, 54.938044, 55.845, 58.933194, 58.6934};
549  // Ionisation of species i assuming full ionisation.
550  const double Z_vals [29] = {1.0, 2.0, 2.0, 6.0, 6.0, 7.0, 7.0, 8.0, 8.0, 8.0,
551  10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0};
552 
553  #ifdef AXION_DEBUG_MODE
554  std::cout << "DEBUGGING INFO for solar models:\nradius/Rsol T/K kappa_s^2/keV^2 omega_pl^2/keV^2" << std::endl;
555  #endif
556 
557  // Linearly extrapolate the data in the solar model file to r = 0 if necessary.
558  if (r_lo > 0)
559  {
560  double r0 = data["radius"][0], r1 = data["radius"][1];
561  double t_intercept = (1.0E-3*K2eV)*(r0*data["temperature"][1]-r1*data["temperature"][0])/(r0-r1);
562  temperature.push_back(t_intercept);
563  double rho_intercept = (r0*data["rho"][1]-r1*data["rho"][0])/(r0-r1);
564  double sum = 0.0;
565  double ne = 0.0;
566  for (int j = 0; j < 29; j++)
567  {
568  double x_intercept = (r0* data[j+6][1]-r1* data[j+6][0])/(r0-r1);
569  double temp = x_intercept*Z_vals[j]/A_vals[j];
570  ne += temp;
571  sum += temp*(1.0 + Z_vals[j]);
572  }
573  double kss = factor*sum*rho_intercept/t_intercept;
574  kappa_s_sq.push_back(kss);
575  double wpls = factor*ne*rho_intercept/(1.0E+6*m_electron);
576  w_pl_sq.push_back(wpls);
577  #ifdef AXION_DEBUG_MODE
578  printf("%5.4f %1.6e %1.6e %1.6e\n", 0.0, t_intercept, kss, wpls);
579  #endif
580  }
581  // Calculate the necessary quantities -- T(r), kappa_s^2(r) and omega_pl^2(r) -- and store them internally.
582  for (int i = 0; i < pts; i++)
583  {
584  double sum = 0.0;
585  double ne = 0.0;
586  temperature.push_back((1.0E-3*K2eV)*data["temperature"][i]);
587  for (int j = 0; j < 29; j++)
588  {
589  double temp = data[j+6][i]*Z_vals[j]/A_vals[j];
590  ne += temp;
591  sum += temp*(1.0 + Z_vals[j]);
592  }
593  double kss = factor*sum*data["rho"][i]/temperature[i];
594  kappa_s_sq.push_back(kss);
595  double wpls = factor*ne*data["rho"][i]/(1.0E+6*m_electron);
596  w_pl_sq.push_back(wpls);
597  #ifdef AXION_DEBUG_MODE
598  printf("%5.4f %1.6e %1.6e %1.6e\n", data["radius"][i], temperature[i], kss, wpls);
599  #endif
600  }
601  // Set up the interpolating functions for temperature and screening scale.
602  accel[0] = gsl_interp_accel_alloc ();
603  linear_interp[0] = gsl_spline_alloc (gsl_interp_linear, pts);
604  const double* temp_vals = &temperature[0];
605  gsl_spline_init (linear_interp[0], radius, temp_vals, pts);
606  accel[1] = gsl_interp_accel_alloc ();
607  linear_interp[1] = gsl_spline_alloc (gsl_interp_linear, pts);
608  const double* kappa_squared_vals = &kappa_s_sq[0];
609  gsl_spline_init (linear_interp[1], radius, kappa_squared_vals, pts);
610  accel[2] = gsl_interp_accel_alloc ();
611  linear_interp[2] = gsl_spline_alloc (gsl_interp_linear, pts);
612  const double* omega_pl_squared_vals = &w_pl_sq[0];
613  gsl_spline_init (linear_interp[2], radius, omega_pl_squared_vals, pts);
614 
615  logger() << LogTags::info << "Initialisation of solar model from file '"+file+"' complete!" << std::endl;
616  logger() << LogTags::debug << "Entries in model file: " << pts << " for solar radius in [" << data["radius"][0] << ", " << data["radius"][pts-1] << "]." << EOM;
617  }
618 
619  // Move assignment operator
620  SolarModel& SolarModel::operator=(SolarModel &&model)
621  {
622  if (this != &model)
623  {
624  std::swap(data,model.data);
625  std::swap(accel,model.accel);
626  std::swap(linear_interp, model.linear_interp);
627  }
628  return *this;
629  }
630 
631  // Class destructor
632  SolarModel::~SolarModel()
633  {
634  for (auto interp : linear_interp)
635  gsl_spline_free (interp);
636  for (auto acc : accel)
637  gsl_interp_accel_free (acc);
638  }
639 
640  // Routine to return the temperature (in keV) of the zone around the distance r from the centre of the Sun.
641  double SolarModel::temperature_in_keV(double r) { return gsl_spline_eval(linear_interp[0], r, accel[0]); }
642 
643  // Routine to return the screening paramter kappa^2 in units of keV^2 (kappa^-1 = Debye-Hueckel radius).
644  double SolarModel::kappa_squared(double r)
645  {
646  // Interpolated value, directly from the Solar model.
647  return gsl_spline_eval(linear_interp[1], r, accel[1]);
648  }
649 
650  // Routine to return the plasma freqeuency squared (in keV^2) of the zone around the distance r from the centre of the Sun.
651  double SolarModel::omega_pl_squared(double r) { return gsl_spline_eval(linear_interp[2], r, accel[2]); }
652 
653  // Constant numbers for precision etc.
654  const double abs_prec = 1.0E-1, rel_prec = 1.0E-6;
655  const int method = 5;
656  // Auxillary structure for passing the model parameters to the gsl solver.
657  struct SolarModel_params1 {double erg; double rad; SolarModel* sol;};
658  struct SolarModel_params2 {double erg; double rs; SolarModel* sol;};
659  struct SolarModel_params3 {double rs; double ma0; SolarModel* sol; AxionInterpolator* eff_exp;};
660  struct SolarModel_params4 {double ma0; AxionInterpolator* eff_exp; AxionInterpolator* gaee_flux;};
661 
662  double rho_integrand (double rho, void * params)
663  {
664  // Retrieve parameters and other integration variables.
665  struct SolarModel_params1 * p1 = (struct SolarModel_params1 *)params;
666  double erg = (p1->erg);
667  double r = (p1->rad);
668  SolarModel* sol = (p1->sol);
669 
670  // Get kappa_s^2, omega_plasma^2 and the temperature.
671  double ks_sq = sol->kappa_squared(rho);
672  double w_pl_sq = sol->omega_pl_squared(rho);
673  double T_in_keV = sol->temperature_in_keV(rho);
674 
675  // Calculate the flux.
676  double x = 4.0*(erg*erg)/ks_sq;
677  double cylinder = rho*rho - r*r;
678  cylinder = rho/sqrt(cylinder);
679  double energy_factor = erg*sqrt(erg*erg - w_pl_sq)/gsl_expm1(erg/T_in_keV);
680  double rate = (ks_sq*T_in_keV)*((1.0 + 1.0/x)*gsl_log1p(x) - 1.0);
681 
682  return cylinder*energy_factor*rate;
683  }
684 
685  double rad_integrand(double rad, void * params)
686  {
687  // Retrieve and pass on parameters.
688  struct SolarModel_params2 * p2 = (struct SolarModel_params2 *)params;
689  SolarModel* sol = (p2->sol);
690  double rmax = std::min(1.0, sol->r_hi);
691  SolarModel_params1 p1 = {p2->erg, rad, sol};
692 
693  gsl_integration_workspace * w = gsl_integration_workspace_alloc(1E6);
694  double result, error;
695 
696  gsl_function F;
697  F.function = &rho_integrand;
698  F.params = &p1;
699 
700  //gsl_set_error_handler_off();
701  gsl_integration_qag (&F, rad, rmax, 1e-2*abs_prec, 1e-2*rel_prec, 1E6, method, w, &result, &error);
702  //printf ("GSL status: %s\n", gsl_strerror (status));
703  //gsl_integration_qags(&F, rad, rmax, 1e-1*abs_prec, 1e-1*rel_prec, 1E6, w, &result, &error);
704  gsl_integration_workspace_free (w);
705 
706  result = rad*result;
707  return result;
708  }
709 
710  double erg_integrand(double erg, void * params)
711  {
712  const double eVm = gev2cm*1E7;
713  const double L = 9.26/eVm;
714  struct SolarModel_params3 * p3 = (struct SolarModel_params3 *)params;
715  SolarModel* sol = p3->sol;
716  double m_ax = p3->ma0;
717  double rs = p3->rs;
718 
719  double argument = 0.25*1.0E-3*L*m_ax*m_ax/erg;
720  double temp = gsl_pow_2(gsl_sf_sinc(argument/pi));
721  double exposure = p3->eff_exp->interpolate(erg);
722  //std::cout << "Energy: " << erg << ", expoure = " << exposure << "." << std::endl;
723  SolarModel_params2 p2 = {erg, rs, sol};
724 
725  gsl_integration_workspace * w = gsl_integration_workspace_alloc(1E6);
726  double result, error;
727 
728  gsl_function F;
729  F.function = &rad_integrand;
730  F.params = &p2;
731 
732  // Max. and min. integration radius
733  double rmin = sol->r_lo, rmax = std::min(rs, sol->r_hi);
734 
735  gsl_integration_qag (&F, rmin, rmax, 1e-1*abs_prec, 1e-1*rel_prec, 1E6, method, w, &result, &error);
736  gsl_integration_workspace_free (w);
737 
738  return temp*exposure*result;
739  }
740 
741  double alt_erg_integrand(double erg, void * params)
742  {
743  const double eVm = gev2cm*1E7;
744  const double L = 9.26/eVm;
745  struct SolarModel_params4 * p4 = (struct SolarModel_params4 *)params;
746  double m_ax = p4->ma0;
747 
748  double argument = 0.25*1.0E-3*L*m_ax*m_ax/erg;
749  double temp = gsl_pow_2(gsl_sf_sinc(argument/pi));
750  double exposure = p4->eff_exp->interpolate(erg);
751  double gaee_flux = p4->gaee_flux->interpolate(erg);
752 
753  return temp*exposure*gaee_flux;
754  }
755 
756  // Provides a customised interpolation container for the CAST likelihoods.
757  class CAST_SolarModel_Interpolator
758  {
759  public:
760  CAST_SolarModel_Interpolator(std::string solar_model_gagg, std::string solar_model_gaee, std::string data_set);
761  CAST_SolarModel_Interpolator(CAST_SolarModel_Interpolator&&);
762  ~CAST_SolarModel_Interpolator();
763  // Delete copy constructor and assignment operator to avoid shallow copies
764  CAST_SolarModel_Interpolator(const CAST_SolarModel_Interpolator&) = delete;
765  CAST_SolarModel_Interpolator operator=(const CAST_SolarModel_Interpolator&) = delete;
766  std::vector<double> evaluate_gagg_contrib(double m_ax);
767  std::vector<double> evaluate_gaee_contrib(double m_ax);
768  private:
769  int n_bins;
770  ASCIItableReader gagg_data;
771  ASCIItableReader gaee_data;
772  ASCIItableReader eff_exp_data;
773  std::vector<gsl_interp_accel*> gagg_acc;
774  std::vector<gsl_interp_accel*> gaee_acc;
775  std::vector<gsl_spline*> gagg_linear_interp;
776  std::vector<gsl_spline*> gaee_linear_interp;
777  };
778 
779  // Class creators for CAST_SolarModel_Interpolator
780  // Needs path to pre-claculated data for the "default" option.
781  CAST_SolarModel_Interpolator::CAST_SolarModel_Interpolator(std::string solar_model_gagg, std::string solar_model_gaee, std::string data_set)
782  {
783  const std::string darkbitdata_path = GAMBIT_DIR "/DarkBit/data/";
784  bool user_gagg_file_missing = true, user_gaee_file_missing = true;
785  logger() << LogTags::info << "Using solar models '"+solar_model_gagg+"' (axion-photon int.) and '"+solar_model_gaee+"' (axion-electron int.) for experiment '"+data_set+"'." << EOM;
786 
787  // Check if a pre-computed a file for a given model exists.
788  user_gagg_file_missing = not(Utils::file_exists(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat"));
789  user_gaee_file_missing = not(Utils::file_exists(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat"));
790  if (not(user_gagg_file_missing)) { logger() << LogTags::info << "Found pre-calculated axion-photon counts file for experiment '"+data_set+"' and solar model '"+solar_model_gagg+"'. Skipping calculation step..." << EOM; }
791  if (not(user_gaee_file_missing)) { logger() << LogTags::info << "Found pre-calculated axion-electron counts file for experiment '"+data_set+"' and solar model '"+solar_model_gaee+"'. Skipping calculation step..." << EOM; }
792 
793  // If either file does not exists, compute it.
794  if (user_gagg_file_missing || user_gaee_file_missing)
795  {
796  // Define the list of logarithmic masses log10(m_ax/keV) for the interpolating tables.
797  const int n_mass_bins = 183;
798  const double log_masses [n_mass_bins] = {-3., -2.8, -2.6, -2.4, -2.2, -2.15, -2.1, -2.05, -2., -1.95, -1.9, -1.85, -1.8475, -1.84, -1.8325, -1.825, -1.8175, -1.81, -1.8025, -1.795, -1.7875, -1.78, -1.7725, -1.765, -1.7575, -1.75,
799  -1.7425, -1.735, -1.7275, -1.72, -1.7125, -1.705, -1.6975, -1.69, -1.6825, -1.675, -1.6675, -1.66, -1.6525, -1.645, -1.6375, -1.63, -1.6225, -1.615, -1.6075, -1.6, -1.5925, -1.585, -1.5775, -1.57,
800  -1.5625, -1.555, -1.5475, -1.54, -1.5325, -1.525, -1.5175, -1.51, -1.5025, -1.495, -1.4875, -1.48, -1.4725, -1.465, -1.4575, -1.45, -1.4425, -1.435, -1.4275, -1.42, -1.4125, -1.405, -1.3975,
801  -1.39, -1.3825, -1.375, -1.3675, -1.36, -1.3525, -1.345, -1.3375, -1.33, -1.3225, -1.315, -1.3075, -1.3, -1.2925, -1.285, -1.2775, -1.27, -1.2625, -1.255, -1.2475, -1.24, -1.2325, -1.225, -1.2175,
802  -1.21, -1.2025, -1.195, -1.1875, -1.18, -1.1725, -1.165, -1.1575, -1.15, -1.1425, -1.135, -1.1275, -1.12, -1.1125, -1.105, -1.0975, -1.09, -1.0825, -1.075, -1.0675, -1.06, -1.0525, -1.045,
803  -1.0375, -1.03, -1.0225, -1.015, -1.0075, -1., -0.9925, -0.985, -0.9775, -0.97, -0.9625, -0.955, -0.9475, -0.94, -0.9325, -0.925, -0.9175, -0.91, -0.9025, -0.895, -0.8875, -0.88, -0.8725, -0.865,
804  -0.8575, -0.85, -0.8425, -0.835, -0.8275, -0.82, -0.8125, -0.805, -0.7975, -0.79, -0.7825, -0.775, -0.7675, -0.76, -0.7525, -0.745, -0.7375, -0.73, -0.7225, -0.715, -0.7075, -0.7, -0.65, -0.6,
805  -0.55, -0.5, -0.4, -0.2, 0., 0.2, 0.4, 0.6, 0.8, 1., 1.2, 1.4, 1.6, 1.8, 2.};
806 
807  // Define quantities specific to CAST and the data set.
808  // prefactor_gagg = (keV/eV)^6 * (1 cm^2/eVcm^2) * (1 day/eVs) * (10^10 cm/eVcm) * (10^-19 eV^-1)^4 * ((9.26 m/eVm) * (9.0 T/(T/eV^2) ))^2 / (128 pi^3)
809  const double prefactor_gagg = 29302.30262;
810  // prefactor_gaee = 10^13 * (10^-19 eV^-1)^2 * ((9.26 m/eVm) * (9.0 T/(T/eV^2) ))^2 / 4
811  // 10^13 = normalisation factor of files
812  const double prefactor_gaee = 1.701818353e-4;
813  // Lowest energy bin (in keV), bin size (in keV), and max. integration radius.
814  double bin_lo = 2.0, bin_delta = 0.5, rs = 1.0;
815  // Number of bins
816  int n_bins = 10;
817  if (data_set=="CAST2007") { bin_lo = 0.8; bin_delta = 0.3; rs = 0.231738; n_bins = 20; }
818 
819  // Arrays to store the results in.
820  double gagg_counts [n_bins*n_mass_bins];
821  double gaee_counts [n_bins*n_mass_bins];
822 
823  // Load the solar model.
824  // Solar radius R_Sol and D_Sol (= 1 a.u.) in 10^10 cm.
825  const double radius_sol = 6.9598, distance_sol = 1495.978707;
826  double temp = prefactor_gagg*gsl_pow_2(radius_sol/distance_sol)*radius_sol;
827 
828  SolarModel model_gagg;
829  if (user_gagg_file_missing)
830  {
831  if (Utils::file_exists(darkbitdata_path+"SolarModel_"+solar_model_gagg+".dat"))
832  {
833  model_gagg = SolarModel(darkbitdata_path+"SolarModel_"+solar_model_gagg+".dat");
834  } else {
835  DarkBit_error().raise(LOCAL_INFO, "ERROR! No solar model file found for '"+solar_model_gagg+"'.\n"
836  " Check 'DarkBit/data' for files named 'SolarModel_*.dat' for available options *.");
837  }
838  }
839 
840  // Load and interpolate effective exposure and the data for the axion-electron spectrum (with its nasty peaks).
841  AxionInterpolator eff_exposure (darkbitdata_path+"CAST/"+data_set+"_EffectiveExposure.dat");
842  AxionInterpolator gaee_spectrum;
843  if (user_gaee_file_missing)
844  {
845  if (Utils::file_exists(darkbitdata_path+"CAST/"+"Axion_Spectrum_"+solar_model_gaee+"_gaee.dat"))
846  {
847  gaee_spectrum = AxionInterpolator(darkbitdata_path+"CAST/"+"Axion_Spectrum_"+solar_model_gaee+"_gaee.dat");
848  } else {
849  DarkBit_error().raise(LOCAL_INFO, "ERROR! No spectrum file found for axion-electron interactions and model '"+solar_model_gaee+"'.\n"
850  " Check 'DarkBit/data' for files named 'Axion_Spectrum_*_gaee.dat' for available options *.");
851  }
852  }
853  double all_peaks [32] = {0.653029, 0.779074, 0.920547, 0.956836, 1.02042, 1.05343, 1.3497, 1.40807, 1.46949, 1.59487, 1.62314, 1.65075, 1.72461, 1.76286, 1.86037, 2.00007, 2.45281, 2.61233, 3.12669, 3.30616, 3.88237, 4.08163, 5.64394,
854  5.76064, 6.14217, 6.19863, 6.58874, 6.63942, 6.66482, 7.68441, 7.74104, 7.76785};
855 
856  // Prepare integration routine by defining the gsl functions etc.
857  gsl_function F;
858  F.function = &erg_integrand;
859  gsl_function G;
860  G.function = &alt_erg_integrand;
861 
862  double erg_lo = bin_lo, erg_hi = bin_lo;
863 
864  logger() << LogTags::info << "Calculating reference counts for dataset '"+data_set+"'..." << EOM;
865  #ifdef AXION_DEBUG_MODE
866  std::cout << "DEBUGGING INFO for solar model integration:\n"
867  "Using model '"+solar_model_gagg+"' for axion-photon interactions,"
868  "and model '"+solar_model_gaee+"' for axion-electron interactions.\n\n"
869  "coupling log10(m/eV) [erg_low/keV, erg_high/keV] log10(counts)" << std::endl;
870  #endif
871  for (int bin = 0; bin < n_bins; bin++)
872  {
873  erg_lo = erg_hi;
874  erg_hi += bin_delta;
875  gsl_integration_workspace * v = gsl_integration_workspace_alloc (1E6);
876  gsl_integration_workspace * w = gsl_integration_workspace_alloc (1E6);
877  // Only take into account the peaks relevant for the current energy bin.
878  std::vector<double> relevant_peaks;
879  relevant_peaks.push_back(erg_lo);
880  for (int i = 0; i < 32; i++)
881  {
882  double temp = all_peaks[i];
883  if ( (erg_lo < temp) && (temp < erg_hi) ) { relevant_peaks.push_back(temp); }
884  }
885  relevant_peaks.push_back(erg_hi);
886 
887  for (int i = 0; i < n_mass_bins; i++)
888  {
889  double gagg_result, gagg_error, gaee_result, gaee_error;
890  double m_ax = pow(10,log_masses[i]);
891  // Only perform integration if axion-photon counts file does not exist.
892  if (user_gagg_file_missing)
893  {
894  SolarModel_params3 p3 = {rs, m_ax, &model_gagg, &eff_exposure};
895  F.params = &p3;
896  gsl_integration_qag (&F, erg_lo, erg_hi, abs_prec, rel_prec, 1E6, method, v, &gagg_result, &gagg_error);
897 
898  #ifdef AXION_OMP_DEBUG_MODE
899  printf("gagg | % 6.4f [%3.2f, %3.2f] % 4.3e\n", log10(m_ax), erg_lo, erg_hi, log10(temp*gagg_result));
900  #endif
901 
902  gagg_counts[bin*n_mass_bins+i] = log10(temp*gagg_result);
903  }
904  // Only perform integration if axion-electron counts file does not exist.
905  if (user_gaee_file_missing)
906  {
907  SolarModel_params4 p4 = {m_ax, &eff_exposure, &gaee_spectrum};
908  G.params = &p4;
909  gsl_integration_qagp(&G, &relevant_peaks[0], relevant_peaks.size(), abs_prec, rel_prec, 1E6, w, &gaee_result, &gaee_error);
910 
911  #ifdef AXION_OMP_DEBUG_MODE
912  printf("gaee | % 6.4f [%3.2f, %3.2f] % 4.3e\n", log10(m_ax), erg_lo, erg_hi, log10(0.826*prefactor_gaee*gaee_result));
913  #endif
914 
915  // Include efficiency factor from not integrating over the full Solar disc in CAST2007 here:
916  if (data_set=="CAST2007") { gaee_result = 0.826*gaee_result; }
917  gaee_counts[bin*n_mass_bins+i] = log10(prefactor_gaee*gaee_result);
918  }
919  }
920  gsl_integration_workspace_free (v);
921  gsl_integration_workspace_free (w);
922  }
923 
924 
925  // Write the results to a file (if the file does not yet exist).
926  if (user_gagg_file_missing)
927  {
928  std::string header = "########################################################################\n"
929  "# Reference Counts for Solar Model "+solar_model_gagg+std::string(std::max(0,36-static_cast<int>(solar_model_gagg.length())),' ')+"#\n"
930  "# Column 1: log10(Axion mass in eV) #\n"
931  "# n>1: log10(Reference photon counts in energy bin n-1) #\n"
932  "########################################################################\n";
933  std::ofstream gagg_file (darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat");
934  gagg_file << header;
935  gagg_file << std::fixed << std::scientific << std::setprecision(7);
936  for (int i = 0; i < n_mass_bins; i++)
937  {
938  gagg_file << log_masses[i];
939  for (int j = 0; j < n_bins; j++) { gagg_file << " " << gagg_counts[j*n_mass_bins+i]; }
940  if (i < n_mass_bins-1) { gagg_file << "\n"; }
941  }
942  gagg_file.close();
943  logger() << LogTags::info << "Output file '"+darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat"+"' written for axion-photon interactions." << EOM;
944  }
945 
946  if (user_gaee_file_missing)
947  {
948  std::string header = "########################################################################\n"
949  "# Reference Counts for Solar Model "+solar_model_gaee+std::string(std::max(0,36-static_cast<int>(solar_model_gaee.length())),' ')+"#\n"
950  "# Column 1: log10(Axion mass in eV) #\n"
951  "# n>1: log10(Reference photon counts in energy bin n-1) #\n"
952  "########################################################################\n";
953  std::ofstream gaee_file (darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat");
954  gaee_file << header;
955  gaee_file << std::fixed << std::scientific << std::setprecision(7);
956  for (int i = 0; i < n_mass_bins; i++)
957  {
958  gaee_file << log_masses[i];
959  for (int j = 0; j < n_bins; j++) { gaee_file << " " << gaee_counts[j*n_mass_bins+i]; }
960  if (i < n_mass_bins-1) { gaee_file << "\n"; }
961  }
962  gaee_file.close();
963  logger() << LogTags::info << "Output file '"+darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gagg.dat"+"' written for axion-electron interactions." << EOM;
964  }
965  }
966 
967  // Read in pre-integrated fluxes for the chosen models.
968  // 0-entry = mass values; remaining entries = counts in bins.
969  gagg_data = ASCIItableReader(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat");
970  gaee_data = ASCIItableReader(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat");
971  n_bins = gagg_data.getncol() - 1;
972 
973  // Point to the address of the first entry of masses.
974  const double* mass_gagg = &gagg_data[0][0];
975  const double* mass_gaee = &gaee_data[0][0];
976 
977  for (int bin = 0; bin < n_bins; bin++)
978  {
979  // Determine the number of interpolated mass values.
980  int gagg_pts = gagg_data[bin+1].size();
981  int gaee_pts = gaee_data[bin+1].size();
982  gagg_acc.push_back( gsl_interp_accel_alloc () );
983  gaee_acc.push_back( gsl_interp_accel_alloc () );
984  gagg_linear_interp.push_back( gsl_spline_alloc (gsl_interp_linear, gagg_pts) );
985  gaee_linear_interp.push_back( gsl_spline_alloc (gsl_interp_linear, gaee_pts) );
986  // Get flux values and initialise splines.
987  const double* flux_gagg = &gagg_data[bin+1][0];
988  const double* flux_gaee = &gaee_data[bin+1][0];
989  gsl_spline_init (gagg_linear_interp[bin], mass_gagg, flux_gagg, gagg_pts);
990  gsl_spline_init (gaee_linear_interp[bin], mass_gaee, flux_gaee, gaee_pts);
991  }
992  }
993 
994  // Move constructor
995  CAST_SolarModel_Interpolator::CAST_SolarModel_Interpolator(CAST_SolarModel_Interpolator &&interp) :
996  n_bins(std::move(interp.n_bins)),
997  gagg_data(std::move(interp.gagg_data)),
998  gaee_data(std::move(interp.gaee_data)),
999  eff_exp_data(std::move(interp.eff_exp_data)),
1000  gagg_acc(std::move(interp.gagg_acc)),
1001  gaee_acc(std::move(interp.gaee_acc)),
1002  gagg_linear_interp(std::move(interp.gagg_linear_interp)),
1003  gaee_linear_interp(std::move(interp.gaee_linear_interp))
1004  {}
1005 
1006  // Class destructor
1007  CAST_SolarModel_Interpolator::~CAST_SolarModel_Interpolator()
1008  {
1009  for(auto gagg_li : gagg_linear_interp)
1010  gsl_spline_free (gagg_li);
1011  for(auto gagg_ac : gagg_acc)
1012  gsl_interp_accel_free (gagg_ac);
1013  for(auto gaee_li : gaee_linear_interp)
1014  gsl_spline_free (gaee_li);
1015  for(auto gaee_ac : gaee_acc)
1016  gsl_interp_accel_free (gaee_ac);
1017 
1018  }
1019 
1020  // Returns reference value counts for the photon-axion contribution.
1021  std::vector<double> CAST_SolarModel_Interpolator::evaluate_gagg_contrib(double m_ax)
1022  {
1023  std::vector<double> result;
1024  double lgm = log10(m_ax);
1025  // If m < 0.001 eV, just return the result for the result for the coherent limit.
1026  lgm = std::max(-3.0, lgm);
1027  // Only perform a calculation for valid masses.
1028  if (lgm < 2.0)
1029  {
1030  for (int i = 0; i < n_bins; i++) { result.push_back(gsl_spline_eval(gagg_linear_interp[i], lgm, gagg_acc[i])); }
1031  } else {
1032  for (int i = 0; i < n_bins; i++) { result.push_back(0.0); }
1033  }
1034 
1035  return result;
1036  }
1037 
1038  // Returns reference value counts for the photon-axion contribution.
1039  std::vector<double> CAST_SolarModel_Interpolator::evaluate_gaee_contrib(double m_ax)
1040  {
1041  std::vector<double> result;
1042  double lgm = log10(m_ax);
1043  // If m < 0.001 eV, just return the result for the result for the coherent limit.
1044  lgm = std::max(-3.0, lgm);
1045  // Only perform a calculation for valid masses.
1046  if (lgm < 2.0)
1047  {
1048  for (int i = 0; i < n_bins; i++) { result.push_back(gsl_spline_eval(gaee_linear_interp[i], lgm, gaee_acc[i])); }
1049  } else {
1050  for (int i = 0; i < n_bins; i++) { result.push_back(0.0); }
1051  }
1052 
1053  return result;
1054  }
1055 
1056  // Use simplified version of Gaussian likelihood from GAMBIT Utils.
1057  double gaussian_nuisance_lnL(double theo, double obs, double sigma) { return Stats::gaussian_loglikelihood(theo, obs, 0, sigma, false); }
1058 
1060  // //
1061  // Miscellaneous Theory Results //
1062  // //
1064 
1070  // Effective relatvistic degrees of freedom //
1073 
1074  // Function to provide the effective relativistic degrees of freedom (for the Standard Model).
1075  double gStar(double T)
1076  {
1077  // Needs log10(T/GeV) for interpolation.
1078  double lgT = log10(T) - 3.0;
1079  // Interpolated effective relatvistic d.o.f. based on 0910.1066, deviations < 0.5%
1080  // Tabulated data: x = log10(T/GeV), y = gStar
1081  static AxionInterpolator gR (GAMBIT_DIR "/DarkBit/data/gR_WantzShellard.dat", InterpolationOptions1D::cspline);
1082  double res;
1083  if (lgT > 3.0) {
1084  res = gR.interpolate (2.99);
1085  } else if (lgT < -5.0) {
1086  res = gR.interpolate (-4.99);
1087  } else {
1088  res = gR.interpolate (lgT);
1089  }
1090 
1091  return res;
1092  }
1093 
1094  // Function to provide the effective relativistic entropic degrees of freedom (for the Standard Model).
1095  double gStar_S(double T)
1096  {
1097  // Need log10(T/GeV) for interpolation.
1098  double lgT = log10(T) - 3.0;
1099  // Interpolated effective relatvistic d.o.f. based on 0910.1066, deviations < 0.5%
1100  // Tabulated data: x = log10(T/GeV), y = gStar
1101  static AxionInterpolator gS (GAMBIT_DIR "/DarkBit/data/gS_WantzShellard.dat", InterpolationOptions1D::cspline);
1102  double res;
1103  if (lgT > 3.0) {
1104  res = gS.interpolate (2.99);
1105  } else if (lgT < -5.0) {
1106  res = gS.interpolate (-4.99);
1107  } else {
1108  res = gS.interpolate (lgT);
1109  }
1110 
1111  return res;
1112  }
1113 
1115  // QCD-axion mass relation //
1117 
1118  // Capability function to provide a simple Gaussian nuisance likelihood for
1119  // the zero-termperature mass of QCD axions.
1121  {
1123  double LambdaChi = *Param["LambdaChi"];
1124 
1125  // Results from NLO calculations (1511.02867).
1126  const double Lmu = 75.5;
1127  const double Lsigma = 0.5;
1128 
1129  result = gaussian_nuisance_lnL(Lmu, LambdaChi, Lsigma);
1130  }
1131 
1132  // Capability function to provide a simple Gaussian nuisance likelihood for
1133  // the model-independent contribution to the axion-photon coupling for QCD axions.
1135  {
1137  double CaggQCD = *Param["CaggQCD"];
1138 
1139  // Results from NLO calculations (1511.02867).
1140  const double CaggQCDmu = 1.92;
1141  const double CaggQCDsigma = 0.04;
1142 
1143  result = gaussian_nuisance_lnL(CaggQCDmu, CaggQCD, CaggQCDsigma);
1144  }
1145 
1146  // Auxillary function for QCD nuisance likelihood below.
1147  double log_chi (double T, double beta, double Tchi)
1148  {
1149  double result = 0.0;
1150  if (T > Tchi) { result = -beta*log10(T/Tchi); }
1151 
1152  return result;
1153  }
1154 
1155  // Capability function to provide a lieklihood for the temperature dependence of the QCD axion mass (doi:10.1038/nature20115).
1157  {
1159  double Tchi = *Param["Tchi"];
1160  double beta = *Param["beta"];
1161 
1162  // Results from lattice QCD (doi:10.1038/nature20115, Supplementary Material).
1163  // We normalised their findings by dividing out their value for chi(T=0) and removed its contribution to the error.
1164  const double temp_vals [20] = {100, 120, 140, 170, 200, 240, 290, 350, 420, 500, 600, 720, 860, 1000, 1200, 1500, 1800, 2100, 2500, 3000};
1165  const double log_chi_vals [20] = {0.00554625, 0.0255462, -0.0844538, -0.484454, -1.00445, -1.75445, -2.45445, -3.07445, -3.66445, -4.22445, -4.80445, -5.39445, -5.96445, -6.45445, -7.05445, -7.79445, -8.40445, -8.93445, -9.53445, -10.1545};
1166  const double log_chi_err_vals [20] = {0.014468, 0.0361846, 0.014468, 0.014468, 0.064104, 0.064104, 0.0510815, 0.0361846, 0.0510815, 0.064104, 0.0878027, 0.110042, 0.142159, 0.163124, 0.183873, 0.224965, 0.255557, 0.286023, 0.316401, 0.356804};
1167 
1168  double dummy = 0.0;
1169  for (int i = 0; i < 20; i++) { dummy = dummy + gaussian_nuisance_lnL(log_chi_vals[i], log_chi(temp_vals[i],beta,Tchi), log_chi_err_vals[i]); }
1170 
1171  result = dummy;
1172  }
1173 
1175  // //
1176  // Axion Experiments //
1177  // //
1179 
1185  // ALPS 1 experiment //
1188 
1189  // Generic functions to calculate the expected signal per frame(!) for any data run.
1190  // Input: laser power, gas coefficient nm1 = n-1; result in no. of photons.
1191  double ALPS1_signal_general(double power, double nm1, double m_ax, double gagg)
1192  {
1193  const double eVm = gev2cm*1E7;
1194  // Photon energy in eV.
1195  const double erg = 2.0*pi*eVm/532.0E-9;
1196  const double len = 4.2;
1197  // We include the uncertainty of the detection efficiency eff = 0.82(5) in the likelihood.
1198  const double eff = 0.82;
1199 
1200  double result = 0.0;
1201 
1202  // CAVE: Approximations/conversion only valid/possible for m_ax << 2.33 eV (532 nm).
1203  if (m_ax < 1.0)
1204  {
1205  // Effective photon mass and momentum transfer.
1206  double m_ph_sq = 2.0*erg*erg*nm1;
1207  double q = 0.5*(m_ax*m_ax+m_ph_sq)/(eVm*erg);
1208  double factor = gsl_pow_4((gagg*1E17)*gsl_sf_sinc(0.5*q*len/pi));
1209 
1210  // Prefactor: 1096 W * 1 h * (10^-17/eV * 4.98 T * 4.2 m)^4 / 16.
1211  result = 0.00282962979*eff*factor*(power/1096.0)/erg;
1212  }
1213 
1214  return result;
1215  }
1216 
1217  // Specific capability to provide the expected signal from data run 1 (5 data frames).
1218  void calc_ALPS1_signal_vac(double &result)
1219  {
1220  using namespace Pipes::calc_ALPS1_signal_vac;
1221  double m_ax = *Param["ma0"];
1222  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
1223 
1224  result = ALPS1_signal_general(1096.0, 0.0, m_ax, gagg);
1225  }
1226 
1227  // Specific capability to provide the expected signal from data run 3 (8 data frames; 0.18 mbar).
1228  void calc_ALPS1_signal_gas(double &result)
1229  {
1230  using namespace Pipes::calc_ALPS1_signal_gas;
1231  double m_ax = *Param["ma0"];
1232  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
1233 
1234  result = ALPS1_signal_general(1044.0, 5.0E-8, m_ax, gagg);
1235  }
1236 
1237  // General likelihood function for the ALPS 1 experiment (given expected signal s = obs - bkg).
1238  double ALPS1_lnL_general(double s, double mu, double sigma)
1239  {
1240  // Propagate uncertainty from efficiency in chi^2-likelihood.
1241  const double rel_error = 0.05/0.82;
1242  return -0.5*gsl_pow_2(s-mu)/(gsl_pow_2(rel_error*s)+gsl_pow_2(sigma));
1243  }
1244 
1245  // Capability to provide joint liklihood for all three data runs.
1246  void calc_lnL_ALPS1(double &result)
1247  {
1248  using namespace Pipes::calc_lnL_ALPS1;
1249  double s1 = *Dep::ALPS1_signal_vac;
1250  double s2 = *Dep::ALPS1_signal_gas;
1251 
1252  // ALPS Collaboration results (limits from this data published in 1004.1313).
1253  // ALPS Collaboration results, vacuum, 5 frames.
1254  const double exp_sig_mu_v1 = -4.01, exp_sig_std_v1 = 3.01;
1255  // ALPS Collaboration results, vacuum, 6 frames.
1256  const double exp_sig_mu_v2 = -2.35, exp_sig_std_v2 = 3.44;
1257  // ALPS Collaboration results, vacuum combined(!), 11 frames (we keep them seperated).
1258  //const double exp_sig_mu_vc = -3.29, exp_sig_std_vc = 2.27;
1259  // ALPS Collaboration results, gas, 8 frames (P = 0.18 mbar).
1260  const double exp_sig_mu_g = 3.98, exp_sig_std_g = 2.45;
1261 
1262  double l1 = ALPS1_lnL_general(s1, exp_sig_mu_v1, exp_sig_std_v1);
1263  double l2 = ALPS1_lnL_general(s1, exp_sig_mu_v2, exp_sig_std_v2);
1264  double l3 = ALPS1_lnL_general(s2, exp_sig_mu_g, exp_sig_std_g);
1265 
1266  result = l1 + l2 + l3;
1267  }
1268 
1270  // CAST experiment (vacuum runs only) //
1272 
1273  // Calculates the signal prediction for the CAST experiment (CCD detector 2004).
1274  void calc_CAST2007_signal_vac(std::vector<double> &result)
1275  {
1276  using namespace Pipes::calc_CAST2007_signal_vac;
1277  double m_ax = *Param["ma0"];
1278  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
1279  double gaee = std::fabs(*Param["gaee"]);
1280 
1281  // Initialise the Solar model calculator and get the reference counts for a given mass.
1282  // Get Solar model we are working with; set default value here
1283  static std::string solar_model_gagg = runOptions->getValueOrDef<std::string> ("AGSS09met", "solar_model_gagg");
1284  static std::string solar_model_gaee = runOptions->getValueOrDef<std::string> ("AGSS09met_old", "solar_model_gaee");
1285  static CAST_SolarModel_Interpolator lg_ref_counts (solar_model_gagg, solar_model_gaee, "CAST2007");
1286  std::vector<double> lg_ref_counts_gagg = lg_ref_counts.evaluate_gagg_contrib(m_ax);
1287  std::vector<double> lg_ref_counts_gaee = lg_ref_counts.evaluate_gaee_contrib(m_ax);
1288  static int n_bins = lg_ref_counts_gagg.size();
1289 
1290  std::vector<double> counts;
1291  double dummy;
1292  for (int i = 0; i < n_bins; i++)
1293  {
1294  dummy = gsl_pow_2(gagg*1E19)*pow(10,lg_ref_counts_gagg[i]) + gsl_pow_2(gaee*gaee_conversion)*pow(10,lg_ref_counts_gaee[i]);
1295  counts.push_back(gsl_pow_2(gagg*1E19)*dummy);
1296  }
1297 
1298  result = counts;
1299  }
1300 
1301  // Calculates the signal prediction for the CAST experiment (all detectors in 1705.02290)
1302  void calc_CAST2017_signal_vac(std::vector<std::vector<double>> &result)
1303  {
1304  using namespace Pipes::calc_CAST2017_signal_vac;
1305  double m_ax = *Param["ma0"];
1306  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
1307  double gaee = std::fabs(*Param["gaee"]);
1308  std::vector<std::vector<double>> res;
1309 
1310  // Initialise the Solar model calculator and get the reference counts for a given mass.
1311  // Get Solar model we are working with; set default value here
1312  static std::string solar_model_gagg = runOptions->getValueOrDef<std::string> ("AGSS09met", "solar_model_gagg");
1313  static std::string solar_model_gaee = runOptions->getValueOrDef<std::string> ("AGSS09met_old", "solar_model_gaee");
1314 
1315  const int n_exps = 12;
1316  const int n_bins = 10;
1317  const std::string exp_names [n_exps] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"};
1318  static std::vector<CAST_SolarModel_Interpolator> lg_ref_counts;
1319  static bool lg_ref_counts_not_calculated = true;
1320  if (lg_ref_counts_not_calculated)
1321  {
1322  for (int e = 0; e < n_exps; e++)
1323  {
1324  CAST_SolarModel_Interpolator dummy (solar_model_gagg, solar_model_gaee, "CAST2017_"+exp_names[e]);
1325  lg_ref_counts.push_back(std::move(dummy));
1326  }
1327  }
1328  lg_ref_counts_not_calculated = false;
1329 
1330  for (int e = 0; e < n_exps; e++)
1331  {
1332  std::vector<double> lg_ref_counts_gagg = lg_ref_counts[e].evaluate_gagg_contrib(m_ax);
1333  std::vector<double> lg_ref_counts_gaee = lg_ref_counts[e].evaluate_gaee_contrib(m_ax);
1334 
1335  std::vector<double> counts;
1336  double dummy;
1337  for (int bin = 0; bin < n_bins; bin++)
1338  {
1339  dummy = gsl_pow_2(gagg*1E19)*pow(10,lg_ref_counts_gagg[bin]) + gsl_pow_2(gaee*gaee_conversion)*pow(10,lg_ref_counts_gaee[bin]);
1340  counts.push_back(gsl_pow_2(gagg*1E19)*dummy);
1341  }
1342 
1343  res.push_back(counts);
1344  }
1345 
1346  result = res;
1347  }
1348 
1349  // General binned Poisson likelihood for the CAST experiment.
1350  double CAST_lnL_general(std::vector<double> s, const std::vector<double> bkg_counts, const std::vector<int> sig_counts)
1351  {
1352  double result = 0.0;
1353  int n_bins = s.size();
1354 
1355  for (int i = 0; i < n_bins; i++)
1356  {
1357  double mu = s[i] + bkg_counts[i];
1358  result += sig_counts[i]*gsl_sf_log(mu) - mu;
1359  }
1360 
1361  return result;
1362  }
1363 
1364  // Capability to provide CAST likelihood for hep-ex/0702006 .
1365  void calc_lnL_CAST2007(double &result)
1366  {
1367  using namespace Pipes::calc_lnL_CAST2007;
1368  std::vector<double> sig_vac = *Dep::CAST2007_signal_vac;
1369 
1370  // CAST CCD 2004 vacuum data (based on hep-ex/0702006).
1371  const int n_bins = 20;
1372  const std::vector<int> dat_vac {1, 3, 1, 1, 1, 2, 1, 2, 0, 2, 0, 1, 0, 2, 2, 0, 2, 1, 2, 2};
1373  const std::vector<double> bkg_vac {2.286801272, 1.559182673, 2.390746817, 1.559182673, 2.598637835, 1.039455092, 0.727618599, 1.559182673, 1.247346181, 1.455237199, 1.871019235, 0.831564073, 1.663128217, 1.247346181, 1.143400636, 1.663128217,
1374  1.247346181, 1.247346181, 2.286801272, 1.247346181};
1375 
1376  // Only calculate norm once.
1377  static double norm = 0.0;
1378  static bool norm_not_calculated = true;
1379  if (norm_not_calculated)
1380  {
1381  for (int i = 0; i < n_bins; i++) { norm += gsl_sf_lnfact(dat_vac[i]); }
1382  }
1383  norm_not_calculated = false;
1384 
1385  result = CAST_lnL_general(sig_vac, bkg_vac, dat_vac) - norm;
1386  }
1387 
1388  // Capability to provide CAST likelihood for 1705.02290 .
1389  void calc_lnL_CAST2017(double &result)
1390  {
1391  using namespace Pipes::calc_lnL_CAST2017;
1392  std::vector<std::vector<double>> sig_vac = *Dep::CAST2017_signal_vac;
1393 
1394  // CAST 2017 vacuum data (naming scheme based on the 10 data sets published in 1705.02290).
1395  const int n_bins = 10;
1396  const int n_exps = 12;
1397 
1398  const std::vector<std::vector<int>> dat_vac_all { {0, 3, 3, 0, 0, 1, 3, 3, 3, 3},
1399  {5, 5, 5, 3, 3, 0, 5, 2, 2, 1},
1400  {3, 3, 1, 2, 2, 2, 4, 5, 4, 3},
1401  {1, 5, 5, 2, 1, 2, 2, 5, 4, 0},
1402  {2, 3, 2, 2, 2, 1, 0, 2, 1, 1},
1403  {3, 5, 1, 4, 1, 2, 0, 3, 2, 2},
1404  {3, 4, 4, 5, 1, 2, 3, 2, 3, 2},
1405  {2, 1, 0, 1, 3, 2, 2, 3, 0, 1},
1406  {1, 2, 2, 1, 3, 0, 0, 1, 4, 0},
1407  {2, 1, 3, 1, 1, 0, 1, 1, 5, 5},
1408  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
1409  {0, 2, 1, 0, 0, 0, 0, 0, 0, 0} };
1410 
1411  const std::vector<std::vector<double>> bkg_vac_all { {0.926256, 1.96148, 1.79803, 1.30766, 1.30766, 1.96148, 2.61531, 2.77877, 2.94223, 2.07045},
1412  {3.68151, 4.86486, 4.99634, 3.55003, 2.49817, 3.28707, 2.89262, 3.68151, 3.48429, 3.41855},
1413  {2.54573, 3.18216, 4.45502, 2.86394, 2.29116, 2.29116, 3.30945, 3.75495, 3.62766, 3.56402},
1414  {2.72482, 5.5794, 3.95748, 2.40044, 2.27069, 2.33556, 3.37359, 3.43847, 3.24384, 3.11408},
1415  {1.44613, 2.30066, 2.43213, 1.70906, 1.97199, 1.24893, 1.24893, 2.23493, 2.16919, 2.23493},
1416  {1.30963, 2.94666, 2.35733, 2.55377, 2.02992, 1.50607, 2.16088, 2.75022, 2.29185, 2.29185},
1417  {2.33334, 2.74167, 2.21667, 2.80001, 2.21667, 1.75001, 2.62501, 2.21667, 2.80001, 2.33334},
1418  {1.74724, 2.37125, 2.68326, 1.62243, 2.05924, 1.74724, 1.49763, 1.74724, 1.18563, 2.24645},
1419  {1.72998, 3.45995, 1.79405, 1.72998, 1.9222, 1.72998, 2.69107, 2.24256, 1.98627, 2.11442},
1420  {1.89627, 2.25182, 2.96292, 1.4222, 1.65924, 1.65924, 1.95553, 2.1333, 1.71849, 2.07404},
1421  {0.0150685, 0.0568493, 0.060274, 0.0150685, 0.0150685, 0.00753425, 0.0267123, 0.0150685, 0.0267123, 0.0116438},
1422  {0.0409574, 0.226904, 0.243287, 0.0532447, 0.0188404, 0.0344043, 0.0417766, 0.0409574, 0.0409574, 0.0286702} };
1423 
1424  // Only calculate norm once.
1425  static double norm = 0.0;
1426  static bool norm_not_calculated = true;
1427  if (norm_not_calculated)
1428  {
1429  for (int bin = 0; bin < n_bins; bin++)
1430  {
1431  for (int e = 0; e < n_exps; e++) { norm += gsl_sf_lnfact(dat_vac_all[e][bin]); }
1432  }
1433  }
1434  norm_not_calculated = false;
1435 
1436  result = 0.0;
1437  for (int e = 0; e < n_exps; e++) { result = result + CAST_lnL_general(sig_vac[e], bkg_vac_all[e], dat_vac_all[e]); }
1438  result = result - norm;
1439  }
1440 
1442  // Various haloscope experiments //
1444 
1445  // Capability to provide generic haloscope "signal" prediction.
1446  // All current haloscope likelihoods are approximated. We only need the predicted signal power up to a constant of proportionality.
1447  void calc_Haloscope_signal(double &result)
1448  {
1449  using namespace Pipes::calc_Haloscope_signal;
1450  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
1451  // Get the DM fraction in axions and the local DM density.
1452  double fraction = *Dep::RD_fraction;
1453  LocalMaxwellianHalo LocalHaloParameters = *Dep::LocalHalo;
1454  double rho0 = LocalHaloParameters.rho0;
1455 
1456  // Signal relative to a reference coupling and local DM density.
1457  double s = gsl_pow_2(gagg/1.0E-24) * fraction * (rho0/0.45);
1458 
1459  result = s;
1460  }
1461 
1465  // ADMX approximated likelihood function for data from publications from 1998 to 2009.
1466  void calc_lnL_Haloscope_ADMX1(double &result)
1467  {
1468  using namespace Pipes::calc_lnL_Haloscope_ADMX1;
1469  double m_ax = *Param["ma0"];
1470  // Calculate equivalent frequency in MHz.
1471  const double eV_to_MHz = 1.0E-15/(2.0*pi*hbar);
1472  double freq = m_ax*eV_to_MHz;
1473  double l = 0.0;
1474  // Initialise GSL histogram and flag.
1475  static gsl_histogram *h = gsl_histogram_alloc(89);
1476  static bool init_flag = false;
1477 
1478  // Unless initialised already, read in digitised limits from 0910.5914.
1479  if (not(init_flag))
1480  {
1481  FILE * f = fopen(GAMBIT_DIR "/DarkBit/data/ADMXLimitsHistogram.dat", "r");
1482  gsl_histogram_fscanf (f, h);
1483  fclose(f);
1484  init_flag = true;
1485  }
1486 
1487  // Likelihood shape parameters based on limits from astro-ph/9801286.
1488  const double a = 0.013060890;
1489  const double b = 0.455482976;
1490 
1491  if ((freq > gsl_histogram_min(h)) && (freq < gsl_histogram_max(h)))
1492  {
1493  size_t index;
1494  gsl_histogram_find(h, freq, &index);
1495  double s = *Dep::Haloscope_signal;
1496  double s_ref = gsl_pow_2(gsl_histogram_get(h, index));
1497  double s_rel = s/s_ref;
1498  // Only apply contraints for a signal > threshold a.
1499  if (s_rel > a) { l = -0.5 * gsl_pow_2( (s_rel - a)/b ); }
1500  }
1501 
1502  result = l;
1503  }
1504 
1505  // ADMX approximated likelihood function for data from 2018 paper (1804.05750).
1506  void calc_lnL_Haloscope_ADMX2(double &result)
1507  {
1508  using namespace Pipes::calc_lnL_Haloscope_ADMX2;
1509  // Rescale the axion mass to ueV.
1510  double m_ax = 1.0E+6*(*Param["ma0"]);
1511  double l = 0.0;
1512 
1513  // ADMX 2018 90% C.L. exclusion limits; digitised from Fig. 4, 1804.05750.
1514  static AxionInterpolator g_limits (GAMBIT_DIR "/DarkBit/data/ADMX2018Limits.dat");
1515 
1516  // If we are within the avialable data range, calculate the limit.
1517  if ( (m_ax > g_limits.lower()) && (m_ax < g_limits.upper()) )
1518  {
1519  double s = *Dep::Haloscope_signal;
1520  // Get limit and rescale it to 1 sigma from the appropriate number of sigmas for 90% C.L. (1 d.o.f.).
1521  double sigma_exp = gsl_pow_2(g_limits.interpolate(m_ax))/1.644817912489;
1522  // Add systematics of 13% according to 1804.05750.
1523  double var_exp = gsl_pow_2(sigma_exp);
1524  double var_theo = gsl_pow_2(0.13*s);
1525  double var_tot = var_exp + var_theo;
1526  l = -0.5*gsl_pow_2(s)/var_tot;
1527  }
1528 
1529  result = l;
1530  }
1531 
1532  // University of Florida (UF) approximated likelihood function; Hagmann+ Phys. Rev. D 42, 1297(R) (1990).
1533  void calc_lnL_Haloscope_UF(double &result)
1534  {
1535  using namespace Pipes::calc_lnL_Haloscope_UF;
1536  // Rescale the axion mass to ueV.
1537  double m = 1.0E+6*(*Param["ma0"]);
1538  double l = 0.0;
1539 
1540  // There are only limits between 5.4 and 5.9 ueV.
1541  if ((m > 5.4) && (m < 5.9)) {
1542  // Likelihood parameters based on information from Phys. Rev. D 42, 1297(R) (1990); correspond to power in 10^-22 W.
1543  const double sigma = 2.859772;
1544  // The "signal" needs to be rescaled to 0.2804 GeV/cm^3 (their reference value)
1545  // and also to the reference DFSZ coupling strength gDFSZ^2 = 0.6188 x 10^-30 GeV^-2
1546  // const double PowerDFSZ = 6.92;
1547  //double s = (0.45/0.2804)*(PowerDFSZ/0.6188)*(*Dep::Haloscope_signal);
1548  //double s = 0.035083106*(0.45/0.2804)*(*Dep::Haloscope_signal);
1549  double s = 0.0273012*(*Dep::Haloscope_signal);
1550  l = -0.5 * gsl_pow_2(s/sigma);
1551  }
1552 
1553  result = l;
1554  }
1555 
1556  // Rochester-Brookhaven-Fermi (RBF) approximated likelihood function; Phys. Rev. D 40, 3153 (1989).
1557  void calc_lnL_Haloscope_RBF(double &result)
1558  {
1559  using namespace Pipes::calc_lnL_Haloscope_RBF;
1560  // Rescale the axion mass to ueV.
1561  double m = 1.0E+6*(*Param["ma0"]);
1562  double l = 0.0;
1563  // Results from Phys. Rev. D 40, 3153 (1989)
1564  // The bins and results below (from Table I) appear to be inconsitent with Figure 14.
1565  //const std::vector<double> bins = {4.507875, 5.037241, 5.459079, 5.851967, 5.996715, 6.257262, 6.617065, 6.976868, 7.113345, 7.295314, 7.857765, 8.631134, 8.684898, 9.259755, 9.760171, 10.173737,
1566  // 11.298638, 11.583999, 12.845377, 13.234130, 15.301962, 16.2655809};
1567  // N_sigma values as defined in the paper.
1568  //const double N_sigma [21] = {5.0, 5.0, 5.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 0.0, 5.0, 4.0, 4.0, 4.0, 4.0};
1569  // Proportionality factors ("sigma") inferred from Table I in in units of GeV^-1/cm^3.
1570  //const double eff_sigma [21] = {8.3030524510E+01, 3.5789241789E+02, 5.3457189090E+02, 8.3673921774E+02, 7.3205267295E+02, 7.1850356207E+02, 7.0099211538E+02, 9.3243407987E+02, 1.3132694610E+03, 1.9447760075E+03, 2.4028734743E+03,
1571  // 3.5992849457E+03, 5.8433323192E+03, 1.2415907565E+03, 1.1487509033E+03, 1.0000000000E+99, 2.6768234439E+03, 9.1546564260E+04, 1.7208310692E+04, 4.2462784870E+04, 2.8794160844E+04};
1572  // The results below are derived from Fig. 14 (assuming N_sigma = 4 for all values).
1573  const std::vector<double> bins = {4.400841, 4.960600, 5.209095, 5.668611, 6.934590, 7.445686, 8.041207, 8.898392, 9.570607, 10.067396, 11.213613, 11.626834, 12.773085, 13.450179, 14.704884, 16.170394};
1574  const double N_sigma = 4.0;
1575  const double eff_sigma [15] = {7.794388E+01, 3.808827E+02, 5.328136E+02, 6.765588E+02, 1.772892E+03, 2.752458E+03, 5.945156E+03, 2.025315E+03, 1.546855E+03, 1.022957E+13, 5.464075E+03, 9.621171E+04, 2.023187E+04, 5.201449E+04, 3.597168E+04};
1576 
1577  // Likelihood shape parameters based on information from PRD 40 (1989) 3153.
1578  // Note that there are no limits between 10.1 and 11.2 ueV; the tabulated value in that bin is just a placeholder, which is never used.
1579  if ( ((m > bins.front()) && (m < 10.067396)) || ((m > 11.213613) && (m < bins.back())))
1580  {
1581  // Use the standard search algorthim to identify the bin index and use the appropriate values for the likelihood.
1582  auto index = upper_bound(bins.begin(), bins.end(), m);
1583  double sigma = eff_sigma[index-bins.begin()-1];
1584  // Uncomment and comment out lines below to swap between implementations using Table I and Figure 14, respectively.
1585  //double offset = sigma*N_sigma[index-bins.begin()-1];
1586  double offset = sigma*N_sigma;
1587  // The "signal" needs to be rescaled to 0.3 GeV/cm^3, which is their reference value.
1588  double s = (0.45/0.3)*(*Dep::Haloscope_signal);
1589  if (s > offset) {
1590  l = -0.5 * gsl_pow_2( (s - offset)/sigma );
1591  }
1592  }
1593 
1594  result = l;
1595  }
1596 
1598  // //
1599  // Axion Cosmology //
1600  // //
1602 
1608  // Energy density in realignment axions today //
1611 
1612  /* Some auxillary functions for solving the necessary differential equations
1613  */
1614 
1615  // Provides function F1 for the change in variables time -> temperature (see 1810.07192).
1616  double SpecialFun1(double T)
1617  {
1618  // log10(T/GeV) required for interpolation.
1619  double lgT = log10(T) - 3.0;
1620  // Tabulated data: x = log10(T/GeV), y = F1(T); gR and gS from 0910.1066 .
1621  static AxionInterpolator F1 (GAMBIT_DIR "/DarkBit/data/Axion_DiffEqnFun1.dat", InterpolationOptions1D::linear);
1622  double res = -1.0;
1623  if ((lgT > 3.0) && (lgT < -5.0)) { res = F1.interpolate (lgT); }
1624  return res;
1625  }
1626 
1627  // Provides function F3 for the change in variables time -> temperature (see 1810.07192).
1628  double SpecialFun3(double T)
1629  {
1630  // log10(T/GeV) required for interpolation.
1631  double lgT = log10(T) - 3.0;
1632  // Tabulated data: x = log10(T/GeV), y = F3(T); gR and gS from 0910.1066 .
1633  static AxionInterpolator F3 (GAMBIT_DIR "/DarkBit/data/Axion_DiffEqnFun3.dat", InterpolationOptions1D::linear);
1634  double res = 0.0;
1635  if ((lgT > 3.0) && (lgT < -5.0)) { res = F3.interpolate (lgT); }
1636  return res;
1637  }
1638 
1639  // Auxillary function to calculate the Hubble parameter in a radiation-dominated universe.
1640  double hubble_rad_dom(double T)
1641  {
1642  // H(T)/eV, T/MeV, m_pl/10^12eV = m_pl/10^3 GeV
1643  const double m_pl = m_planck_red*1.0E-3;
1644  return 0.331153*sqrt(gStar(T))*T*T/m_pl;
1645  }
1646 
1647  // General form of the temperature-dependent axion mass.
1648  double axion_mass_temp(double T, double beta, double Tchi)
1649  {
1650  double res = 1.0;
1651  if (T > Tchi) { res = pow(T/Tchi,-0.5*beta); }
1652  return res;
1653  }
1654 
1655  // Auxillary structure for passing the model parameters to the gsl solver.
1656  struct AxionEDT_params {double ma0; double beta; double Tchi; double thetai; double Tosc;};
1657 
1658  // Auxillary function with root Tosc, the temperature where the axion field oscillations start (defined by mA = 3H).
1659  // Note that this is only to set the temperature scale of the problem. The differential equation is solved numerically around
1660  // this point and the numerical factor in the definition is pure convention.
1661  double equation_Tosc(double T, void *params)
1662  {
1663  // T/MeV, ma0/eV, m_pl/10^12eV = m_pl/10^3 GeV
1664  const double m_pl = m_planck_red*1.0E-3;
1665  struct AxionEDT_params * p1 = (struct AxionEDT_params *)params;
1666  double ma0 = (p1->ma0);
1667  double beta = (p1->beta);
1668  double Tchi = (p1->Tchi);
1669 
1670  double result = 1.0 - gStar(T)*gsl_pow_2(T*T*pi/(ma0*m_pl*axion_mass_temp(T, beta, Tchi)))/10.0;
1671 
1672  return result;
1673  }
1674 
1675  // Capability function to solve equation_Tosc for Tosc.
1677  {
1679 
1680  double ma0 = *Param["ma0"];
1681  double beta = *Param["beta"];
1682  double Tchi = *Param["Tchi"];
1683  // m_pl/10^12 eV = m_pl/10^3 GeV
1684  const double m_pl = m_planck_red*1.0E-3;
1685 
1686  // Initialising the parameter structure with known and dummy values.
1687  AxionEDT_params params = {ma0, beta, Tchi, 0.0, 0.0};
1688 
1689  // Use gsl implementation Brent's method to determine the oscillation temperature.
1690  gsl_function F;
1691  F.function = &equation_Tosc;
1692  F.params = &params;
1693 
1694  // Set counters and initialise equation solver.
1695  int status;
1696  int iter = 0, max_iter = 1E6;
1697  gsl_root_fsolver *s;
1698  s = gsl_root_fsolver_alloc(gsl_root_fsolver_brent);
1699  double r, r_up, r_lo;
1700 
1701  // Calculate first estimate for the root bracketing [r_lo, r_up].
1702  // Calculate best estimate for comparison. g(Tchi)^-0.25 = 0.49, g(Tchi)^-...=0.76
1703  r = 0.49*pow((10.0/(pi*pi)) * gsl_pow_2(m_pl*ma0), 0.25);
1704  // Compare to decide which branch of the equation is valid; T1 > Tchi or T1 < Tchi
1705  if ( (r > Tchi) && (beta > 1.0E-10) )
1706  {
1707  r = 0.76*pow((10.0/(pi*pi)) * gsl_pow_2(m_pl*ma0) * pow(Tchi, beta), 1.0/(4.0+beta));
1708  }
1709  // Find appropriate values for r_lo and r_up
1710  r_up = r;
1711  r_lo = r;
1712  while (GSL_FN_EVAL(&F,r_up) > 0.0) { r_up = 2.0*r_up; }
1713  while (GSL_FN_EVAL(&F,r_lo) < 0.0) { r_lo = 0.5*r_lo; }
1714 
1715  // Execute equation solver until we reach 10^-6 absolute precision.
1716  gsl_root_fsolver_set(s, &F, r_lo, r_up);
1717  do
1718  {
1719  iter++;
1720  status = gsl_root_fsolver_iterate (s);
1721  r = gsl_root_fsolver_root (s);
1722  r_lo = gsl_root_fsolver_x_lower (s);
1723  r_up = gsl_root_fsolver_x_upper (s);
1724  status = gsl_root_test_interval (r_lo, r_up, 1.0E-6, 0.0);
1725  } while (status == GSL_CONTINUE && iter < max_iter);
1726 
1727  gsl_root_fsolver_free (s);
1728 
1729  result = r;
1730  }
1731 
1732  /* Differential equation solver to calculate the axion energy density today */
1733 
1734  // Initialise the quantities needed for the ODE solver from the gsl library.
1735  // Define the system of differential equations as a function of relative temperature.
1736  int scal_field_eq(double tau, const double y[], double f[], void *params)
1737  {
1738  struct AxionEDT_params * p = (struct AxionEDT_params *)params;
1739  double ma0 = (p->ma0);
1740  double beta = (p->beta);
1741  double Tchi= (p->Tchi);
1742  double Tosc = (p->Tosc);
1743  double thetai = (p->thetai);
1744  // f stores derivatives, y stores functions.
1745  f[0] = y[1];
1746  f[1] = -gsl_pow_2(SpecialFun1(-tau*Tosc) * ma0*axion_mass_temp(-tau*Tosc,beta,Tchi) / (hubble_rad_dom(-tau*Tosc) * (-tau))) * gsl_sf_sin(y[0]*thetai)/thetai;
1747 
1748  return GSL_SUCCESS;
1749  }
1750 
1751  // Define the Jacobian for the system of differential equations.
1752  int scal_field_eq_jac(double tau, const double y[], double *dfdy, double dfdt[], void *params)
1753  {
1754  //(void)(t); // avoid unused parameter warning.
1755  struct AxionEDT_params * p = (struct AxionEDT_params *)params;
1756  double ma0 = (p->ma0);
1757  double beta = (p->beta);
1758  double Tchi = (p->Tchi);
1759  double Tosc = (p->Tosc);
1760  double thetai = (p->thetai);
1761  gsl_matrix_view dfdy_mat = gsl_matrix_view_array (dfdy, 2, 2);
1762  gsl_matrix * m = &dfdy_mat.matrix;
1763  // (i, j) entries for matrix m; last entry = df[i]/df[j].
1764  gsl_matrix_set (m, 0, 0, 0);
1765  gsl_matrix_set (m, 0, 1, 1);
1766  gsl_matrix_set (m, 1, 0, -gsl_pow_2(SpecialFun1(-tau*Tosc) * ma0*axion_mass_temp(-tau*Tosc,beta,Tchi) / (hubble_rad_dom(-tau*Tosc) * (-tau))) * gsl_sf_cos(y[0]*thetai));
1767  gsl_matrix_set (m, 1, 1, -SpecialFun3(-tau*Tosc) / (-tau));
1768  dfdt[0] = 0.0;
1769  dfdt[1] = 0.0;
1770 
1771  return GSL_SUCCESS;
1772  }
1773 
1774  // Capability function to solve the differential equation for the energy density in axions today (in terms of the critical density).
1775  void RD_oh2_Axions(double &result)
1776  {
1777  using namespace Pipes::RD_oh2_Axions;
1778  double ma0 = *Param["ma0"];
1779  double beta = *Param["beta"];
1780  double Tchi = *Param["Tchi"];
1781  double thetai = *Param["thetai"];
1782  double fa = *Param["fa"];
1783  double Tosc = *Dep::AxionOscillationTemperature;
1784 
1785  if ( (thetai<-pi) || (thetai>3.0*pi) ) { DarkBit_error().raise(LOCAL_INFO, "ERROR! The parameter 'thetai' should be chosen from the interval [-pi,3pi]."); }
1786  // If thetai in (pi,3pi): map it back to its equivalent value in (-pi,pi]. This is to allow sampling around pi and easier averaging.
1787  if (thetai>pi) { thetai = thetai - 2.0*pi; }
1788 
1789  // Only do computations if thetai > 0.
1790  result = 0.0;
1791  if (fabs(thetai) > 0)
1792  {
1793  // TCMB in MeV.
1794  const double TCMB = *Dep::T_cmb*K2eV*1.0E-6;
1795  // Critical energy density today * h^2 (in eV^4).
1796  const double ede_crit_today = 3.0*2.69862E-11;
1797 
1798  struct AxionEDT_params p = {ma0, beta, Tchi, thetai, Tosc};
1799 
1800  // Function, Jacobian, number of dimensions + pointer to params.
1801  gsl_odeiv2_system sys = {scal_field_eq, scal_field_eq_jac, 2, &p};
1802  // Evolution from Temp = 1e5 x Tosc to Temp = 0.001 x Tosc.
1803  double tau2 = -0.001, tau1 = -1E5;
1804  // Initial conditions for (u and v = u') as functions of temperature:
1805  double y[2] = {1.0, 0.0};
1806  // Settings for the driver: pointer to the ODE system sys, the gsl method, initial step size,
1807  // absolute accuracy in y[0] and y[1], relative accuracy in y[0] and y[1].
1808  // Other possible choices: gsl_odeiv2_step_rk4 (classic), gsl_odeiv2_step_rk8pd, gsl_odeiv2_step_rkf45 (standard choices).
1809  gsl_odeiv2_driver * d = gsl_odeiv2_driver_alloc_y_new (&sys, gsl_odeiv2_step_bsimp, -0.1*tau1, 1E-8, 1E-8);
1810 
1811  // Numerically solve ODE by continuing the integration well into the harmonic and adiabatic regime (stopping conditions
1812  // via check1 = |hat(theta)| and check2 = 3H/m need to be satisfied).
1813  double new_step;
1814  double check1 = 1.0, check2 = 1.0;
1815  int i = 0;
1816 
1817  #ifdef AXION_DEBUG_MODE
1818  std::cout << "DEBUGGING INFO for relic density calculation:\n"
1819  "#'temperature' theta dtheta/dtau" << std::endl;
1820  #endif
1821 
1822  do
1823  {
1824  i++;
1825  new_step = -pow(10.0, 1.0 + (log10(-tau2)-1.0)*i/1000.0);
1826  int status = gsl_odeiv2_driver_apply (d, &tau1, new_step, y);
1827  if (status != GSL_SUCCESS) { std::cout << "Error, return value = " << d << std::endl; }
1828  check1 = fabs(thetai)*sqrt(gsl_pow_2( fabs(y[0]) ) + gsl_pow_2( fabs((-new_step)*y[1]*hubble_rad_dom(-new_step*Tosc)/(ma0*axion_mass_temp(-new_step*Tosc,beta,Tchi))) ));
1829  check2 = 3.0*hubble_rad_dom(-new_step*Tosc)/(ma0*axion_mass_temp(-new_step*Tosc,beta,Tchi));
1830 
1831  #ifdef AXION_DEBUG_MODE
1832  std::cout << -new_step << " " << thetai*y[0] << " " << -tau2*thetai*y[1] << std::endl;
1833  #endif
1834 
1835  } while ( ((check1>1.0E-2) || (check2>1.0E-3)) && (i<1E3) );
1836 
1837  i++;
1838  if (i>=1E+3)
1839  {
1840  std::ostringstream buffer;
1841  buffer << "T_end: " << -new_step << " | theta_hat_val: " << check1 << ", theta_der: "<< -tau2*y[1]*thetai << ", 3H/m_osc: " << 3.0*hubble_rad_dom(Tosc)/(ma0*axion_mass_temp(-new_step*Tosc,beta,Tchi)) << ", 3H/m: " << check2 << " .\n";
1842  DarkBit_warning().raise(LOCAL_INFO, "WARNING! Maximum number of integration steps reached for energy density calculator!\n "+buffer.str());
1843  }
1844 
1845  // Calculate the axion energy density at the stopping point.
1846  double ede = 1E+18*gsl_pow_2(fa)*(0.5*gsl_pow_2(y[1]*thetai*hubble_rad_dom(-new_step*Tosc)*(-new_step)) + gsl_pow_2(ma0*axion_mass_temp(-new_step*Tosc,beta,Tchi))*(1.0 - gsl_sf_cos(y[0]*thetai)));
1847  // Use conservation of entropy to scale the axion energy density to its present value (relative to the critical energy density).
1848  double OmegaAh2 = ede*gsl_pow_3(TCMB/(-new_step*Tosc))*(gStar_S(TCMB)/gStar_S(-new_step*Tosc))*(1.0/axion_mass_temp(-new_step*Tosc,beta,Tchi))/ede_crit_today;
1849 
1850  gsl_odeiv2_driver_free (d);
1851 
1852  result = OmegaAh2;
1853  }
1854  }
1855 
1857  // //
1858  // Axion Astrophysics //
1859  // //
1861 
1867  // R-parameter //
1870 
1871  // Capability function to calculate the R-parameter (1512.08108).
1872  // Based and extending on Refs [11, 12, 13, 75] and 10.3204/DESY-PROC-2015-02/straniero_oscar in 1512.08108 .
1873  void calc_RParameter(double &result)
1874  {
1875  using namespace Pipes::calc_RParameter;
1876  double ma0 = *Param["ma0"];
1877  double gaee2 = gsl_pow_2(gaee_conversion*std::fabs(*Param["gaee"]));
1878  double gagg = 1.0E+10*std::fabs(*Param["gagg"]); // gagg needs to be in 10^-10 GeV^-1.
1879  // Value for He-abundance Y from 1503.08146: <Y> = 0.2515(17).
1880  const double Y = 0.2515;
1881  // Use interpolation for the finite-mass correction.
1882  static AxionInterpolator correction (GAMBIT_DIR "/DarkBit/data/Axions_RParameterCorrection.dat", InterpolationOptions1D::linear);
1883  // Initialise an effective axion-photon coupling, valid for low masses.
1884  double geff = gagg;
1885  // Apply correction for higher mass values...
1886  static double m_min = pow(10,correction.lower());
1887  static double m_max = pow(10,correction.upper());
1888  if ((ma0 > m_min) && (ma0 < m_max)) { geff *= pow(10, 0.5*correction.interpolate(log10(ma0))); }
1889  // ... or set to zero if mass is too high.
1890  if (ma0 >= m_max) { geff = 0.0; }
1891  // Expressions only valid for gaee2 < 35.18 but limits should become stronger for gaee2 > 35.18 (but perhaps not gaee2 >> 35.18).
1892  // Conservative approach: Constrain gaee2 > 35.18 at the level of gaee2 = 35.18.
1893  if (gaee2 > 35.18) { gaee2 = 35.18; }
1894 
1895  result = -0.421824 - 0.0948659*(-4.675 + sqrt(21.8556 + 21.0824*geff)) - 0.00533169*gaee2 - 0.0386834*(-1.23 - 0.137991*pow(gaee2,0.75) + sqrt(1.5129 + gaee2)) + 7.3306*Y;
1896  }
1897 
1898  // Capability function to calculate the likelihood for the R-parameter.
1899  void calc_lnL_RParameter(double &result)
1900  {
1901  using namespace Pipes::calc_lnL_RParameter;
1902  double Rtheo = *Dep::RParameter;
1903 
1904  // Observed R values from astro-ph/0403600.
1905  const double Robs = 1.39;
1906  const double RobsErr = 0.03;
1907  // Value for He-abundance Y from 1503.08146: <Y> = 0.2515(17).
1908  const double YobsErrContrib = 7.3306*0.0017;
1909 
1910  result = -0.5*gsl_pow_2(Rtheo - Robs)/(RobsErr*RobsErr+YobsErrContrib*YobsErrContrib);
1911  }
1912 
1914  // White Dwarf cooling hints //
1916 
1917  // White Dwarf interpolator class
1918  class WDInterpolator
1919  {
1920  public:
1921  // Constructor
1922  WDInterpolator(const std::vector<double> x, const std::vector<double> y, std::string correction_file, InterpolationOptions1D type = InterpolationOptions1D::linear)
1923  {
1924  period_change = AxionInterpolator(x, y, type);
1925  correction = AxionInterpolator(correction_file, type);
1926  }
1927 
1928  // Evaluation function
1929  double evaluate(double mrel, double x2)
1930  {
1931  double res = x2;
1932  // For higher masses, reduce the effective coupling accordingly:
1933  if (mrel > 100.0)
1934  {
1935  res *= 15.0 * exp(-mrel) * pow(mrel,2.5)/(M_SQRT2 * pow(pi,3.5));
1936  } else if (mrel > 0.01) {
1937  res *= pow(10,correction.interpolate(log10(mrel)));
1938  }
1939  // We only have predictions up to x2 = max value. Limits should get stronger for x2 > max value, so
1940  // it is conservative to use the prediction for x2 = max value for x2 > max value.
1941  res = std::min(res, period_change.upper());
1942  res = period_change.interpolate(res);
1943  return res;
1944  }
1945 
1946  private:
1947  AxionInterpolator period_change;
1948  AxionInterpolator correction;
1949  };
1950 
1951  // Capability function to compute the cooling likelihood of G117-B15A (1205.6180; observations from Kepler+ (2011)).
1952  void calc_lnL_WDVar_G117B15A(double &result)
1953  {
1954  using namespace Pipes::calc_lnL_WDVar_G117B15A;
1955  // Rescale coupling to be used in their model prediction.
1956  double x2 = (1.0e+14 * std::fabs(*Param["gaee"]))/2.8;
1957  x2 = x2*x2;
1958 
1959  // Values for the model prediction provided by the authors.
1960  const std::vector<double> x2vals = {0.0, 1.0, 6.25, 25.0, 56.25, 100.0, 156.25, 225.0, 306.25, 404.0, 506.25, 625.0, 756.25, 900.0};
1961  const std::vector<double> dPidts = {1.235687, 1.244741, 1.299579, 1.470017, 1.796766, 2.260604, 2.795575, 3.484570, 4.232738, 5.056075, 6.113390, 7.342085, 8.344424, 9.775156};
1962  const double err2 = 0.09*0.09;
1963  const double obs = 4.19;
1964  const double obs_err2 = 0.73*0.73;
1965 
1966  static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_G117B15A.dat");
1967 
1968  // Use interpolation for the finite-mass correction.
1969  const double internal_temperature_keV = 1.19698;
1970  double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
1971 
1972  double pred = dPidt.evaluate(mrel, x2);
1973 
1974  result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
1975  }
1976 
1977  // Capability function to compute the cooling likelihood of R548 (1211.3389 using T = 11630 K; observations from Mukadam+ (2012)).
1978  void calc_lnL_WDVar_R548(double &result)
1979  {
1980  using namespace Pipes::calc_lnL_WDVar_R548;
1981  // Rescale coupling to be used in their model prediction.
1982  double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
1983  x2 = x2*x2;
1984 
1985  // Values for the model prediction provided by the authors.
1986  const std::vector<double> x2vals = {0.0, 1.0, 6.25, 25.0, 56.25, 100.0, 156.25, 225.0, 306.25, 400.0, 506.25, 625.0, 756.25, 900.0};
1987  const std::vector<double> dPidts = {1.075373, 1.095319, 1.123040, 1.289434, 1.497666, 1.869437, 2.300523, 2.844954, 3.379978, 4.086028, 4.847149, 5.754807, 6.714841, 7.649140};
1988  const double err2 = 0.09*0.09;
1989  const double obs = 3.3;
1990  const double obs_err2 = 1.1*1.1;
1991 
1992  static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_R548.dat");
1993 
1994  // Use interpolation for the finite-mass correction.
1995  const double internal_temperature_keV = 1.11447;
1996  double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
1997 
1998  double pred = dPidt.evaluate(mrel, x2);
1999 
2000  result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
2001  }
2002 
2003  // Capability function to compute the cooling likelihood of PG1351+489 (1605.07668 & 1406.6034; using observations from Redaelli+ (2011)).
2004  void calc_lnL_WDVar_PG1351489(double &result)
2005  {
2006  using namespace Pipes::calc_lnL_WDVar_PG1351489;
2007  // Rescale coupling to be used in their model prediction.
2008  double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
2009  x2 = x2*x2;
2010 
2011  // Values for the model prediction provided by the authors.
2012  const std::vector<double> x2vals = {0.0, 4.0, 16.0, 36.0, 64.0, 100.0, 144.0, 196.0, 256.0, 324.0, 400.0};
2013  const std::vector<double> dPidts = {0.90878126, 0.96382008, 1.2022906, 1.5712931, 2.1220619, 2.8002354, 3.6172605, 4.5000560, 5.5256592, 6.5055283, 7.5341296};
2014  const double err2 = 0.5*0.5;
2015  const double obs = 2.0;
2016  const double obs_err2 = 0.9*0.9;
2017 
2018  static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_PG1351489.dat");
2019 
2020  // Use interpolation for the finite-mass correction.
2021  const double internal_temperature_keV = 2.64273;
2022  double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
2023 
2024  double pred = dPidt.evaluate(mrel, x2);
2025 
2026  result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
2027  }
2028 
2029  // Capability function to compute the cooling likelihood of L192 (1605.06458 using l=1 & k=2; observations from Sullivan+Chote (2015)).
2030  void calc_lnL_WDVar_L192 (double &result)
2031  {
2032  using namespace Pipes::calc_lnL_WDVar_L192;
2033  // Rescale coupling to be used in their model prediction.
2034  double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
2035  x2 = x2*x2;
2036 
2037  // Values for the model prediction provided by the authors.
2038  const std::vector<double> x2vals = {0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0, 81.0, 100.0, 121.0, 144.0, 169.0, 196.0, 225.0, 256.0, 289.0, 324.0, 361.0, 400.0, 441.0, 484.0, 529.0, 576.0, 625.0, 676.0, 729.0, 784.0, 841.0, 900.0};
2039  const std::vector<double> dPidts = {2.41, 2.40, 2.44, 2.42, 2.50, 2.57, 2.63, 2.74, 2.83, 2.99, 3.15, 3.32, 3.52, 3.70, 3.90, 4.08, 4.42, 4.69, 4.98, 5.34, 5.62, 6.02, 6.27, 6.62, 7.04, 7.38, 7.89, 8.09, 8.65, 9.16, 9.62};
2040  const double err2 = 0.85*0.85;
2041  const double obs = 3.0;
2042  const double obs_err2 = 0.6*0.6;
2043 
2044  static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_L192.dat");
2045 
2046  // Use interpolation for the finite-mass correction.
2047  const double internal_temperature_keV = 1.04931;
2048  double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
2049 
2050  double pred = dPidt.evaluate(mrel, x2);
2051 
2052  result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
2053  }
2054 
2056  // SN 1987A limits (from axion-photon conversion in the B-field of the Milky Way or axion-photon decay) //
2058 
2059  // Capability function to calculate the likelihood for SN 1987A (based on data from 25 to 100 MeV photons, interpreted
2060  // by Chupp et al., Phys. Rev. Lett. 62, 505 (1989). Use 10 sec of data for conversion.
2061  void calc_lnL_SN1987A (double &result)
2062  {
2063  using namespace Pipes::calc_lnL_SN1987A;
2064  double f_10s = *Dep::PhotonFluence_SN1987A_Conversion;
2065 
2066  // Standard devations of the null observation.
2067  const double sigma_10s = 0.2;
2068 
2069  double ratio = f_10s/sigma_10s;
2070 
2071  result = -0.5*ratio*ratio;
2072  }
2073 
2075  // SN 1987A photon fluence (from axion-photon conversion in the B-field of the Milky Way) //
2077 
2078  // Capability function to calculate the photon fluence from SN 1987A as a result of axion-photon
2079  // conversion in the B-field of the Milky Way (based on arXiv:1410.3747).
2081  {
2083  double m = (1.0E+10*(*Param["ma0"]))/5.433430;
2084  double g = (1.0E+12*std::fabs(*Param["gagg"]))/5.339450;
2085 
2086  result = 0.570589*gsl_pow_4(g);
2087  if (m > 1.0) { result = result*pow(m, -4.021046); }
2088  }
2089 
2091  // Spectral distortions (H.E.S.S. telescope searches) //
2093 
2094  // Calculate the likelihood for H.E.S.S. data assuming conversion in the galaxy cluster magnetic field (GCMF, "Conservative" limits, 1311.3148).
2095  void calc_lnL_HESS_GCMF (double &result)
2096  {
2097  using namespace Pipes::calc_lnL_HESS_GCMF;
2098  double m_ax = *Param["ma0"];
2099  double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
2100 
2101  // Compute the domensionless parameters Epsilon and Gamma from the axion mass and axion-photon coupling (see 1311.3148).
2102  const double c_epsilon = 0.071546787;
2103  const double c_gamma = 0.015274036*370.0/sqrt(37.0);
2104  double epsilon = log10(m_ax*c_epsilon) + 5.0;
2105  double gamma = log10(gagg*c_gamma) + 20.0;
2106 
2107  // Initialise the interpolation and extrapolation routies for the H.E.S.S. results.
2108  static HESS_Interpolator interp (GAMBIT_DIR "/DarkBit/data/HESS_GCMF_Table.dat");
2109 
2110  result = interp.lnL(epsilon, gamma);
2111  }
2112 
2118  void calc_lnL_XENON1T_Anomaly(double &result)
2119  {
2120  using namespace Pipes::calc_lnL_XENON1T_Anomaly;
2121 
2122  // Rescale couplings and 3H abundance to the reference values used in 2006.10035 for convenience.
2123  double gae = std::fabs(*Param["gaee"]) / 5.0e-12;
2124  double gagamma = std::fabs(*Param["gagg"]) / 2.0e-10;
2125  double gaN = std::fabs(*Param["gaN"]) / 1.0e-6;
2126  double x_3H = *Param["x_3H"] / 6.2e-25;
2127  double bkg_scale = 1.0 + *Param["delta_bkg"];
2128  double eff = 1.0 + *Param["delta_eff"];
2129 
2130  static bool include_inverse_Primakoff = runOptions->getValueOrDef<bool> (true, "include_inverse_Primakoff");
2131 
2132  // XENON1T 2020 data (based on 2006.10035 and using an exposure of 0.65 tonne-years)
2133  static const Eigen::ArrayXd observed = (Eigen::ArrayXd(29) <<
2134  26., 61., 55., 47., 49.,
2135  47., 44., 41., 40., 37.,
2136  51., 41., 42., 51., 47.,
2137  48., 24., 43., 42., 34.,
2138  42., 40., 38., 53., 41.,
2139  57., 39., 46., 35.).finished();
2140 
2141  static const Eigen::ArrayXd bkg_tritium = (Eigen::ArrayXd(29) <<
2142  4.54543769e+00, 8.60509728e+00, 8.94256482e+00, 8.61684767e+00, 8.02464466e+00,
2143  7.29168481e+00, 6.48068011e+00, 5.65037508e+00, 4.81438779e+00, 3.97835836e+00,
2144  3.17827210e+00, 2.43987288e+00, 1.78022901e+00, 1.21426641e+00, 7.54437371e-01,
2145  4.13276721e-01, 1.95253807e-01, 7.58276424e-02, 2.37741495e-02, 1.17262241e-02,
2146  4.76851304e-03, 1.65434695e-04, 5.42752619e-05, 5.22947241e-05, 5.03141863e-05,
2147  4.83336485e-05, 4.63531107e-05, 4.43725729e-05, 4.23920351e-05).finished();
2148 
2149  static const Eigen::ArrayXd bkg_other = (Eigen::ArrayXd(29) <<
2150  22.07404375, 39.45186703, 41.83417331, 42.46968003, 42.78761694, 42.96532578,
2151  43.13847573, 43.56505579, 44.1162301 , 44.04330642, 43.60594679, 43.40248223,
2152  43.45031774, 43.49263918, 43.57084528, 43.66270961, 43.75990478, 43.85453193,
2153  43.95159076, 44.04782058, 44.14510208, 44.24450247, 44.3406822 , 44.43638726,
2154  44.5331988, 44.62865958, 44.72654689, 44.82382807, 44.91842725).finished();
2155 
2156  static const Eigen::ArrayXd signal_ref_ABC = (Eigen::ArrayXd(29) <<
2157  7.46283683e+01, 9.11300502e+01, 4.91874199e+01, 3.53433982e+01, 3.97196350e+01,
2158  3.57128137e+01, 2.27540737e+01, 1.19536450e+01, 6.29278747e+00, 3.30948412e+00,
2159  1.61495065e+00, 6.21479171e-01, 1.55434142e-01, 2.13046184e-02, 1.63362970e-03,
2160  5.94631877e-05, 6.22346656e-07, 0, 0, 0,
2161  0, 0, 0, 0, 0,
2162  0, 0, 0, 0).finished();
2163 
2164  static const Eigen::ArrayXd signal_ref_primakoff = (Eigen::ArrayXd(29) <<
2165  8.52269995e+00, 2.00539997e+01, 1.92433543e+01, 2.22113223e+01, 2.89487211e+01,
2166  2.48618807e+01, 1.56554086e+01, 8.76011034e+00, 4.77088790e+00, 2.59966792e+00,
2167  1.43054774e+00, 7.84191139e-01, 4.15162321e-01, 2.10029374e-01, 1.02847245e-01,
2168  5.27333566e-02, 2.93001979e-02, 1.70759274e-02, 1.08781046e-02, 7.76743790e-03,
2169  6.24916022e-03, 5.51837192e-03, 5.15693524e-03, 4.97968515e-03, 4.88717867e-03,
2170  4.84242242e-03, 4.82038071e-03, 2.38442778e-03, 0).finished();
2171 
2172  static const Eigen::ArrayXd signal_ref_fe57 = (Eigen::ArrayXd(29) <<
2173  4.64697658e-22, 1.14417236e-18, 1.48421451e-15, 1.01555196e-12, 3.67061998e-10,
2174  7.02067458e-08, 7.12116995e-06, 3.84028003e-04, 1.10431167e-02, 1.69885040e-01,
2175  1.40292258e+00, 6.23942228e+00, 1.49856121e+01, 1.94718769e+01, 1.36959639e+01,
2176  5.21070939e+00, 1.07020697e+00, 1.18324090e-01, 7.01902932e-03, 2.22639151e-04,
2177  3.76396817e-06, 3.38186408e-08, 1.61083936e-10, 4.05908328e-13, 5.40175385e-16,
2178  3.79105023e-19, 1.40152641e-22, 2.72678128e-26, 2.78978087e-30).finished();
2179 
2180  static const Eigen::ArrayXd signal_ref_ABC_inv_p = (Eigen::ArrayXd(29) <<
2181  6.90095435e+00, 1.39022260e+01, 1.15151093e+01, 7.97641672e+00, 5.52073353e+00,
2182  4.15542096e+00, 2.85016524e+00, 1.77391643e+00, 1.07580666e+00, 6.28453363e-01,
2183  3.26090878e-01, 1.31638303e-01, 3.77207469e-02, 7.52278937e-03, 1.06834615e-03,
2184  1.12228106e-04, 9.07094259e-06, 5.85004243e-07, 3.10665723e-08, 1.39581067e-09,
2185  5.42752207e-11, 1.86190569e-12, 5.72719620e-14, 1.60150437e-15, 4.11906863e-17,
2186  9.84235043e-19, 2.20372014e-20, 4.65786516e-22, 9.35349973e-24).finished();
2187 
2188  static const Eigen::ArrayXd signal_ref_primakoff_inv_p = (Eigen::ArrayXd(29) <<
2189  8.80070525e-01, 3.29923191e+00, 4.56900949e+00, 4.56444853e+00, 3.82216381e+00,
2190  2.86024934e+00, 1.98175269e+00, 1.29748018e+00, 8.13307028e-01, 4.92544613e-01,
2191  2.90070188e-01, 1.66927792e-01, 9.42159402e-02, 5.23046562e-02, 2.86265445e-02,
2192  1.54743414e-02, 8.27421198e-03, 4.38184245e-03, 2.30069622e-03, 1.19872791e-03,
2193  6.20254770e-04, 3.18925270e-04, 1.63051423e-04, 8.29261227e-05, 4.19736901e-05,
2194  2.11518187e-05, 1.06157318e-05, 5.30780091e-06, 2.64457315e-06).finished();
2195 
2196  static const Eigen::ArrayXd signal_ref_fe57_inv_p = (Eigen::ArrayXd(29) <<
2197  2.68534579e-23, 1.02403478e-19, 1.64401745e-16, 1.34346994e-13, 5.65333857e-11,
2198  1.23357889e-08, 1.40118942e-06, 8.30825834e-05, 2.57975431e-03, 4.20954642e-02,
2199  3.62322333e-01, 1.65087221e+00, 3.99391811e+00, 5.14060546e+00, 3.52226638e+00,
2200  1.28362583e+00, 2.48258237e-01, 2.54009765e-02, 1.36995132e-03, 3.88033329e-05,
2201  5.75235843e-07, 4.44951177e-09, 1.79118821e-11, 3.74451860e-14, 4.05797696e-17,
2202  2.27644923e-20, 6.60289329e-24, 9.89294778e-28, 7.65058090e-32).finished();
2203 
2204  static const double asimov = (observed * observed.log() - observed).sum();
2205 
2206  const Eigen::ArrayXd bkg = x_3H * bkg_tritium + bkg_other;
2207  Eigen::ArrayXd signal = gae * gae * (
2208  signal_ref_ABC * gae * gae +
2209  signal_ref_primakoff * gagamma * gagamma +
2210  signal_ref_fe57 * gaN * gaN);
2211 
2212  if (include_inverse_Primakoff)
2213  {
2214  signal = signal + gagamma * gagamma * (
2215  signal_ref_ABC_inv_p * gae * gae +
2216  signal_ref_primakoff_inv_p * gagamma * gagamma +
2217  signal_ref_fe57_inv_p * gaN * gaN);
2218  }
2219 
2220  const Eigen::ArrayXd expected = eff * (bkg_scale * bkg + signal);
2221 
2222  result = (observed * expected.log() - expected).sum() - asimov;
2223  }
2224 
2225  struct dRdE_params { double m; double sigma; };
2226 
2227  double dRdE (double E, void * params)
2228  {
2229  struct dRdE_params * par = (struct dRdE_params *)params;
2230  // Efficiency of the Xenon1T experiment from arXiv:2006.09721
2231  // Columns: Energy [keV] | Efficiency [dimensionless]
2232  static AxionInterpolator efficiency (GAMBIT_DIR "/DarkBit/data/XENON1T/efficiency.txt", InterpolationOptions1D::cspline);
2233  return std::exp(-0.5*pow((E - par->m)/par->sigma,2))*efficiency.interpolate(E);
2234  }
2235 
2236  void calc_lnL_XENON1T_DM_Anomaly(double &result)
2237  {
2238  using namespace Pipes::calc_lnL_XENON1T_DM_Anomaly;
2239 
2240  result = 0;
2241 
2242  // Rescale couplings and 3H abundance to the reference values used in 2006.10035 for convenience.
2243  double gae = std::fabs(*Param["gaee"]);
2244  double ma = *Param["ma0"] / 1.0e3;
2245  double x_3H = *Param["x_3H"] / 6.2e-25;
2246  double bkg_scale = 1.0 + *Param["delta_bkg"];
2247  double rel_eff = 1.0 + *Param["delta_eff"];
2248  double dm_fraction = *Param["eta"];
2249  LocalMaxwellianHalo LocalHaloParameters = *Dep::LocalHalo;
2250  double rho0 = LocalHaloParameters.rho0;
2251 
2252  // XENON1T 2020 data (based on 2006.10035 and using an exposure of 0.65 tonne-years)
2253  static const Eigen::ArrayXd observed = (Eigen::ArrayXd(29) <<
2254  26., 61., 55., 47., 49.,
2255  47., 44., 41., 40., 37.,
2256  51., 41., 42., 51., 47.,
2257  48., 24., 43., 42., 34.,
2258  42., 40., 38., 53., 41.,
2259  57., 39., 46., 35.).finished();
2260 
2261  static const Eigen::ArrayXd bkg_tritium = (Eigen::ArrayXd(29) <<
2262  4.54543769e+00, 8.60509728e+00, 8.94256482e+00, 8.61684767e+00, 8.02464466e+00,
2263  7.29168481e+00, 6.48068011e+00, 5.65037508e+00, 4.81438779e+00, 3.97835836e+00,
2264  3.17827210e+00, 2.43987288e+00, 1.78022901e+00, 1.21426641e+00, 7.54437371e-01,
2265  4.13276721e-01, 1.95253807e-01, 7.58276424e-02, 2.37741495e-02, 1.17262241e-02,
2266  4.76851304e-03, 1.65434695e-04, 5.42752619e-05, 5.22947241e-05, 5.03141863e-05,
2267  4.83336485e-05, 4.63531107e-05, 4.43725729e-05, 4.23920351e-05).finished();
2268 
2269  static const Eigen::ArrayXd bkg_other = (Eigen::ArrayXd(29) <<
2270  22.07404375, 39.45186703, 41.83417331, 42.46968003, 42.78761694,
2271  42.96532578, 43.13847573, 43.56505579, 44.1162301 , 44.04330642,
2272  43.60594679, 43.40248223, 43.45031774, 43.49263918, 43.57084528,
2273  43.66270961, 43.75990478, 43.85453193, 43.95159076, 44.04782058,
2274  44.14510208, 44.24450247, 44.3406822 , 44.43638726, 44.5331988 ,
2275  44.62865958, 44.72654689, 44.82382807, 44.91842725).finished();
2276 
2277  // Photoelectric cross section for Xe from https://dx.doi.org/10.18434/T48G6X
2278  // Columns: Photon energy [MeV] | Photoelectric absorption [10^-28 m^2/atom]
2279  static AxionInterpolator sigma_pe (GAMBIT_DIR "/DarkBit/data/XENON1T/photoelectric.txt");
2280 
2281  gsl_integration_workspace * w = gsl_integration_workspace_alloc(1000);
2282  if ( (ma >= 1.0) && (ma <= 30.0) )
2283  {
2284  // Energy resolution from 2003.03825
2285  double energy_resolution = 0.15 + 31.71/sqrt(ma);
2286  double sigma = ma * energy_resolution / 100.0;
2287  const double exposure = 0.647309514*1000.0*365.0;
2288  const double photoel_eff_conversion = 1.5e19/131.0;
2289  double amplitude = dm_fraction * (rho0/0.3) * exposure * gae*gae * ma * photoel_eff_conversion*sigma_pe.interpolate(ma/1000.0);
2290  gsl_function f;
2291  struct dRdE_params params = {ma, sigma};
2292  f.function = &dRdE;
2293  f.params = &params;
2294  double dRdE_result, error;
2295  std::vector<double> signal_vec;
2296  for (int i = 0; i < 29; i++)
2297  {
2298  gsl_integration_qags (&f, 1.+i, 2.+i, 0, 1e-7, 1000, w, &dRdE_result, &error);
2299  double s = amplitude * 1/sqrt(2*pi)/sigma * dRdE_result;
2300  signal_vec.push_back(s);
2301  }
2302  gsl_integration_workspace_free (w);
2303 
2304  static const double asimov = (observed * observed.log() - observed).sum();
2305 
2306  const Eigen::ArrayXd bkg = x_3H * bkg_tritium + bkg_other;
2307  const Eigen::ArrayXd signal = Eigen::ArrayXd::Map(signal_vec.data(), signal_vec.size());
2308  const Eigen::ArrayXd expected = rel_eff * (bkg_scale * bkg + signal);
2309 
2310  result = (observed * expected.log() - expected).sum() - asimov;
2311  }
2312  }
2313 
2315  {
2317 
2318  result = -0.5 * ( gsl_pow_2(*Param["delta_bkg"]/0.026) + gsl_pow_2(*Param["delta_eff"]/0.030) );
2319  }
2320 
2321  } // namespace DarkBit
2322 } // namespace Gambit
error & DarkBit_error()
greatScanData data
Definition: great.cpp:38
double dRdE(double E, void *params)
Definition: Axions.cpp:2227
void QCDAxion_TemperatureDependence_Nuisance_lnL(double &result)
Definition: Axions.cpp:1156
double gStar_S(double T)
Definition: Axions.cpp:1095
START_MODEL beta
Definition: Axions.hpp:36
void setcolnames(std::vector< std::string > names)
const double m_electron
const double alpha_EM
double log_chi(double T, double beta, double Tchi)
Definition: Axions.cpp:1147
START_MODEL rs
Simple reader for ASCII tables.
void calc_lnL_CAST2007(double &result)
Definition: Axions.cpp:1365
double SpecialFun3(double T)
Definition: Axions.cpp:1628
#define LOCAL_INFO
Definition: local_info.hpp:34
int scal_field_eq(double tau, const double y[], double f[], void *params)
Definition: Axions.cpp:1736
const double gagg_conversion
Supporting classes and functions for the axion module.
Definition: Axions.cpp:70
void QCDAxion_ZeroTemperatureMass_Nuisance_lnL(double &result)
Definition: Axions.cpp:1120
InterpolationOptions2D
Two-dimensional integration container for bilinear interpolation and bicubic splines.
Definition: Axions.cpp:197
void calc_Haloscope_signal(double &result)
Definition: Axions.cpp:1447
double gaussian_loglikelihood(double theory, double obs, double theoryerr, double obserr, bool profile_systematics)
Use a detection to compute a simple chi-square-like log likelihood, for the case when obs is Gaussian...
Definition: statistics.cpp:133
void RD_oh2_Axions(double &result)
Definition: Axions.cpp:1775
void calc_lnL_WDVar_L192(double &result)
Definition: Axions.cpp:2030
void calc_CAST2007_signal_vac(std::vector< double > &result)
Definition: Axions.cpp:1274
double erg_integrand(double erg, void *params)
Definition: Axions.cpp:710
void calc_lnL_Haloscope_ADMX2(double &result)
Definition: Axions.cpp:1506
void swap(Spectrum &first, Spectrum &second)
Swap resources of two Spectrum objects Note: Not a member function! This is an external function whic...
Definition: spectrum.cpp:57
START_MODEL thetai CaggQCD
Definition: Axions.hpp:50
void calc_RParameter(double &result)
Capabilities relating to astrophysical observations (R-parameter, H.E.S.S. telescope search...
Definition: Axions.cpp:1873
START_MODEL b
Definition: demo.hpp:270
int scal_field_eq_jac(double tau, const double y[], double *dfdy, double dfdt[], void *params)
Definition: Axions.cpp:1752
void calc_ALPS1_signal_vac(double &result)
Definition: Axions.cpp:1218
void calc_lnL_SN1987A(double &result)
Definition: Axions.cpp:2061
void QCDAxion_AxionPhotonConstant_Nuisance_lnL(double &result)
Definition: Axions.cpp:1134
double gStar(double T)
Various capabilities and functions to provide SM physics as well as QCD input for axions...
Definition: Axions.cpp:1075
const double atomic_mass_unit
void calc_lnL_Haloscope_ADMX1(double &result)
Definition: Axions.cpp:1466
General small utility functions.
void calc_ALPS1_signal_gas(double &result)
Definition: Axions.cpp:1228
void calc_CAST2017_signal_vac(std::vector< std::vector< double >> &result)
Definition: Axions.cpp:1302
const double hbar
const std::map< InterpolationOptions1D, std::string > int_type_name
Definition: Axions.cpp:81
const double m_planck_red
void calc_lnL_RParameter(double &result)
Definition: Axions.cpp:1899
GAMBIT error class.
Definition: exceptions.hpp:136
void calc_lnL_CAST2017(double &result)
Definition: Axions.cpp:1389
START_MODEL x2
Definition: demo.hpp:42
const double sigma
Definition: SM_Z.hpp:43
Declarations of statistical utilities.
double hubble_rad_dom(double T)
Definition: Axions.cpp:1640
bool check2(const str &s1, const str &s2)
Sub-check for are_similar.
MATH_OPERATION(Dif,-) MATH_OPERATION(pow) MATH_OPERATION(fmin) MATH_OPERATION(fmax) class FunkInterp shared_ptr< FunkInterp > interp(T f, std::vector< double > x, std::vector< double > y)
Definition: daFunk.hpp:1349
const double pi
EXPORT_SYMBOLS bool file_exists(const std::string &filename)
Check if a file exists.
double alt_erg_integrand(double erg, void *params)
Definition: Axions.cpp:741
const double mu
Definition: SM_Z.hpp:42
void calc_lnL_XENON1T_Anomaly_NuisanceParameters(double &result)
Definition: Axions.cpp:2314
const double gaee_conversion
Definition: Axions.cpp:71
double G(const double x)
Definition: FlavBit.cpp:1588
const int method
Definition: Axions.cpp:655
double rho_integrand(double rho, void *params)
Definition: Axions.cpp:662
const Logging::endofmessage EOM
Explicit const instance of the end of message struct in Gambit namespace.
Definition: logger.hpp:100
double CAST_lnL_general(std::vector< double > s, const std::vector< double > bkg_counts, const std::vector< int > sig_counts)
Definition: Axions.cpp:1350
Basic set of known mathematical and physical constants for GAMBIT.
Header file that includes all GAMBIT headers required for a module source file.
EXPORT_SYMBOLS Logging::LogMaster & logger()
Function to retrieve a reference to the Gambit global log object.
Definition: logger.cpp:95
void calc_lnL_HESS_GCMF(double &result)
Definition: Axions.cpp:2095
Simple header file for turning compiler warnings back on after having included one of the begin_ignor...
double ALPS1_lnL_general(double s, double mu, double sigma)
Definition: Axions.cpp:1238
START_MODEL dNur_CMB r
void calc_lnL_WDVar_G117B15A(double &result)
Definition: Axions.cpp:1952
const std::map< InterpolationOptions2D, std::string > int_2d_type_name
Definition: Axions.cpp:198
void calc_lnL_WDVar_R548(double &result)
Definition: Axions.cpp:1978
DS5_MSPCTM DS_INTDOF int
Pragma directives to suppress compiler warnings coming from including Eigen library headers...
Rollcall header for module DarkBit.
START_MODEL Tchi
Definition: Axions.hpp:36
void calc_lnL_XENON1T_Anomaly(double &result)
Capability for the XENON1T likelihood from 2006.10035.
Definition: Axions.cpp:2118
const double gev2cm
warning & DarkBit_warning()
bool check1(const str &s1, const str &s2)
Sub-check for are_similar.
double pow(const double &a)
Outputs a^i.
InterpolationOptions1D
Generic one-dimensional integration container for linear interpolation and cubic splines.
Definition: Axions.cpp:80
double equation_Tosc(double T, void *params)
Definition: Axions.cpp:1661
double parabola(double x, const double params[])
H.E.S.S.-likelihood-related interpolation routines.
Definition: Axions.cpp:351
START_MODEL thetai thetai
Definition: Axions.hpp:50
const double K2eV
void calc_lnL_WDVar_PG1351489(double &result)
Definition: Axions.cpp:2004
const double eV2g
double ALPS1_signal_general(double power, double nm1, double m_ax, double gagg)
Likelihoods for ALPS 1 (LSW), CAST (helioscopes), and ADMX, UF, RBF (haloscopes). ...
Definition: Axions.cpp:1191
double rad_integrand(double rad, void *params)
Definition: Axions.cpp:685
START_MODEL thetai LambdaChi
Definition: Axions.hpp:50
void calc_PhotonFluence_SN1987A_Conversion(double &result)
Definition: Axions.cpp:2080
void calc_lnL_XENON1T_DM_Anomaly(double &result)
Definition: Axions.cpp:2236
void calc_lnL_Haloscope_UF(double &result)
Definition: Axions.cpp:1533
double axion_mass_temp(double T, double beta, double Tchi)
Definition: Axions.cpp:1648
TODO: see if we can use this one:
Definition: Analysis.hpp:33
double E(const double x, const double y)
const double rel_prec
Definition: Axions.cpp:654
void calc_AxionOscillationTemperature(double &result)
Definition: Axions.cpp:1676
void calc_lnL_ALPS1(double &result)
Definition: Axions.cpp:1246
void calc_lnL_Haloscope_RBF(double &result)
Definition: Axions.cpp:1557
double gaussian_nuisance_lnL(double theo, double obs, double sigma)
Definition: Axions.cpp:1057
const double abs_prec
Definition: Axions.cpp:654
double SpecialFun1(double T)
Capabilities relating to axion cosmology. Currently only provides the energy density in axions today ...
Definition: Axions.cpp:1616
double intersect_parabola_line(double a, double b, double sign, const double pparams[])
Definition: Axions.cpp:354
Utility functions for DarkBit.