file src/DarkBit/src/Axions.cpp
[No description available] More…
Namespaces
Name |
---|
Gambit TODO: see if we can use this one: |
Gambit::DarkBit |
Classes
Detailed Description
Author: Sebastian Hoof (hoof@uni-goettingen.de)
Date:
- 2016 Oct
- 2017 Jan, Feb, June, July, Sept - Dec
- 2018 Jan, Mar - May, Sept
- 2019 Feb, May - July
- 2020 Sept, Dec
- 2021 Jan
- 2022 May
GAMBIT: Global and Modular BSM Inference Tool
Axion-specific module functions for DarkBit.
Authors (add name and date if you modify):
Source code
/// GAMBIT: Global and Modular BSM Inference Tool
/// *********************************************
/// \file
///
/// Axion-specific module functions for DarkBit.
///
/// *********************************************
///
/// Authors (add name and date if you modify):
///
/// \author Sebastian Hoof
/// (hoof@uni-goettingen.de)
/// \date 2016 Oct
/// \date 2017 Jan, Feb, June, July, Sept - Dec
/// \date 2018 Jan, Mar - May, Sept
/// \date 2019 Feb, May - July
/// \date 2020 Sept, Dec
/// \date 2021 Jan
/// \date 2022 May
///
/// *********************************************
#include <algorithm>
#include <cmath>
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
#include "gambit/Utils/begin_ignore_warnings_eigen.hpp"
#include <Eigen/Core>
#include "gambit/Utils/end_ignore_warnings.hpp"
#include <gsl/gsl_math.h>
#include <gsl/gsl_sf.h>
#include <gsl/gsl_sf_trig.h>
#include <gsl/gsl_sf_erf.h>
#include <gsl/gsl_spline.h>
#include <gsl/gsl_interp2d.h>
#include <gsl/gsl_spline2d.h>
#include <gsl/gsl_histogram.h>
#include <gsl/gsl_roots.h>
#include <gsl/gsl_matrix.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_odeiv2.h>
#include "gambit/Elements/gambit_module_headers.hpp"
#include "gambit/Utils/util_functions.hpp"
#include "gambit/Utils/ascii_table_reader.hpp"
#include "gambit/Utils/statistics.hpp"
#include "gambit/Utils/numerical_constants.hpp"
#include "gambit/DarkBit/DarkBit_rollcall.hpp"
#include "gambit/DarkBit/DarkBit_utils.hpp"
//#define AXION_DEBUG_MODE
//#define AXION_OMP_DEBUG_MODE
namespace Gambit
{
namespace DarkBit
{
////////////////////////////////////////////////////////////////////
// //
// General Functions and Classes for Axions //
// //
////////////////////////////////////////////////////////////////////
/*! \brief Supporting classes and functions for the axion module.
*/
const double gagg_conversion = 1.0E-9;
const double gaee_conversion = 1.0E+13;
/////////////////////////////////////////////////////////////////
// Auxillary functions and classes for interpolation //
/////////////////////////////////////////////////////////////////
/*! \brief Generic one-dimensional integration container for linear interpolation and cubic splines.
*/
enum class InterpolationOptions1D { linear, cspline };
const std::map<InterpolationOptions1D, std::string> int_type_name = { { InterpolationOptions1D::linear, "linear" }, { InterpolationOptions1D::cspline, "cspline"} };
// AxionInterpolator class: Provides a general 1-D interpolation container based on the gsl library.
// Can be declared static for efficiency & easy one-time initialisation of interpolating functions.
class AxionInterpolator
{
public:
// Overloaded class creators for the AxionInterpolator class using the init function below.
AxionInterpolator();
AxionInterpolator(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type = InterpolationOptions1D::linear);
AxionInterpolator(std::string file, InterpolationOptions1D type = InterpolationOptions1D::linear);
AxionInterpolator& operator=(AxionInterpolator&&);
// Destructor.
~AxionInterpolator();
// Delete copy constructor and assignment operator to avoid shallow copies.
AxionInterpolator(const AxionInterpolator&) = delete;
AxionInterpolator operator=(const AxionInterpolator&) = delete;
// Routine to access interpolated values.
double interpolate(double x);
// Routine to access upper and lower boundaries of available data.
double lower();
double upper();
private:
// Initialiser for the AxionInterpolator class.
void init(std::string file, InterpolationOptions1D type);
void init(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type);
// The gsl objects for the interpolating functions.
gsl_interp_accel *acc;
gsl_spline *spline;
// Upper and lower boundaries available for the interpolating function.
double lo;
double up;
};
// Default constructor.
AxionInterpolator::AxionInterpolator()
{
acc = gsl_interp_accel_alloc();
spline = gsl_spline_alloc(gsl_interp_linear, 2);
}
// Initialiser for the AxionInterpolator class.
void AxionInterpolator::init(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type)
{
int pts = x.size();
// Get first and last value of the "x" component.
lo = x.front();
up = x.back();
acc = gsl_interp_accel_alloc();
if (type == InterpolationOptions1D::cspline)
{
spline = gsl_spline_alloc(gsl_interp_cspline, pts);
}
else if (type == InterpolationOptions1D::linear)
{
spline = gsl_spline_alloc(gsl_interp_linear, pts);
}
else
{
DarkBit_error().raise(LOCAL_INFO, "ERROR! Interpolation type not known to class AxionInterpolator.");
}
gsl_spline_init(spline, &x[0], &y[0], pts);
}
AxionInterpolator::AxionInterpolator(const std::vector<double> x, const std::vector<double> y, InterpolationOptions1D type) { init(x, y, type); }
// Initialiser for the AxionInterpolator class.
void AxionInterpolator::init(std::string file, InterpolationOptions1D type)
{
// Check if file exists.
if (not(Utils::file_exists(file)))
{
DarkBit_error().raise(LOCAL_INFO, "ERROR! File '"+file+"' not found!");
} else {
logger() << LogTags::debug << "Reading data from file '"+file+"' and interpolating it with '"+int_type_name.at(type)+"' method." << EOM;
}
// Read numerical values from data file.
ASCIItableReader tab (file);
tab.setcolnames("x", "y");
init(tab["x"], tab["y"], type);
}
AxionInterpolator::AxionInterpolator(std::string file, InterpolationOptions1D type) { init(file, type); }
// Move assignment operator
AxionInterpolator& AxionInterpolator::operator=(AxionInterpolator&& interp)
{
if(this != &interp)
{
std::swap(acc,interp.acc);
std::swap(spline,interp.spline);
std::swap(lo,interp.lo);
std::swap(up,interp.up);
}
return *this;
}
// Destructor
AxionInterpolator::~AxionInterpolator()
{
gsl_spline_free(spline);
gsl_interp_accel_free(acc);
}
// Routine to access interpolated values.
double AxionInterpolator::interpolate(double x) { return gsl_spline_eval(spline, x, acc); }
// Routines to return upper and lower boundaries of interpolating function
double AxionInterpolator::lower() { return lo; }
double AxionInterpolator::upper() { return up; }
/*! \brief Two-dimensional integration container for bilinear interpolation and bicubic splines.
*/
enum class InterpolationOptions2D { bilinear, bicubic };
const std::map<InterpolationOptions2D, std::string> int_2d_type_name = { { InterpolationOptions2D::bilinear, "bilinear" }, { InterpolationOptions2D::bicubic, "bicubic"} };
// AxionInterpolator2D class: Provides a 2-D interpolation container based on the gsl library.
// Can be declared static for efficiency & easy one-time initialisation of interpolating functions.
class AxionInterpolator2D
{
public:
// Overloaded class creators for the AxionInterpolator class using the init function below.
AxionInterpolator2D();
AxionInterpolator2D(std::string file, InterpolationOptions2D type = InterpolationOptions2D::bilinear);
AxionInterpolator2D& operator=(AxionInterpolator2D&&);
// Destructor.
~AxionInterpolator2D();
// Delete copy constructor and assignment operator to avoid shallow copies.
AxionInterpolator2D(const AxionInterpolator2D&) = delete;
AxionInterpolator2D operator=(const AxionInterpolator2D&) = delete;
// Routine to access interpolated values.
double interpolate(double x, double y);
// Routine to check if a point is inside the interpolating box.
bool is_inside_box(double x, double y);
private:
// Initialiser for the AxionInterpolator2D class.
void init(std::string file, InterpolationOptions2D type);
// The gsl objects for the interpolating functions that need to be available to the class routines.
gsl_interp_accel *x_acc;
gsl_interp_accel *y_acc;
gsl_spline2d *spline;
double* z;
// Upper and lower "x" and "y" values available to the interpolating function.
double x_lo, y_lo, x_up, y_up;
};
// Move assignment operator
AxionInterpolator2D& AxionInterpolator2D::operator=(AxionInterpolator2D&& interp)
{
if(this != &interp)
{
std::swap(x_acc,interp.x_acc);
std::swap(y_acc,interp.y_acc);
std::swap(spline,interp.spline);
std::swap(x_lo,interp.x_lo);
std::swap(x_up,interp.x_up);
std::swap(y_lo,interp.y_lo);
std::swap(y_up,interp.y_up);
std::swap(z,interp.z);
}
return *this;
}
// Destructor
AxionInterpolator2D::~AxionInterpolator2D()
{
gsl_spline2d_free (spline);
gsl_interp_accel_free (x_acc);
gsl_interp_accel_free (y_acc);
free(z);
}
// Initialiser for the AxionInterpolator class.
void AxionInterpolator2D::init(std::string file, InterpolationOptions2D type)
{
// Check if file exists.
if (not(Utils::file_exists(file)))
{
DarkBit_error().raise(LOCAL_INFO, "ERROR! File '"+file+"' not found!");
} else {
logger() << LogTags::debug << "Reading data from file '"+file+"' and interpolating it with '"+int_2d_type_name.at(type)+"' method." << EOM;
}
// Read numerical values from data file.
ASCIItableReader tab (file);
tab.setcolnames("x", "y", "z");
// Initialise gsl interpolation routine.
// Get unique entries of "x" and "y" for the grid and grid size.
std::vector<double> x_vec = tab["x"];
sort(x_vec.begin(), x_vec.end());
x_vec.erase(unique(x_vec.begin(), x_vec.end()), x_vec.end());
int nx = x_vec.size();
std::vector<double> y_vec = tab["y"];
sort(y_vec.begin(), y_vec.end());
y_vec.erase(unique(y_vec.begin(), y_vec.end()), y_vec.end());
int ny = y_vec.size();
int n_grid_pts = tab["z"].size();
if (nx*ny != n_grid_pts)
{
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+"'.");
}
const double* x = &x_vec[0];
const double* y = &y_vec[0];
// Allocate memory for "z" values array in gsl format
z = (double*) malloc(nx * ny * sizeof(double));
if (type == InterpolationOptions2D::bicubic)
{
spline = gsl_spline2d_alloc(gsl_interp2d_bicubic, nx, ny);
}
else if (type == InterpolationOptions2D::bilinear)
{
spline = gsl_spline2d_alloc(gsl_interp2d_bilinear, nx, ny);
}
else
{
DarkBit_error().raise(LOCAL_INFO, "ERROR! Interpolation type not known to class AxionInterpolator2D.");
}
x_acc = gsl_interp_accel_alloc();
y_acc = gsl_interp_accel_alloc();
// Determine first and last "x" and "y" values and grid step size.
x_lo = x_vec.front();
x_up = x_vec.back();
y_lo = y_vec.front();
y_up = y_vec.back();
double x_delta = (x_up-x_lo) / (nx-1);
double y_delta = (y_up-y_lo) / (ny-1);
// Intialise grid.
for (int i = 0; i < n_grid_pts; i++)
{
// Determine appropriate indices for the grid points.
double temp = (tab["x"][i]-x_lo) / x_delta;
int ind_x = (int) (temp+0.5);
temp = (tab["y"][i]-y_lo) / y_delta;
int ind_y = (int) (temp+0.5);
//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;
gsl_spline2d_set(spline, z, ind_x, ind_y, tab["z"][i]);
}
gsl_spline2d_init (spline, x, y, z, nx, ny);
}
// Default creator with dummy entries for the objects w/ memory allocation
AxionInterpolator2D::AxionInterpolator2D()
{
x_acc = gsl_interp_accel_alloc();
y_acc = gsl_interp_accel_alloc();
spline = gsl_spline2d_alloc(gsl_interp2d_bilinear, 2, 2);
z = (double*) malloc(2 * 2 * sizeof(double));
}
// Overloaded class creators for the AxionInterpolator class using the init function above.
AxionInterpolator2D::AxionInterpolator2D(std::string file, InterpolationOptions2D type) { init(file, type); }
// Routine to access interpolated values.
double AxionInterpolator2D::interpolate(double x, double y) { return gsl_spline2d_eval(spline, x, y, x_acc, y_acc); }
// Routine to check if a point is inside the interpolating box.
bool AxionInterpolator2D::is_inside_box(double x, double y) { return ((x >= x_lo) && (x <= x_up) && (y >= y_lo) && (y <= y_up)); }
/*! \brief H.E.S.S.-likelihood-related interpolation routines.
*/
// Auxillary function for a parabola (needed for H.E.S.S. likelihood approximation).
double parabola(double x, const double params[]) { return params[0]*x*x + params[1]*x + params[2]; }
// Auxillary function to return the appropriate intersection between a parabola and a line (needed for H.E.S.S. likelihood).
double intersect_parabola_line(double a, double b, double sign, const double pparams[])
{
const double x1 = -3.673776;
const double y1 = 0.4;
double x2 = a - pparams[1];
double temp1 = a*a + 4.0*b*pparams[0] - 2.0*a*pparams[1] + pparams[1]*pparams[1] - 4.0*pparams[0]*pparams[2];
x2 = x2 - sign*sqrt(temp1);
x2 = x2/(2.0*pparams[0]);
double y2 = parabola(x2, pparams);
temp1 = x1 - x2;
double temp2 = y1 - y2;
return sqrt(temp1*temp1 + temp2*temp2);
}
// HESS_Interpolator class: Provides a customised interpolation container for the H.E.S.S. likelihood.
class HESS_Interpolator
{
public:
// Class creator.
HESS_Interpolator(std::string file);
// Class destructor
~HESS_Interpolator();
// Delete copy constructor and assignment operator to avoid shallow copies
HESS_Interpolator(const HESS_Interpolator&) = delete;
HESS_Interpolator operator=(const HESS_Interpolator&) = delete;
// Container for the tabulated data.
ASCIItableReader interp_lnL;
// Routine to return interpolated log-likelihood values.
double lnL(double epsilon, double gamma);
private:
gsl_interp_accel *acc[17];
gsl_spline *spline[17];
};
// Class creator. Needs path to tabulated H.E.S.S. data.
HESS_Interpolator::HESS_Interpolator(std::string file)
{
// Initialise upper part of the likelihood interpolation (i.e. higher axion-photon coupling).
interp_lnL = ASCIItableReader(file);
interp_lnL.setcolnames("lnL16", "lnL15", "lnL14", "lnL13", "lnL12", "lnL11", "lnL10", "lnL9", "lnL8", "lnL7", "lnL6", "lnL5", "lnL4", "lnL3", "lnL2", "lnL1", "lnL0");
for (int i = 16; i >= 0; i--)
{
int pts = interp_lnL["lnL"+std::to_string(i)].size();
acc[i] = gsl_interp_accel_alloc ();
spline[i] = gsl_spline_alloc (gsl_interp_cspline, pts);
const double* epsvals = &interp_lnL["lnL"+std::to_string(i)][0];
if (pts==8) {
const double lnLvals [8] = {0., -2.30259, -2.99573, -4.60517, -4.60517, -2.99573, -2.30259, 0.};
gsl_spline_init (spline[i], epsvals, lnLvals, pts);
} else {
const double lnLvals [7] = {0., -2.30259, -2.99573, -4.60517, -2.99573, -2.30259, 0.};
gsl_spline_init (spline[i], epsvals, lnLvals, pts);
}
}
}
// Destructor
HESS_Interpolator::~HESS_Interpolator()
{
for (auto spl : spline)
gsl_spline_free (spl);
for (auto ac : acc)
gsl_interp_accel_free (ac);
}
// Rotuine to interpolate the H.E.S.S. log-likelihood values.
double HESS_Interpolator::lnL(double epsilon, double gamma)
{
// Parameters for the parabolae.
const double ppars00 [3] = {0.553040458173831, 3.9888540782199913, 6.9972958867687565};
const double ppars90 [3] = {1.2852894785722664, 9.42311266504736, 17.49643049277964};
const double ppars95 [3] = {1.4501115909461886, 10.647792383304218, 19.811978366687622};
double result = 0.0;
// Check if we are inside the constrained region.
if ((gamma > parabola(epsilon, ppars00)) && (gamma < 1.2) && (epsilon > -4.64) && (epsilon < -2.57))
{
// Check if we are in the upper part (higher coupling; interpolation using linear and cubic splines).
if (gamma > 0.4)
{
// Cubic interpolation in Epsilon.
int index_lo = floor((gamma-0.4)/0.05);
int index_hi = index_lo + 1;
double z_lo = 0.0, z_hi = 0.0;
// Only use interpolating function where needed.
if ( (epsilon > interp_lnL["lnL"+std::to_string(index_lo)].front()) && (epsilon < interp_lnL["lnL"+std::to_string(index_lo)].back()) )
{
z_lo = gsl_spline_eval (spline[index_lo], epsilon, acc[index_lo]);
}
if ( (epsilon > interp_lnL["lnL"+std::to_string(index_hi)].front()) && (epsilon < interp_lnL["lnL"+std::to_string(index_hi)].back()) )
{
z_hi = gsl_spline_eval (spline[index_hi], epsilon, acc[index_hi]);
}
// Linear interpolation in Gamma.
double a = static_cast<double>(index_hi) - (gamma-0.4)/0.05;
double b = 1.0 - a;
result = a*z_lo + b*z_hi;
// If not in the upper part, we must be in the lower part.
} else {
const double loglikevals [4] = {-4.60517, -2.99573, -2.30259, 0.0};
// Gamma values belonging to the likelihood values along symmetry line (in terms of distance to 0.4).
double gammavals [4] = {0.0, 0.134006, 0.174898, 0.592678};
double distance = 0.4 - gamma;
// Check if point is on a vertical line with the 99% C.L. point
if (fabs(epsilon + 3.673776) > 1e-6)
{
// Calculate distance of point
double a = -3.673776 - epsilon;
double b = (-3.673776*gamma - 0.4*epsilon)/a;
double temp1 = distance;
distance = sqrt(a*a + temp1*temp1);
a = temp1/a;
temp1 = GSL_SIGN(-3.673776 - epsilon);
double temp2 = intersect_parabola_line(a, b, temp1, ppars00);
// CAVE: There used to be problems with distance > 1.0; these should be fixed now. Otherwise: replace by min(dist,1).
distance = distance/temp2;
gammavals[3] = 1.0;
gammavals[2] = intersect_parabola_line(a, b, temp1, ppars90)/temp2;
gammavals[1] = intersect_parabola_line(a, b, temp1, ppars95)/temp2;
}
gsl_interp_accel *acc = gsl_interp_accel_alloc ();
gsl_spline *spline = gsl_spline_alloc (gsl_interp_cspline, 4);
gsl_spline_init (spline, gammavals, loglikevals, 4);
result = gsl_spline_eval (spline, distance, acc);
gsl_spline_free (spline);
gsl_interp_accel_free (acc);
}
}
// CAVE: There used to be a bug with log-likelihood > 0.0; this is fixed now, but still safeguard the result against roundoff errors.
return std::min(result,0.0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// Solar model and integration routines to calculate the expected Helioscope signals //
/////////////////////////////////////////////////////////////////////////////////////////////////
// SolarModel class: Provides a container to store a (tabulated) Solar model and functions to return its properties.
class SolarModel
{
public:
SolarModel();
SolarModel(std::string file);
~SolarModel();
SolarModel& operator=(SolarModel&&);
// Delete copy constructor and assignment operator to avoid shallow copies
SolarModel(const SolarModel&) = delete;
SolarModel operator=(const SolarModel&) = delete;
// Min. and max. radius of the solar model file (distance r from the centre of the Sun in units of the solar radius)
double r_lo, r_hi;
// Routine to return the screening parameter kappa^2 (kappa^-1 = Debye-Hueckel radius).
double kappa_squared(double r);
// Routine to return the temperature in keV.
double temperature_in_keV(double r);
// Routine to return the plasma frequency squared.
double omega_pl_squared(double r);
private:
ASCIItableReader data;
gsl_interp_accel *accel[3];
gsl_spline *linear_interp[3];
};
SolarModel::SolarModel() {}
SolarModel::SolarModel(std::string file)
{
data = ASCIItableReader(file);
int pts = data.getnrow();
// Terminate GAMBIT is number of columns is wrong; i.e. the wrong solar model file format.
if (data.getncol() != 35)
{
DarkBit_error().raise(LOCAL_INFO, "ERROR! Solar model file '"+file+"' not compatible with GAMBIT!\n"
" See [arXiv:1810.07192] or example file in 'DarkBit/data/' for the correct format.");
}
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",
"X_K", "X_Ca", "X_Sc", "X_Ti", "X_V", "X_Cr", "X_Mn", "X_Fe", "X_Co", "X_Ni");
// Extract the radius from the files (in units of the solar radius).
const double* radius = &data["radius"][0];
r_lo = data["radius"][0];
r_hi = data["radius"][pts-1];
// Extract the temperature from the files (has to be converted into keV) & calculate the screening scale kappa_s_squared.
// Initialise necessary variables for the screening scale calculation.
std::vector<double> temperature;
std::vector<double> kappa_s_sq;
std::vector<double> w_pl_sq;
// Multiplicative factor: (4 pi alpha_EM / atomic_mass_unit) x (1 g/cm^3) in units of keV^3
const double factor = 4.0*pi*alpha_EM*gsl_pow_3(1.0E+6*gev2cm)/((1.0E+9*eV2g)*atomic_mass_unit);
// 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).
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,
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};
// Ionisation of species i assuming full ionisation.
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,
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};
#ifdef AXION_DEBUG_MODE
std::cout << "DEBUGGING INFO for solar models:\nradius/Rsol T/K kappa_s^2/keV^2 omega_pl^2/keV^2" << std::endl;
#endif
// Linearly extrapolate the data in the solar model file to r = 0 if necessary.
if (r_lo > 0)
{
double r0 = data["radius"][0], r1 = data["radius"][1];
double t_intercept = (1.0E-3*K2eV)*(r0*data["temperature"][1]-r1*data["temperature"][0])/(r0-r1);
temperature.push_back(t_intercept);
double rho_intercept = (r0*data["rho"][1]-r1*data["rho"][0])/(r0-r1);
double sum = 0.0;
double ne = 0.0;
for (int j = 0; j < 29; j++)
{
double x_intercept = (r0* data[j+6][1]-r1* data[j+6][0])/(r0-r1);
double temp = x_intercept*Z_vals[j]/A_vals[j];
ne += temp;
sum += temp*(1.0 + Z_vals[j]);
}
double kss = factor*sum*rho_intercept/t_intercept;
kappa_s_sq.push_back(kss);
double wpls = factor*ne*rho_intercept/(1.0E+6*m_electron);
w_pl_sq.push_back(wpls);
#ifdef AXION_DEBUG_MODE
printf("%5.4f %1.6e %1.6e %1.6e\n", 0.0, t_intercept, kss, wpls);
#endif
}
// Calculate the necessary quantities -- T(r), kappa_s^2(r) and omega_pl^2(r) -- and store them internally.
for (int i = 0; i < pts; i++)
{
double sum = 0.0;
double ne = 0.0;
temperature.push_back((1.0E-3*K2eV)*data["temperature"][i]);
for (int j = 0; j < 29; j++)
{
double temp = data[j+6][i]*Z_vals[j]/A_vals[j];
ne += temp;
sum += temp*(1.0 + Z_vals[j]);
}
double kss = factor*sum*data["rho"][i]/temperature[i];
kappa_s_sq.push_back(kss);
double wpls = factor*ne*data["rho"][i]/(1.0E+6*m_electron);
w_pl_sq.push_back(wpls);
#ifdef AXION_DEBUG_MODE
printf("%5.4f %1.6e %1.6e %1.6e\n", data["radius"][i], temperature[i], kss, wpls);
#endif
}
// Set up the interpolating functions for temperature and screening scale.
accel[0] = gsl_interp_accel_alloc ();
linear_interp[0] = gsl_spline_alloc (gsl_interp_linear, pts);
const double* temp_vals = &temperature[0];
gsl_spline_init (linear_interp[0], radius, temp_vals, pts);
accel[1] = gsl_interp_accel_alloc ();
linear_interp[1] = gsl_spline_alloc (gsl_interp_linear, pts);
const double* kappa_squared_vals = &kappa_s_sq[0];
gsl_spline_init (linear_interp[1], radius, kappa_squared_vals, pts);
accel[2] = gsl_interp_accel_alloc ();
linear_interp[2] = gsl_spline_alloc (gsl_interp_linear, pts);
const double* omega_pl_squared_vals = &w_pl_sq[0];
gsl_spline_init (linear_interp[2], radius, omega_pl_squared_vals, pts);
logger() << LogTags::info << "Initialisation of solar model from file '"+file+"' complete!" << std::endl;
logger() << LogTags::debug << "Entries in model file: " << pts << " for solar radius in [" << data["radius"][0] << ", " << data["radius"][pts-1] << "]." << EOM;
}
// Move assignment operator
SolarModel& SolarModel::operator=(SolarModel &&model)
{
if (this != &model)
{
std::swap(data,model.data);
std::swap(accel,model.accel);
std::swap(linear_interp, model.linear_interp);
}
return *this;
}
// Class destructor
SolarModel::~SolarModel()
{
for (auto interp : linear_interp)
gsl_spline_free (interp);
for (auto acc : accel)
gsl_interp_accel_free (acc);
}
// Routine to return the temperature (in keV) of the zone around the distance r from the centre of the Sun.
double SolarModel::temperature_in_keV(double r) { return gsl_spline_eval(linear_interp[0], r, accel[0]); }
// Routine to return the screening paramter kappa^2 in units of keV^2 (kappa^-1 = Debye-Hueckel radius).
double SolarModel::kappa_squared(double r)
{
// Interpolated value, directly from the Solar model.
return gsl_spline_eval(linear_interp[1], r, accel[1]);
}
// Routine to return the plasma freqeuency squared (in keV^2) of the zone around the distance r from the centre of the Sun.
double SolarModel::omega_pl_squared(double r) { return gsl_spline_eval(linear_interp[2], r, accel[2]); }
// Constant numbers for precision etc.
const double abs_prec = 1.0E-1, rel_prec = 1.0E-6;
const int method = 5;
// Auxillary structure for passing the model parameters to the gsl solver.
struct SolarModel_params1 {double erg; double rad; SolarModel* sol;};
struct SolarModel_params2 {double erg; double rs; SolarModel* sol;};
struct SolarModel_params3 {double rs; double ma0; SolarModel* sol; AxionInterpolator* eff_exp;};
struct SolarModel_params4 {double ma0; AxionInterpolator* eff_exp; AxionInterpolator* gaee_flux;};
double rho_integrand (double rho, void * params)
{
// Retrieve parameters and other integration variables.
struct SolarModel_params1 * p1 = (struct SolarModel_params1 *)params;
double erg = (p1->erg);
double r = (p1->rad);
SolarModel* sol = (p1->sol);
// Get kappa_s^2, omega_plasma^2 and the temperature.
double ks_sq = sol->kappa_squared(rho);
double w_pl_sq = sol->omega_pl_squared(rho);
double T_in_keV = sol->temperature_in_keV(rho);
// Calculate the flux.
double x = 4.0*(erg*erg)/ks_sq;
double cylinder = rho*rho - r*r;
cylinder = rho/sqrt(cylinder);
double energy_factor = erg*sqrt(erg*erg - w_pl_sq)/gsl_expm1(erg/T_in_keV);
double rate = (ks_sq*T_in_keV)*((1.0 + 1.0/x)*gsl_log1p(x) - 1.0);
return cylinder*energy_factor*rate;
}
double rad_integrand(double rad, void * params)
{
// Retrieve and pass on parameters.
struct SolarModel_params2 * p2 = (struct SolarModel_params2 *)params;
SolarModel* sol = (p2->sol);
double rmax = std::min(1.0, sol->r_hi);
SolarModel_params1 p1 = {p2->erg, rad, sol};
gsl_integration_workspace * w = gsl_integration_workspace_alloc(1E6);
double result, error;
gsl_function F;
F.function = &rho_integrand;
F.params = &p1;
//gsl_set_error_handler_off();
gsl_integration_qag (&F, rad, rmax, 1e-2*abs_prec, 1e-2*rel_prec, 1E6, method, w, &result, &error);
//printf ("GSL status: %s\n", gsl_strerror (status));
//gsl_integration_qags(&F, rad, rmax, 1e-1*abs_prec, 1e-1*rel_prec, 1E6, w, &result, &error);
gsl_integration_workspace_free (w);
result = rad*result;
return result;
}
double erg_integrand(double erg, void * params)
{
const double eVm = gev2cm*1E7;
const double L = 9.26/eVm;
struct SolarModel_params3 * p3 = (struct SolarModel_params3 *)params;
SolarModel* sol = p3->sol;
double m_ax = p3->ma0;
double rs = p3->rs;
double argument = 0.25*1.0E-3*L*m_ax*m_ax/erg;
double temp = gsl_pow_2(gsl_sf_sinc(argument/pi));
double exposure = p3->eff_exp->interpolate(erg);
//std::cout << "Energy: " << erg << ", expoure = " << exposure << "." << std::endl;
SolarModel_params2 p2 = {erg, rs, sol};
gsl_integration_workspace * w = gsl_integration_workspace_alloc(1E6);
double result, error;
gsl_function F;
F.function = &rad_integrand;
F.params = &p2;
// Max. and min. integration radius
double rmin = sol->r_lo, rmax = std::min(rs, sol->r_hi);
gsl_integration_qag (&F, rmin, rmax, 1e-1*abs_prec, 1e-1*rel_prec, 1E6, method, w, &result, &error);
gsl_integration_workspace_free (w);
return temp*exposure*result;
}
double alt_erg_integrand(double erg, void * params)
{
const double eVm = gev2cm*1E7;
const double L = 9.26/eVm;
struct SolarModel_params4 * p4 = (struct SolarModel_params4 *)params;
double m_ax = p4->ma0;
double argument = 0.25*1.0E-3*L*m_ax*m_ax/erg;
double temp = gsl_pow_2(gsl_sf_sinc(argument/pi));
double exposure = p4->eff_exp->interpolate(erg);
double gaee_flux = p4->gaee_flux->interpolate(erg);
return temp*exposure*gaee_flux;
}
// Provides a customised interpolation container for the CAST likelihoods.
class CAST_SolarModel_Interpolator
{
public:
CAST_SolarModel_Interpolator(std::string solar_model_gagg, std::string solar_model_gaee, std::string data_set);
CAST_SolarModel_Interpolator(CAST_SolarModel_Interpolator&&);
~CAST_SolarModel_Interpolator();
// Delete copy constructor and assignment operator to avoid shallow copies
CAST_SolarModel_Interpolator(const CAST_SolarModel_Interpolator&) = delete;
CAST_SolarModel_Interpolator operator=(const CAST_SolarModel_Interpolator&) = delete;
std::vector<double> evaluate_gagg_contrib(double m_ax);
std::vector<double> evaluate_gaee_contrib(double m_ax);
private:
int n_bins;
ASCIItableReader gagg_data;
ASCIItableReader gaee_data;
ASCIItableReader eff_exp_data;
std::vector<gsl_interp_accel*> gagg_acc;
std::vector<gsl_interp_accel*> gaee_acc;
std::vector<gsl_spline*> gagg_linear_interp;
std::vector<gsl_spline*> gaee_linear_interp;
};
// Class creators for CAST_SolarModel_Interpolator
// Needs path to pre-claculated data for the "default" option.
CAST_SolarModel_Interpolator::CAST_SolarModel_Interpolator(std::string solar_model_gagg, std::string solar_model_gaee, std::string data_set)
{
const std::string darkbitdata_path = GAMBIT_DIR "/DarkBit/data/";
bool user_gagg_file_missing = true, user_gaee_file_missing = true;
logger() << LogTags::info << "Using solar models '"+solar_model_gagg+"' (axion-photon int.) and '"+solar_model_gaee+"' (axion-electron int.) for experiment '"+data_set+"'." << EOM;
// Check if a pre-computed a file for a given model exists.
user_gagg_file_missing = not(Utils::file_exists(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat"));
user_gaee_file_missing = not(Utils::file_exists(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat"));
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; }
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; }
// If either file does not exists, compute it.
if (user_gagg_file_missing || user_gaee_file_missing)
{
// Define the list of logarithmic masses log10(m_ax/keV) for the interpolating tables.
const int n_mass_bins = 183;
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,
-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,
-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,
-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,
-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,
-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,
-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,
-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.};
// Define quantities specific to CAST and the data set.
// 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)
const double prefactor_gagg = 29302.30262;
// prefactor_gaee = 10^13 * (10^-19 eV^-1)^2 * ((9.26 m/eVm) * (9.0 T/(T/eV^2) ))^2 / 4
// 10^13 = normalisation factor of files
const double prefactor_gaee = 1.701818353e-4;
// Lowest energy bin (in keV), bin size (in keV), and max. integration radius.
double bin_lo = 2.0, bin_delta = 0.5, rs = 1.0;
// Number of bins
int n_bins = 10;
if (data_set=="CAST2007") { bin_lo = 0.8; bin_delta = 0.3; rs = 0.231738; n_bins = 20; }
// Arrays to store the results in.
std::vector<double> gagg_counts (n_bins*n_mass_bins);
std::vector<double> gaee_counts (n_bins*n_mass_bins);
// Load the solar model.
// Solar radius R_Sol and D_Sol (= 1 a.u.) in 10^10 cm.
const double radius_sol = 6.9598, distance_sol = 1495.978707;
double temp = prefactor_gagg*gsl_pow_2(radius_sol/distance_sol)*radius_sol;
SolarModel model_gagg;
if (user_gagg_file_missing)
{
if (Utils::file_exists(darkbitdata_path+"SolarModel_"+solar_model_gagg+".dat"))
{
model_gagg = SolarModel(darkbitdata_path+"SolarModel_"+solar_model_gagg+".dat");
} else {
DarkBit_error().raise(LOCAL_INFO, "ERROR! No solar model file found for '"+solar_model_gagg+"'.\n"
" Check 'DarkBit/data' for files named 'SolarModel_*.dat' for available options *.");
}
}
// Load and interpolate effective exposure and the data for the axion-electron spectrum (with its nasty peaks).
AxionInterpolator eff_exposure (darkbitdata_path+"CAST/"+data_set+"_EffectiveExposure.dat");
AxionInterpolator gaee_spectrum;
if (user_gaee_file_missing)
{
if (Utils::file_exists(darkbitdata_path+"CAST/"+"Axion_Spectrum_"+solar_model_gaee+"_gaee.dat"))
{
gaee_spectrum = AxionInterpolator(darkbitdata_path+"CAST/"+"Axion_Spectrum_"+solar_model_gaee+"_gaee.dat");
} else {
DarkBit_error().raise(LOCAL_INFO, "ERROR! No spectrum file found for axion-electron interactions and model '"+solar_model_gaee+"'.\n"
" Check 'DarkBit/data' for files named 'Axion_Spectrum_*_gaee.dat' for available options *.");
}
}
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,
5.76064, 6.14217, 6.19863, 6.58874, 6.63942, 6.66482, 7.68441, 7.74104, 7.76785};
// Prepare integration routine by defining the gsl functions etc.
gsl_function F;
F.function = &erg_integrand;
gsl_function G;
G.function = &alt_erg_integrand;
double erg_lo = bin_lo, erg_hi = bin_lo;
logger() << LogTags::info << "Calculating reference counts for dataset '"+data_set+"'..." << EOM;
#ifdef AXION_DEBUG_MODE
std::cout << "DEBUGGING INFO for solar model integration:\n"
"Using model '"+solar_model_gagg+"' for axion-photon interactions,"
"and model '"+solar_model_gaee+"' for axion-electron interactions.\n\n"
"coupling log10(m/eV) [erg_low/keV, erg_high/keV] log10(counts)" << std::endl;
#endif
for (int bin = 0; bin < n_bins; bin++)
{
erg_lo = erg_hi;
erg_hi += bin_delta;
gsl_integration_workspace * v = gsl_integration_workspace_alloc (1E6);
gsl_integration_workspace * w = gsl_integration_workspace_alloc (1E6);
// Only take into account the peaks relevant for the current energy bin.
std::vector<double> relevant_peaks;
relevant_peaks.push_back(erg_lo);
for (int i = 0; i < 32; i++)
{
double temp = all_peaks[i];
if ( (erg_lo < temp) && (temp < erg_hi) ) { relevant_peaks.push_back(temp); }
}
relevant_peaks.push_back(erg_hi);
for (int i = 0; i < n_mass_bins; i++)
{
double gagg_result, gagg_error, gaee_result, gaee_error;
double m_ax = pow(10,log_masses[i]);
// Only perform integration if axion-photon counts file does not exist.
if (user_gagg_file_missing)
{
SolarModel_params3 p3 = {rs, m_ax, &model_gagg, &eff_exposure};
F.params = &p3;
gsl_integration_qag (&F, erg_lo, erg_hi, abs_prec, rel_prec, 1E6, method, v, &gagg_result, &gagg_error);
#ifdef AXION_OMP_DEBUG_MODE
printf("gagg | % 6.4f [%3.2f, %3.2f] % 4.3e\n", log10(m_ax), erg_lo, erg_hi, log10(temp*gagg_result));
#endif
gagg_counts[bin*n_mass_bins+i] = log10(temp*gagg_result);
}
// Only perform integration if axion-electron counts file does not exist.
if (user_gaee_file_missing)
{
SolarModel_params4 p4 = {m_ax, &eff_exposure, &gaee_spectrum};
G.params = &p4;
gsl_integration_qagp(&G, &relevant_peaks[0], relevant_peaks.size(), abs_prec, rel_prec, 1E6, w, &gaee_result, &gaee_error);
#ifdef AXION_OMP_DEBUG_MODE
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));
#endif
// Include efficiency factor from not integrating over the full Solar disc in CAST2007 here:
if (data_set=="CAST2007") { gaee_result = 0.826*gaee_result; }
gaee_counts[bin*n_mass_bins+i] = log10(prefactor_gaee*gaee_result);
}
}
gsl_integration_workspace_free (v);
gsl_integration_workspace_free (w);
}
// Write the results to a file (if the file does not yet exist).
if (user_gagg_file_missing)
{
std::string header = "########################################################################\n"
"# Reference Counts for Solar Model "+solar_model_gagg+std::string(std::max(0,36-static_cast<int>(solar_model_gagg.length())),' ')+"#\n"
"# Column 1: log10(Axion mass in eV) #\n"
"# n>1: log10(Reference photon counts in energy bin n-1) #\n"
"########################################################################\n";
std::ofstream gagg_file (darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat");
gagg_file << header;
gagg_file << std::fixed << std::scientific << std::setprecision(7);
for (int i = 0; i < n_mass_bins; i++)
{
gagg_file << log_masses[i];
for (int j = 0; j < n_bins; j++) { gagg_file << " " << gagg_counts[j*n_mass_bins+i]; }
if (i < n_mass_bins-1) { gagg_file << "\n"; }
}
gagg_file.close();
logger() << LogTags::info << "Output file '"+darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat"+"' written for axion-photon interactions." << EOM;
}
if (user_gaee_file_missing)
{
std::string header = "########################################################################\n"
"# Reference Counts for Solar Model "+solar_model_gaee+std::string(std::max(0,36-static_cast<int>(solar_model_gaee.length())),' ')+"#\n"
"# Column 1: log10(Axion mass in eV) #\n"
"# n>1: log10(Reference photon counts in energy bin n-1) #\n"
"########################################################################\n";
std::ofstream gaee_file (darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat");
gaee_file << header;
gaee_file << std::fixed << std::scientific << std::setprecision(7);
for (int i = 0; i < n_mass_bins; i++)
{
gaee_file << log_masses[i];
for (int j = 0; j < n_bins; j++) { gaee_file << " " << gaee_counts[j*n_mass_bins+i]; }
if (i < n_mass_bins-1) { gaee_file << "\n"; }
}
gaee_file.close();
logger() << LogTags::info << "Output file '"+darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gagg.dat"+"' written for axion-electron interactions." << EOM;
}
}
// Read in pre-integrated fluxes for the chosen models.
// 0-entry = mass values; remaining entries = counts in bins.
gagg_data = ASCIItableReader(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gagg+"_gagg.dat");
gaee_data = ASCIItableReader(darkbitdata_path+"CAST/"+data_set+"_ReferenceCounts_"+solar_model_gaee+"_gaee.dat");
n_bins = gagg_data.getncol() - 1;
// Point to the address of the first entry of masses.
const double* mass_gagg = &gagg_data[0][0];
const double* mass_gaee = &gaee_data[0][0];
for (int bin = 0; bin < n_bins; bin++)
{
// Determine the number of interpolated mass values.
int gagg_pts = gagg_data[bin+1].size();
int gaee_pts = gaee_data[bin+1].size();
gagg_acc.push_back( gsl_interp_accel_alloc () );
gaee_acc.push_back( gsl_interp_accel_alloc () );
gagg_linear_interp.push_back( gsl_spline_alloc (gsl_interp_linear, gagg_pts) );
gaee_linear_interp.push_back( gsl_spline_alloc (gsl_interp_linear, gaee_pts) );
// Get flux values and initialise splines.
const double* flux_gagg = &gagg_data[bin+1][0];
const double* flux_gaee = &gaee_data[bin+1][0];
gsl_spline_init (gagg_linear_interp[bin], mass_gagg, flux_gagg, gagg_pts);
gsl_spline_init (gaee_linear_interp[bin], mass_gaee, flux_gaee, gaee_pts);
}
}
// Move constructor
CAST_SolarModel_Interpolator::CAST_SolarModel_Interpolator(CAST_SolarModel_Interpolator &&interp) :
n_bins(std::move(interp.n_bins)),
gagg_data(std::move(interp.gagg_data)),
gaee_data(std::move(interp.gaee_data)),
eff_exp_data(std::move(interp.eff_exp_data)),
gagg_acc(std::move(interp.gagg_acc)),
gaee_acc(std::move(interp.gaee_acc)),
gagg_linear_interp(std::move(interp.gagg_linear_interp)),
gaee_linear_interp(std::move(interp.gaee_linear_interp))
{}
// Class destructor
CAST_SolarModel_Interpolator::~CAST_SolarModel_Interpolator()
{
for(auto gagg_li : gagg_linear_interp)
gsl_spline_free (gagg_li);
for(auto gagg_ac : gagg_acc)
gsl_interp_accel_free (gagg_ac);
for(auto gaee_li : gaee_linear_interp)
gsl_spline_free (gaee_li);
for(auto gaee_ac : gaee_acc)
gsl_interp_accel_free (gaee_ac);
}
// Returns reference value counts for the photon-axion contribution.
std::vector<double> CAST_SolarModel_Interpolator::evaluate_gagg_contrib(double m_ax)
{
std::vector<double> result;
double lgm = log10(m_ax);
// If m < 0.001 eV, just return the result for the result for the coherent limit.
lgm = std::max(-3.0, lgm);
// Only perform a calculation for valid masses.
if (lgm < 2.0)
{
for (int i = 0; i < n_bins; i++) { result.push_back(gsl_spline_eval(gagg_linear_interp[i], lgm, gagg_acc[i])); }
} else {
for (int i = 0; i < n_bins; i++) { result.push_back(0.0); }
}
return result;
}
// Returns reference value counts for the photon-axion contribution.
std::vector<double> CAST_SolarModel_Interpolator::evaluate_gaee_contrib(double m_ax)
{
std::vector<double> result;
double lgm = log10(m_ax);
// If m < 0.001 eV, just return the result for the result for the coherent limit.
lgm = std::max(-3.0, lgm);
// Only perform a calculation for valid masses.
if (lgm < 2.0)
{
for (int i = 0; i < n_bins; i++) { result.push_back(gsl_spline_eval(gaee_linear_interp[i], lgm, gaee_acc[i])); }
} else {
for (int i = 0; i < n_bins; i++) { result.push_back(0.0); }
}
return result;
}
// Use simplified version of Gaussian likelihood from GAMBIT Utils.
double gaussian_nuisance_lnL(double theo, double obs, double sigma) { return Stats::gaussian_loglikelihood(theo, obs, 0, sigma, false); }
////////////////////////////////////////////////////////
// //
// Miscellaneous Theory Results //
// //
////////////////////////////////////////////////////////
/*! \brief Various capabilities and functions to provide SM physics as well as QCD input for axions.
*
* Supported models: QCDAxion
*/
////////////////////////////////////////////////////////
// Effective relatvistic degrees of freedom //
////////////////////////////////////////////////////////
// Function to provide the effective relativistic degrees of freedom (for the Standard Model).
double gStar(double T)
{
// Needs log10(T/GeV) for interpolation.
double lgT = log10(T) - 3.0;
// Interpolated effective relatvistic d.o.f. based on 0910.1066, deviations < 0.5%
// Tabulated data: x = log10(T/GeV), y = gStar
static AxionInterpolator gR (GAMBIT_DIR "/DarkBit/data/gR_WantzShellard.dat", InterpolationOptions1D::cspline);
double res;
if (lgT > 3.0) {
res = gR.interpolate (2.99);
} else if (lgT < -5.0) {
res = gR.interpolate (-4.99);
} else {
res = gR.interpolate (lgT);
}
return res;
}
// Function to provide the effective relativistic entropic degrees of freedom (for the Standard Model).
double gStar_S(double T)
{
// Need log10(T/GeV) for interpolation.
double lgT = log10(T) - 3.0;
// Interpolated effective relatvistic d.o.f. based on 0910.1066, deviations < 0.5%
// Tabulated data: x = log10(T/GeV), y = gStar
static AxionInterpolator gS (GAMBIT_DIR "/DarkBit/data/gS_WantzShellard.dat", InterpolationOptions1D::cspline);
double res;
if (lgT > 3.0) {
res = gS.interpolate (2.99);
} else if (lgT < -5.0) {
res = gS.interpolate (-4.99);
} else {
res = gS.interpolate (lgT);
}
return res;
}
///////////////////////////////////////
// QCD-axion mass relation //
///////////////////////////////////////
// Capability function to provide a simple Gaussian nuisance likelihood for
// the zero-termperature mass of QCD axions.
void QCDAxion_ZeroTemperatureMass_Nuisance_lnL(double &result)
{
using namespace Pipes::QCDAxion_ZeroTemperatureMass_Nuisance_lnL;
double LambdaChi = *Param["LambdaChi"];
// Results from NLO calculations (1511.02867).
const double Lmu = 75.5;
const double Lsigma = 0.5;
result = gaussian_nuisance_lnL(Lmu, LambdaChi, Lsigma);
}
// Capability function to provide a simple Gaussian nuisance likelihood for
// the model-independent contribution to the axion-photon coupling for QCD axions.
void QCDAxion_AxionPhotonConstant_Nuisance_lnL(double &result)
{
using namespace Pipes::QCDAxion_AxionPhotonConstant_Nuisance_lnL;
double CaggQCD = *Param["CaggQCD"];
// Results from NLO calculations (1511.02867).
const double CaggQCDmu = 1.92;
const double CaggQCDsigma = 0.04;
result = gaussian_nuisance_lnL(CaggQCDmu, CaggQCD, CaggQCDsigma);
}
// Auxillary function for QCD nuisance likelihood below.
double log_chi (double T, double beta, double Tchi)
{
double result = 0.0;
if (T > Tchi) { result = -beta*log10(T/Tchi); }
return result;
}
// Capability function to provide a lieklihood for the temperature dependence of the QCD axion mass (doi:10.1038/nature20115).
void QCDAxion_TemperatureDependence_Nuisance_lnL(double &result)
{
using namespace Pipes::QCDAxion_TemperatureDependence_Nuisance_lnL;
double Tchi = *Param["Tchi"];
double beta = *Param["beta"];
// Results from lattice QCD (doi:10.1038/nature20115, Supplementary Material).
// We normalised their findings by dividing out their value for chi(T=0) and removed its contribution to the error.
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};
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};
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};
double dummy = 0.0;
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]); }
result = dummy;
}
/////////////////////////////////////////////
// //
// Axion Experiments //
// //
/////////////////////////////////////////////
/*! \brief Likelihoods for ALPS 1 (LSW), CAST (helioscopes), and ADMX, UF, RBF (haloscopes).
*
* Supported models: GeneralALP
*/
/////////////////////////////////
// ALPS 1 experiment //
/////////////////////////////////
// Generic functions to calculate the expected signal per frame(!) for any data run.
// Input: laser power, gas coefficient nm1 = n-1; result in no. of photons.
double ALPS1_signal_general(double power, double nm1, double m_ax, double gagg)
{
const double eVm = gev2cm*1E7;
// Photon energy in eV.
const double erg = 2.0*pi*eVm/532.0E-9;
const double len = 4.2;
// We include the uncertainty of the detection efficiency eff = 0.82(5) in the likelihood.
const double eff = 0.82;
double result = 0.0;
// CAVE: Approximations/conversion only valid/possible for m_ax << 2.33 eV (532 nm).
if (m_ax < 1.0)
{
// Effective photon mass and momentum transfer.
double m_ph_sq = 2.0*erg*erg*nm1;
double q = 0.5*(m_ax*m_ax+m_ph_sq)/(eVm*erg);
double factor = gsl_pow_4((gagg*1E17)*gsl_sf_sinc(0.5*q*len/pi));
// Prefactor: 1096 W * 1 h * (10^-17/eV * 4.98 T * 4.2 m)^4 / 16.
result = 0.00282962979*eff*factor*(power/1096.0)/erg;
}
return result;
}
// Specific capability to provide the expected signal from data run 1 (5 data frames).
void calc_ALPS1_signal_vac(double &result)
{
using namespace Pipes::calc_ALPS1_signal_vac;
double m_ax = *Param["ma0"];
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
result = ALPS1_signal_general(1096.0, 0.0, m_ax, gagg);
}
// Specific capability to provide the expected signal from data run 3 (8 data frames; 0.18 mbar).
void calc_ALPS1_signal_gas(double &result)
{
using namespace Pipes::calc_ALPS1_signal_gas;
double m_ax = *Param["ma0"];
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
result = ALPS1_signal_general(1044.0, 5.0E-8, m_ax, gagg);
}
// General likelihood function for the ALPS 1 experiment (given expected signal s = obs - bkg).
double ALPS1_lnL_general(double s, double mu, double sigma)
{
// Propagate uncertainty from efficiency in chi^2-likelihood.
const double rel_error = 0.05/0.82;
return -0.5*gsl_pow_2(s-mu)/(gsl_pow_2(rel_error*s)+gsl_pow_2(sigma));
}
// Capability to provide joint liklihood for all three data runs.
void calc_lnL_ALPS1(double &result)
{
using namespace Pipes::calc_lnL_ALPS1;
double s1 = *Dep::ALPS1_signal_vac;
double s2 = *Dep::ALPS1_signal_gas;
// ALPS Collaboration results (limits from this data published in 1004.1313).
// ALPS Collaboration results, vacuum, 5 frames.
const double exp_sig_mu_v1 = -4.01, exp_sig_std_v1 = 3.01;
// ALPS Collaboration results, vacuum, 6 frames.
const double exp_sig_mu_v2 = -2.35, exp_sig_std_v2 = 3.44;
// ALPS Collaboration results, vacuum combined(!), 11 frames (we keep them seperated).
//const double exp_sig_mu_vc = -3.29, exp_sig_std_vc = 2.27;
// ALPS Collaboration results, gas, 8 frames (P = 0.18 mbar).
const double exp_sig_mu_g = 3.98, exp_sig_std_g = 2.45;
double l1 = ALPS1_lnL_general(s1, exp_sig_mu_v1, exp_sig_std_v1);
double l2 = ALPS1_lnL_general(s1, exp_sig_mu_v2, exp_sig_std_v2);
double l3 = ALPS1_lnL_general(s2, exp_sig_mu_g, exp_sig_std_g);
result = l1 + l2 + l3;
}
//////////////////////////////////////////////////
// CAST experiment (vacuum runs only) //
//////////////////////////////////////////////////
// Calculates the signal prediction for the CAST experiment (CCD detector 2004).
void calc_CAST2007_signal_vac(std::vector<double> &result)
{
using namespace Pipes::calc_CAST2007_signal_vac;
double m_ax = *Param["ma0"];
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
double gaee = std::fabs(*Param["gaee"]);
// Initialise the Solar model calculator and get the reference counts for a given mass.
// Get Solar model we are working with; set default value here
static std::string solar_model_gagg = runOptions->getValueOrDef<std::string> ("AGSS09met", "solar_model_gagg");
static std::string solar_model_gaee = runOptions->getValueOrDef<std::string> ("AGSS09met_old", "solar_model_gaee");
static CAST_SolarModel_Interpolator lg_ref_counts (solar_model_gagg, solar_model_gaee, "CAST2007");
std::vector<double> lg_ref_counts_gagg = lg_ref_counts.evaluate_gagg_contrib(m_ax);
std::vector<double> lg_ref_counts_gaee = lg_ref_counts.evaluate_gaee_contrib(m_ax);
static int n_bins = lg_ref_counts_gagg.size();
std::vector<double> counts;
double dummy;
for (int i = 0; i < n_bins; i++)
{
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]);
counts.push_back(gsl_pow_2(gagg*1E19)*dummy);
}
result = counts;
}
// Calculates the signal prediction for the CAST experiment (all detectors in 1705.02290)
void calc_CAST2017_signal_vac(std::vector<std::vector<double>> &result)
{
using namespace Pipes::calc_CAST2017_signal_vac;
double m_ax = *Param["ma0"];
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
double gaee = std::fabs(*Param["gaee"]);
std::vector<std::vector<double>> res;
// Initialise the Solar model calculator and get the reference counts for a given mass.
// Get Solar model we are working with; set default value here
static std::string solar_model_gagg = runOptions->getValueOrDef<std::string> ("AGSS09met", "solar_model_gagg");
static std::string solar_model_gaee = runOptions->getValueOrDef<std::string> ("AGSS09met_old", "solar_model_gaee");
const int n_exps = 12;
const int n_bins = 10;
const std::string exp_names [n_exps] = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L"};
static std::vector<CAST_SolarModel_Interpolator> lg_ref_counts;
static bool lg_ref_counts_not_calculated = true;
if (lg_ref_counts_not_calculated)
{
for (int e = 0; e < n_exps; e++)
{
CAST_SolarModel_Interpolator dummy (solar_model_gagg, solar_model_gaee, "CAST2017_"+exp_names[e]);
lg_ref_counts.push_back(std::move(dummy));
}
}
lg_ref_counts_not_calculated = false;
for (int e = 0; e < n_exps; e++)
{
std::vector<double> lg_ref_counts_gagg = lg_ref_counts[e].evaluate_gagg_contrib(m_ax);
std::vector<double> lg_ref_counts_gaee = lg_ref_counts[e].evaluate_gaee_contrib(m_ax);
std::vector<double> counts;
double dummy;
for (int bin = 0; bin < n_bins; bin++)
{
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]);
counts.push_back(gsl_pow_2(gagg*1E19)*dummy);
}
res.push_back(counts);
}
result = res;
}
// General binned Poisson likelihood for the CAST experiment.
double CAST_lnL_general(std::vector<double> s, const std::vector<double> bkg_counts, const std::vector<int> sig_counts)
{
double result = 0.0;
int n_bins = s.size();
for (int i = 0; i < n_bins; i++)
{
double mu = s[i] + bkg_counts[i];
result += sig_counts[i]*gsl_sf_log(mu) - mu;
}
return result;
}
// Capability to provide CAST likelihood for hep-ex/0702006 .
void calc_lnL_CAST2007(double &result)
{
using namespace Pipes::calc_lnL_CAST2007;
std::vector<double> sig_vac = *Dep::CAST2007_signal_vac;
// CAST CCD 2004 vacuum data (based on hep-ex/0702006).
const int n_bins = 20;
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};
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,
1.247346181, 1.247346181, 2.286801272, 1.247346181};
// Only calculate norm once.
static double norm = 0.0;
static bool norm_not_calculated = true;
if (norm_not_calculated)
{
for (int i = 0; i < n_bins; i++) { norm += gsl_sf_lnfact(dat_vac[i]); }
}
norm_not_calculated = false;
result = CAST_lnL_general(sig_vac, bkg_vac, dat_vac) - norm;
}
// Capability to provide CAST likelihood for 1705.02290 .
void calc_lnL_CAST2017(double &result)
{
using namespace Pipes::calc_lnL_CAST2017;
std::vector<std::vector<double>> sig_vac = *Dep::CAST2017_signal_vac;
// CAST 2017 vacuum data (naming scheme based on the 10 data sets published in 1705.02290).
const int n_bins = 10;
const int n_exps = 12;
const std::vector<std::vector<int>> dat_vac_all { {0, 3, 3, 0, 0, 1, 3, 3, 3, 3},
{5, 5, 5, 3, 3, 0, 5, 2, 2, 1},
{3, 3, 1, 2, 2, 2, 4, 5, 4, 3},
{1, 5, 5, 2, 1, 2, 2, 5, 4, 0},
{2, 3, 2, 2, 2, 1, 0, 2, 1, 1},
{3, 5, 1, 4, 1, 2, 0, 3, 2, 2},
{3, 4, 4, 5, 1, 2, 3, 2, 3, 2},
{2, 1, 0, 1, 3, 2, 2, 3, 0, 1},
{1, 2, 2, 1, 3, 0, 0, 1, 4, 0},
{2, 1, 3, 1, 1, 0, 1, 1, 5, 5},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 2, 1, 0, 0, 0, 0, 0, 0, 0} };
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},
{3.68151, 4.86486, 4.99634, 3.55003, 2.49817, 3.28707, 2.89262, 3.68151, 3.48429, 3.41855},
{2.54573, 3.18216, 4.45502, 2.86394, 2.29116, 2.29116, 3.30945, 3.75495, 3.62766, 3.56402},
{2.72482, 5.5794, 3.95748, 2.40044, 2.27069, 2.33556, 3.37359, 3.43847, 3.24384, 3.11408},
{1.44613, 2.30066, 2.43213, 1.70906, 1.97199, 1.24893, 1.24893, 2.23493, 2.16919, 2.23493},
{1.30963, 2.94666, 2.35733, 2.55377, 2.02992, 1.50607, 2.16088, 2.75022, 2.29185, 2.29185},
{2.33334, 2.74167, 2.21667, 2.80001, 2.21667, 1.75001, 2.62501, 2.21667, 2.80001, 2.33334},
{1.74724, 2.37125, 2.68326, 1.62243, 2.05924, 1.74724, 1.49763, 1.74724, 1.18563, 2.24645},
{1.72998, 3.45995, 1.79405, 1.72998, 1.9222, 1.72998, 2.69107, 2.24256, 1.98627, 2.11442},
{1.89627, 2.25182, 2.96292, 1.4222, 1.65924, 1.65924, 1.95553, 2.1333, 1.71849, 2.07404},
{0.0150685, 0.0568493, 0.060274, 0.0150685, 0.0150685, 0.00753425, 0.0267123, 0.0150685, 0.0267123, 0.0116438},
{0.0409574, 0.226904, 0.243287, 0.0532447, 0.0188404, 0.0344043, 0.0417766, 0.0409574, 0.0409574, 0.0286702} };
// Only calculate norm once.
static double norm = 0.0;
static bool norm_not_calculated = true;
if (norm_not_calculated)
{
for (int bin = 0; bin < n_bins; bin++)
{
for (int e = 0; e < n_exps; e++) { norm += gsl_sf_lnfact(dat_vac_all[e][bin]); }
}
}
norm_not_calculated = false;
result = 0.0;
for (int e = 0; e < n_exps; e++) { result = result + CAST_lnL_general(sig_vac[e], bkg_vac_all[e], dat_vac_all[e]); }
result = result - norm;
}
/////////////////////////////////////////////
// Various haloscope experiments //
/////////////////////////////////////////////
// Capability to provide generic haloscope "signal" prediction.
// All current haloscope likelihoods are approximated. We only need the predicted signal power up to a constant of proportionality.
void calc_Haloscope_signal(double &result)
{
using namespace Pipes::calc_Haloscope_signal;
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
// Get the DM fraction in axions and the local DM density.
double fraction = *Dep::RD_fraction;
LocalMaxwellianHalo LocalHaloParameters = *Dep::LocalHalo;
double rho0 = LocalHaloParameters.rho0;
// Signal relative to a reference coupling and local DM density.
double s = gsl_pow_2(gagg/1.0E-24) * fraction * (rho0/0.45);
result = s;
}
/*! Approximated likelihood for the AxionDarkMatterEXperiment (ADMX).
*/
// ADMX approximated likelihood function for data from publications from 1998 to 2009.
void calc_lnL_Haloscope_ADMX1(double &result)
{
using namespace Pipes::calc_lnL_Haloscope_ADMX1;
double m_ax = *Param["ma0"];
// Calculate equivalent frequency in MHz.
const double eV_to_MHz = 1.0E-15/(2.0*pi*hbar);
double freq = m_ax*eV_to_MHz;
double l = 0.0;
// Initialise GSL histogram and flag.
static gsl_histogram *h = gsl_histogram_alloc(89);
static bool init_flag = false;
// Unless initialised already, read in digitised limits from 0910.5914.
if (not(init_flag))
{
FILE * f = fopen(GAMBIT_DIR "/DarkBit/data/ADMXLimitsHistogram.dat", "r");
gsl_histogram_fscanf (f, h);
fclose(f);
init_flag = true;
}
// Likelihood shape parameters based on limits from astro-ph/9801286.
const double a = 0.013060890;
const double b = 0.455482976;
if ((freq > gsl_histogram_min(h)) && (freq < gsl_histogram_max(h)))
{
size_t index;
gsl_histogram_find(h, freq, &index);
double s = *Dep::Haloscope_signal;
double s_ref = gsl_pow_2(gsl_histogram_get(h, index));
double s_rel = s/s_ref;
// Only apply contraints for a signal > threshold a.
if (s_rel > a) { l = -0.5 * gsl_pow_2( (s_rel - a)/b ); }
}
result = l;
}
// ADMX approximated likelihood function for data from 2018 paper (1804.05750).
void calc_lnL_Haloscope_ADMX2(double &result)
{
using namespace Pipes::calc_lnL_Haloscope_ADMX2;
// Rescale the axion mass to ueV.
double m_ax = 1.0E+6*(*Param["ma0"]);
double l = 0.0;
// ADMX 2018 90% C.L. exclusion limits; digitised from Fig. 4, 1804.05750.
static AxionInterpolator g_limits (GAMBIT_DIR "/DarkBit/data/ADMX2018Limits.dat");
// If we are within the avialable data range, calculate the limit.
if ( (m_ax > g_limits.lower()) && (m_ax < g_limits.upper()) )
{
double s = *Dep::Haloscope_signal;
// Get limit and rescale it to 1 sigma from the appropriate number of sigmas for 90% C.L. (1 d.o.f.).
double sigma_exp = gsl_pow_2(g_limits.interpolate(m_ax))/1.644817912489;
// Add systematics of 13% according to 1804.05750.
double var_exp = gsl_pow_2(sigma_exp);
double var_theo = gsl_pow_2(0.13*s);
double var_tot = var_exp + var_theo;
l = -0.5*gsl_pow_2(s)/var_tot;
}
result = l;
}
// University of Florida (UF) approximated likelihood function; Hagmann+ Phys. Rev. D 42, 1297(R) (1990).
void calc_lnL_Haloscope_UF(double &result)
{
using namespace Pipes::calc_lnL_Haloscope_UF;
// Rescale the axion mass to ueV.
double m = 1.0E+6*(*Param["ma0"]);
double l = 0.0;
// There are only limits between 5.4 and 5.9 ueV.
if ((m > 5.4) && (m < 5.9)) {
// Likelihood parameters based on information from Phys. Rev. D 42, 1297(R) (1990); correspond to power in 10^-22 W.
const double sigma = 2.859772;
// The "signal" needs to be rescaled to 0.2804 GeV/cm^3 (their reference value)
// and also to the reference DFSZ coupling strength gDFSZ^2 = 0.6188 x 10^-30 GeV^-2
// const double PowerDFSZ = 6.92;
//double s = (0.45/0.2804)*(PowerDFSZ/0.6188)*(*Dep::Haloscope_signal);
//double s = 0.035083106*(0.45/0.2804)*(*Dep::Haloscope_signal);
double s = 0.0273012*(*Dep::Haloscope_signal);
l = -0.5 * gsl_pow_2(s/sigma);
}
result = l;
}
// Rochester-Brookhaven-Fermi (RBF) approximated likelihood function; Phys. Rev. D 40, 3153 (1989).
void calc_lnL_Haloscope_RBF(double &result)
{
using namespace Pipes::calc_lnL_Haloscope_RBF;
// Rescale the axion mass to ueV.
double m = 1.0E+6*(*Param["ma0"]);
double l = 0.0;
// Results from Phys. Rev. D 40, 3153 (1989)
// The bins and results below (from Table I) appear to be inconsitent with Figure 14.
//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,
// 11.298638, 11.583999, 12.845377, 13.234130, 15.301962, 16.2655809};
// N_sigma values as defined in the paper.
//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};
// Proportionality factors ("sigma") inferred from Table I in in units of GeV^-1/cm^3.
//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,
// 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};
// The results below are derived from Fig. 14 (assuming N_sigma = 4 for all values).
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};
const double N_sigma = 4.0;
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};
// Likelihood shape parameters based on information from PRD 40 (1989) 3153.
// 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.
if ( ((m > bins.front()) && (m < 10.067396)) || ((m > 11.213613) && (m < bins.back())))
{
// Use the standard search algorthim to identify the bin index and use the appropriate values for the likelihood.
auto index = upper_bound(bins.begin(), bins.end(), m);
double sigma = eff_sigma[index-bins.begin()-1];
// Uncomment and comment out lines below to swap between implementations using Table I and Figure 14, respectively.
//double offset = sigma*N_sigma[index-bins.begin()-1];
double offset = sigma*N_sigma;
// The "signal" needs to be rescaled to 0.3 GeV/cm^3, which is their reference value.
double s = (0.45/0.3)*(*Dep::Haloscope_signal);
if (s > offset) {
l = -0.5 * gsl_pow_2( (s - offset)/sigma );
}
}
result = l;
}
///////////////////////////////////////////
// //
// Axion Cosmology //
// //
///////////////////////////////////////////
/*! \brief Capabilities relating to axion cosmology. Currently only provides the energy density in axions today due to the realignment mechanism.
*
* Supported models: GeneralALP
*/
//////////////////////////////////////////////////////////
// Energy density in realignment axions today //
//////////////////////////////////////////////////////////
/* Some auxillary functions for solving the necessary differential equations
*/
// Provides function F1 for the change in variables time -> temperature (see 1810.07192).
double SpecialFun1(double T)
{
// log10(T/GeV) required for interpolation.
double lgT = log10(T) - 3.0;
// Tabulated data: x = log10(T/GeV), y = F1(T); gR and gS from 0910.1066 .
static AxionInterpolator F1 (GAMBIT_DIR "/DarkBit/data/Axion_DiffEqnFun1.dat", InterpolationOptions1D::linear);
double res = -1.0;
if ((lgT > 3.0) && (lgT < -5.0)) { res = F1.interpolate (lgT); }
return res;
}
// Provides function F3 for the change in variables time -> temperature (see 1810.07192).
double SpecialFun3(double T)
{
// log10(T/GeV) required for interpolation.
double lgT = log10(T) - 3.0;
// Tabulated data: x = log10(T/GeV), y = F3(T); gR and gS from 0910.1066 .
static AxionInterpolator F3 (GAMBIT_DIR "/DarkBit/data/Axion_DiffEqnFun3.dat", InterpolationOptions1D::linear);
double res = 0.0;
if ((lgT > 3.0) && (lgT < -5.0)) { res = F3.interpolate (lgT); }
return res;
}
// Auxillary function to calculate the Hubble parameter in a radiation-dominated universe.
double hubble_rad_dom(double T)
{
// H(T)/eV, T/MeV, m_pl/10^12eV = m_pl/10^3 GeV
const double m_pl = m_planck_red*1.0E-3;
return 0.331153*sqrt(gStar(T))*T*T/m_pl;
}
// General form of the temperature-dependent axion mass.
double axion_mass_temp(double T, double beta, double Tchi)
{
double res = 1.0;
if (T > Tchi) { res = pow(T/Tchi,-0.5*beta); }
return res;
}
// Auxillary structure for passing the model parameters to the gsl solver.
struct AxionEDT_params {double ma0; double beta; double Tchi; double thetai; double Tosc;};
// Auxillary function with root Tosc, the temperature where the axion field oscillations start (defined by mA = 3H).
// Note that this is only to set the temperature scale of the problem. The differential equation is solved numerically around
// this point and the numerical factor in the definition is pure convention.
double equation_Tosc(double T, void *params)
{
// T/MeV, ma0/eV, m_pl/10^12eV = m_pl/10^3 GeV
const double m_pl = m_planck_red*1.0E-3;
struct AxionEDT_params * p1 = (struct AxionEDT_params *)params;
double ma0 = (p1->ma0);
double beta = (p1->beta);
double Tchi = (p1->Tchi);
double result = 1.0 - gStar(T)*gsl_pow_2(T*T*pi/(ma0*m_pl*axion_mass_temp(T, beta, Tchi)))/10.0;
return result;
}
// Capability function to solve equation_Tosc for Tosc.
void calc_AxionOscillationTemperature(double &result)
{
using namespace Pipes::calc_AxionOscillationTemperature;
double ma0 = *Param["ma0"];
double beta = *Param["beta"];
double Tchi = *Param["Tchi"];
// m_pl/10^12 eV = m_pl/10^3 GeV
const double m_pl = m_planck_red*1.0E-3;
// Initialising the parameter structure with known and dummy values.
AxionEDT_params params = {ma0, beta, Tchi, 0.0, 0.0};
// Use gsl implementation Brent's method to determine the oscillation temperature.
gsl_function F;
F.function = &equation_Tosc;
F.params = ¶ms;
// Set counters and initialise equation solver.
int status;
int iter = 0, max_iter = 1E6;
gsl_root_fsolver *s;
s = gsl_root_fsolver_alloc(gsl_root_fsolver_brent);
double r, r_up, r_lo;
// Calculate first estimate for the root bracketing [r_lo, r_up].
// Calculate best estimate for comparison. g(Tchi)^-0.25 = 0.49, g(Tchi)^-...=0.76
r = 0.49*pow((10.0/(pi*pi)) * gsl_pow_2(m_pl*ma0), 0.25);
// Compare to decide which branch of the equation is valid; T1 > Tchi or T1 < Tchi
if ( (r > Tchi) && (beta > 1.0E-10) )
{
r = 0.76*pow((10.0/(pi*pi)) * gsl_pow_2(m_pl*ma0) * pow(Tchi, beta), 1.0/(4.0+beta));
}
// Find appropriate values for r_lo and r_up
r_up = r;
r_lo = r;
while (GSL_FN_EVAL(&F,r_up) > 0.0) { r_up = 2.0*r_up; }
while (GSL_FN_EVAL(&F,r_lo) < 0.0) { r_lo = 0.5*r_lo; }
// Execute equation solver until we reach 10^-6 absolute precision.
gsl_root_fsolver_set(s, &F, r_lo, r_up);
do
{
iter++;
status = gsl_root_fsolver_iterate (s);
r = gsl_root_fsolver_root (s);
r_lo = gsl_root_fsolver_x_lower (s);
r_up = gsl_root_fsolver_x_upper (s);
status = gsl_root_test_interval (r_lo, r_up, 1.0E-6, 0.0);
} while (status == GSL_CONTINUE && iter < max_iter);
gsl_root_fsolver_free (s);
result = r;
}
/* Differential equation solver to calculate the axion energy density today */
// Initialise the quantities needed for the ODE solver from the gsl library.
// Define the system of differential equations as a function of relative temperature.
int scal_field_eq(double tau, const double y[], double f[], void *params)
{
struct AxionEDT_params * p = (struct AxionEDT_params *)params;
double ma0 = (p->ma0);
double beta = (p->beta);
double Tchi= (p->Tchi);
double Tosc = (p->Tosc);
double thetai = (p->thetai);
// f stores derivatives, y stores functions.
f[0] = y[1];
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;
return GSL_SUCCESS;
}
// Define the Jacobian for the system of differential equations.
int scal_field_eq_jac(double tau, const double y[], double *dfdy, double dfdt[], void *params)
{
//(void)(t); // avoid unused parameter warning.
struct AxionEDT_params * p = (struct AxionEDT_params *)params;
double ma0 = (p->ma0);
double beta = (p->beta);
double Tchi = (p->Tchi);
double Tosc = (p->Tosc);
double thetai = (p->thetai);
gsl_matrix_view dfdy_mat = gsl_matrix_view_array (dfdy, 2, 2);
gsl_matrix * m = &dfdy_mat.matrix;
// (i, j) entries for matrix m; last entry = df[i]/df[j].
gsl_matrix_set (m, 0, 0, 0);
gsl_matrix_set (m, 0, 1, 1);
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));
gsl_matrix_set (m, 1, 1, -SpecialFun3(-tau*Tosc) / (-tau));
dfdt[0] = 0.0;
dfdt[1] = 0.0;
return GSL_SUCCESS;
}
// Capability function to solve the differential equation for the energy density in axions today (in terms of the critical density).
void RD_oh2_Axions(double &result)
{
using namespace Pipes::RD_oh2_Axions;
double ma0 = *Param["ma0"];
double beta = *Param["beta"];
double Tchi = *Param["Tchi"];
double thetai = *Param["thetai"];
double fa = *Param["fa"];
double Tosc = *Dep::AxionOscillationTemperature;
if ( (thetai<-pi) || (thetai>3.0*pi) ) { DarkBit_error().raise(LOCAL_INFO, "ERROR! The parameter 'thetai' should be chosen from the interval [-pi,3pi]."); }
// 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.
if (thetai>pi) { thetai = thetai - 2.0*pi; }
// Only do computations if thetai > 0.
result = 0.0;
if (fabs(thetai) > 0)
{
// TCMB in MeV.
const double TCMB = *Dep::T_cmb*K2eV*1.0E-6;
// Critical energy density today * h^2 (in eV^4).
const double ede_crit_today = 3.0*2.69862E-11;
struct AxionEDT_params p = {ma0, beta, Tchi, thetai, Tosc};
// Function, Jacobian, number of dimensions + pointer to params.
gsl_odeiv2_system sys = {scal_field_eq, scal_field_eq_jac, 2, &p};
// Evolution from Temp = 1e5 x Tosc to Temp = 0.001 x Tosc.
double tau2 = -0.001, tau1 = -1E5;
// Initial conditions for (u and v = u') as functions of temperature:
double y[2] = {1.0, 0.0};
// Settings for the driver: pointer to the ODE system sys, the gsl method, initial step size,
// absolute accuracy in y[0] and y[1], relative accuracy in y[0] and y[1].
// Other possible choices: gsl_odeiv2_step_rk4 (classic), gsl_odeiv2_step_rk8pd, gsl_odeiv2_step_rkf45 (standard choices).
gsl_odeiv2_driver * d = gsl_odeiv2_driver_alloc_y_new (&sys, gsl_odeiv2_step_bsimp, -0.1*tau1, 1E-8, 1E-8);
// Numerically solve ODE by continuing the integration well into the harmonic and adiabatic regime (stopping conditions
// via check1 = |hat(theta)| and check2 = 3H/m need to be satisfied).
double new_step;
double check1 = 1.0, check2 = 1.0;
int i = 0;
#ifdef AXION_DEBUG_MODE
std::cout << "DEBUGGING INFO for relic density calculation:\n"
"#'temperature' theta dtheta/dtau" << std::endl;
#endif
do
{
i++;
new_step = -pow(10.0, 1.0 + (log10(-tau2)-1.0)*i/1000.0);
int status = gsl_odeiv2_driver_apply (d, &tau1, new_step, y);
if (status != GSL_SUCCESS) { std::cout << "Error, return value = " << d << std::endl; }
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))) ));
check2 = 3.0*hubble_rad_dom(-new_step*Tosc)/(ma0*axion_mass_temp(-new_step*Tosc,beta,Tchi));
#ifdef AXION_DEBUG_MODE
std::cout << -new_step << " " << thetai*y[0] << " " << -tau2*thetai*y[1] << std::endl;
#endif
} while ( ((check1>1.0E-2) || (check2>1.0E-3)) && (i<1E3) );
i++;
if (i>=1E+3)
{
std::ostringstream buffer;
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";
DarkBit_warning().raise(LOCAL_INFO, "WARNING! Maximum number of integration steps reached for energy density calculator!\n "+buffer.str());
}
// Calculate the axion energy density at the stopping point.
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)));
// Use conservation of entropy to scale the axion energy density to its present value (relative to the critical energy density).
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;
gsl_odeiv2_driver_free (d);
result = OmegaAh2;
}
}
//////////////////////////////////////////////
// //
// Axion Astrophysics //
// //
//////////////////////////////////////////////
/*! \brief Capabilities relating to astrophysical observations (R-parameter, H.E.S.S. telescope search, cooling hints).
*
* Supported models: GeneralALP
*/
///////////////////////////
// R-parameter //
///////////////////////////
// Capability function to calculate the R-parameter (1512.08108).
// Based and extending on Refs [11, 12, 13, 75] and 10.3204/DESY-PROC-2015-02/straniero_oscar in 1512.08108 .
void calc_RParameter(double &result)
{
using namespace Pipes::calc_RParameter;
double ma0 = *Param["ma0"];
double gaee2 = gsl_pow_2(gaee_conversion*std::fabs(*Param["gaee"]));
double gagg = 1.0E+10*std::fabs(*Param["gagg"]); // gagg needs to be in 10^-10 GeV^-1.
// Value for He-abundance Y from 1503.08146: <Y> = 0.2515(17).
const double Y = 0.2515;
// Use interpolation for the finite-mass correction.
static AxionInterpolator correction (GAMBIT_DIR "/DarkBit/data/Axions_RParameterCorrection.dat", InterpolationOptions1D::linear);
// Initialise an effective axion-photon coupling, valid for low masses.
double geff = gagg;
// Apply correction for higher mass values...
static double m_min = pow(10,correction.lower());
static double m_max = pow(10,correction.upper());
if ((ma0 > m_min) && (ma0 < m_max)) { geff *= pow(10, 0.5*correction.interpolate(log10(ma0))); }
// ... or set to zero if mass is too high.
if (ma0 >= m_max) { geff = 0.0; }
// Expressions only valid for gaee2 < 35.18 but limits should become stronger for gaee2 > 35.18 (but perhaps not gaee2 >> 35.18).
// Conservative approach: Constrain gaee2 > 35.18 at the level of gaee2 = 35.18.
if (gaee2 > 35.18) { gaee2 = 35.18; }
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;
}
// Capability function to calculate the likelihood for the R-parameter.
void calc_lnL_RParameter(double &result)
{
using namespace Pipes::calc_lnL_RParameter;
double Rtheo = *Dep::RParameter;
// Observed R values from astro-ph/0403600.
const double Robs = 1.39;
const double RobsErr = 0.03;
// Value for He-abundance Y from 1503.08146: <Y> = 0.2515(17).
const double YobsErrContrib = 7.3306*0.0017;
result = -0.5*gsl_pow_2(Rtheo - Robs)/(RobsErr*RobsErr+YobsErrContrib*YobsErrContrib);
}
/////////////////////////////////////////
// White Dwarf cooling hints //
/////////////////////////////////////////
// White Dwarf interpolator class
class WDInterpolator
{
public:
// Constructor
WDInterpolator(const std::vector<double> x, const std::vector<double> y, std::string correction_file, InterpolationOptions1D type = InterpolationOptions1D::linear)
{
period_change = AxionInterpolator(x, y, type);
correction = AxionInterpolator(correction_file, type);
}
// Evaluation function
double evaluate(double mrel, double x2)
{
double res = x2;
// For higher masses, reduce the effective coupling accordingly:
if (mrel > 100.0)
{
res *= 15.0 * exp(-mrel) * pow(mrel,2.5)/(M_SQRT2 * pow(pi,3.5));
} else if (mrel > 0.01) {
res *= pow(10,correction.interpolate(log10(mrel)));
}
// We only have predictions up to x2 = max value. Limits should get stronger for x2 > max value, so
// it is conservative to use the prediction for x2 = max value for x2 > max value.
res = std::min(res, period_change.upper());
res = period_change.interpolate(res);
return res;
}
private:
AxionInterpolator period_change;
AxionInterpolator correction;
};
// Capability function to compute the cooling likelihood of G117-B15A (1205.6180; observations from Kepler+ (2011)).
void calc_lnL_WDVar_G117B15A(double &result)
{
using namespace Pipes::calc_lnL_WDVar_G117B15A;
// Rescale coupling to be used in their model prediction.
double x2 = (1.0e+14 * std::fabs(*Param["gaee"]))/2.8;
x2 = x2*x2;
// Values for the model prediction provided by the authors.
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};
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};
const double err2 = 0.09*0.09;
const double obs = 4.19;
const double obs_err2 = 0.73*0.73;
static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_G117B15A.dat");
// Use interpolation for the finite-mass correction.
const double internal_temperature_keV = 1.19698;
double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
double pred = dPidt.evaluate(mrel, x2);
result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
}
// Capability function to compute the cooling likelihood of R548 (1211.3389 using T = 11630 K; observations from Mukadam+ (2012)).
void calc_lnL_WDVar_R548(double &result)
{
using namespace Pipes::calc_lnL_WDVar_R548;
// Rescale coupling to be used in their model prediction.
double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
x2 = x2*x2;
// Values for the model prediction provided by the authors.
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};
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};
const double err2 = 0.09*0.09;
const double obs = 3.3;
const double obs_err2 = 1.1*1.1;
static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_R548.dat");
// Use interpolation for the finite-mass correction.
const double internal_temperature_keV = 1.11447;
double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
double pred = dPidt.evaluate(mrel, x2);
result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
}
// Capability function to compute the cooling likelihood of PG1351+489 (1605.07668 & 1406.6034; using observations from Redaelli+ (2011)).
void calc_lnL_WDVar_PG1351489(double &result)
{
using namespace Pipes::calc_lnL_WDVar_PG1351489;
// Rescale coupling to be used in their model prediction.
double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
x2 = x2*x2;
// Values for the model prediction provided by the authors.
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};
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};
const double err2 = 0.5*0.5;
const double obs = 2.0;
const double obs_err2 = 0.9*0.9;
static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_PG1351489.dat");
// Use interpolation for the finite-mass correction.
const double internal_temperature_keV = 2.64273;
double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
double pred = dPidt.evaluate(mrel, x2);
result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
}
// Capability function to compute the cooling likelihood of L192 (1605.06458 using l=1 & k=2; observations from Sullivan+Chote (2015)).
void calc_lnL_WDVar_L192 (double &result)
{
using namespace Pipes::calc_lnL_WDVar_L192;
// Rescale coupling to be used in their model prediction.
double x2 = (1.0E+14 * std::fabs(*Param["gaee"]))/2.8;
x2 = x2*x2;
// Values for the model prediction provided by the authors.
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};
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};
const double err2 = 0.85*0.85;
const double obs = 3.0;
const double obs_err2 = 0.6*0.6;
static WDInterpolator dPidt (x2vals, dPidts, GAMBIT_DIR "/DarkBit/data/Axions_WDCorrection_L192.dat");
// Use interpolation for the finite-mass correction.
const double internal_temperature_keV = 1.04931;
double mrel = 0.001 * (*Param["ma0"]) / internal_temperature_keV;
double pred = dPidt.evaluate(mrel, x2);
result = -0.5 * gsl_pow_2(obs - pred) / (obs_err2 + err2);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// SN 1987A limits (from axion-photon conversion in the B-field of the Milky Way or axion-photon decay) //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Capability function to calculate the likelihood for SN 1987A (based on data from 25 to 100 MeV photons, interpreted
// by Chupp et al., Phys. Rev. Lett. 62, 505 (1989). Use 10 sec of data for conversion and 223 sec for decay.
void calc_lnL_SN1987A (double &result)
{
using namespace Pipes::calc_lnL_SN1987A;
double f_10s = *Dep::PhotonFluence_SN1987A_Conversion;
double f_223s = *Dep::PhotonFluence_SN1987A_Decay;
// Standard devations of the null observation.
const double sigma_10s = 0.2;
const double sigma_223s = 0.59333;
// Conversion and decay constraints are based on the same data but never apply at the same time.
// Pick the larger value of the ratio/stronger of the two constraints, as this will always be the relevant one.
double ratio = std::max(f_10s/sigma_10s, f_223s/sigma_223s);
result = -0.5*ratio*ratio;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// SN 1987A photon fluence (from axion-photon conversion in the B-field of the Milky Way) //
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Capability function to calculate the photon fluence from SN 1987A as a result of axion-photon
// conversion in the B-field of the Milky Way (based on arXiv:1410.3747).
void calc_PhotonFluence_SN1987A_Conversion (double &result)
{
using namespace Pipes::calc_PhotonFluence_SN1987A_Conversion;
double m = (1.0E+10*(*Param["ma0"]))/5.433430;
double g = (1.0E+12*std::fabs(*Param["gagg"]))/5.339450;
result = 0.570589*gsl_pow_4(g);
if (m > 1.0) { result = result*pow(m, -4.021046); }
}
////////////////////////////////////////////////////////////////////////
// SN 1987A photon fluence (from axion decay into photons) //
////////////////////////////////////////////////////////////////////////
// Capability function to calculate the photon fluence from SN 1987A as a result of axion decay.
// Based on MC simulations by Marie Lecroq & Sebastian Hoof (following arXiv:1702.02964).
void calc_PhotonFluence_SN1987A_Decay (double &result)
{
using namespace Pipes::calc_PhotonFluence_SN1987A_Decay;
double lgg = log10(std::fabs(*Param["gagg"]));
double lgm = log10(*Param["ma0"]);
result = 0.0;
// Initialise interpolation class with MC data from file.
static AxionInterpolator2D fluence (GAMBIT_DIR "/DarkBit/data/SN1987A_DecayFluence.dat");
if (fluence.is_inside_box(lgm,lgg)) { result = fluence.interpolate(lgm,lgg); };
}
//////////////////////////////////////////////////////////////////
// Spectral distortions (H.E.S.S. telescope searches) //
//////////////////////////////////////////////////////////////////
// Calculate the likelihood for H.E.S.S. data assuming conversion in the galaxy cluster magnetic field (GCMF, "Conservative" limits, 1311.3148).
void calc_lnL_HESS_GCMF (double &result)
{
using namespace Pipes::calc_lnL_HESS_GCMF;
double m_ax = *Param["ma0"];
double gagg = gagg_conversion*std::fabs(*Param["gagg"]); // gagg needs to be in eV^-1.
// Compute the domensionless parameters Epsilon and Gamma from the axion mass and axion-photon coupling (see 1311.3148).
const double c_epsilon = 0.071546787;
const double c_gamma = 0.015274036*370.0/sqrt(37.0);
double epsilon = log10(m_ax*c_epsilon) + 5.0;
double gamma = log10(gagg*c_gamma) + 20.0;
// Initialise the interpolation and extrapolation routies for the H.E.S.S. results.
static HESS_Interpolator interp (GAMBIT_DIR "/DarkBit/data/HESS_GCMF_Table.dat");
result = interp.lnL(epsilon, gamma);
}
/**
* @brief Capability for the XENON1T likelihood from 2006.10035
*
* The signal model consists of 3 components: Primakoff, ABC, and Fe57.
*/
void calc_lnL_XENON1T_Anomaly(double &result)
{
using namespace Pipes::calc_lnL_XENON1T_Anomaly;
// Rescale couplings and 3H abundance to the reference values used in 2006.10035 for convenience.
double gae = std::fabs(*Param["gaee"]) / 5.0e-12;
double gagamma = std::fabs(*Param["gagg"]) / 2.0e-10;
double gaN = std::fabs(*Param["gaN"]) / 1.0e-6;
double x_3H = *Param["x_3H"] / 6.2e-25;
double bkg_scale = 1.0 + *Param["delta_bkg"];
double eff = 1.0 + *Param["delta_eff"];
static bool include_inverse_Primakoff = runOptions->getValueOrDef<bool> (true, "include_inverse_Primakoff");
// XENON1T 2020 data (based on 2006.10035 and using an exposure of 0.65 tonne-years)
static const Eigen::ArrayXd observed = (Eigen::ArrayXd(29) <<
26., 61., 55., 47., 49.,
47., 44., 41., 40., 37.,
51., 41., 42., 51., 47.,
48., 24., 43., 42., 34.,
42., 40., 38., 53., 41.,
57., 39., 46., 35.).finished();
static const Eigen::ArrayXd bkg_tritium = (Eigen::ArrayXd(29) <<
4.54543769e+00, 8.60509728e+00, 8.94256482e+00, 8.61684767e+00, 8.02464466e+00,
7.29168481e+00, 6.48068011e+00, 5.65037508e+00, 4.81438779e+00, 3.97835836e+00,
3.17827210e+00, 2.43987288e+00, 1.78022901e+00, 1.21426641e+00, 7.54437371e-01,
4.13276721e-01, 1.95253807e-01, 7.58276424e-02, 2.37741495e-02, 1.17262241e-02,
4.76851304e-03, 1.65434695e-04, 5.42752619e-05, 5.22947241e-05, 5.03141863e-05,
4.83336485e-05, 4.63531107e-05, 4.43725729e-05, 4.23920351e-05).finished();
static const Eigen::ArrayXd bkg_other = (Eigen::ArrayXd(29) <<
22.07404375, 39.45186703, 41.83417331, 42.46968003, 42.78761694, 42.96532578,
43.13847573, 43.56505579, 44.1162301 , 44.04330642, 43.60594679, 43.40248223,
43.45031774, 43.49263918, 43.57084528, 43.66270961, 43.75990478, 43.85453193,
43.95159076, 44.04782058, 44.14510208, 44.24450247, 44.3406822 , 44.43638726,
44.5331988, 44.62865958, 44.72654689, 44.82382807, 44.91842725).finished();
static const Eigen::ArrayXd signal_ref_ABC = (Eigen::ArrayXd(29) <<
7.46283683e+01, 9.11300502e+01, 4.91874199e+01, 3.53433982e+01, 3.97196350e+01,
3.57128137e+01, 2.27540737e+01, 1.19536450e+01, 6.29278747e+00, 3.30948412e+00,
1.61495065e+00, 6.21479171e-01, 1.55434142e-01, 2.13046184e-02, 1.63362970e-03,
5.94631877e-05, 6.22346656e-07, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0).finished();
static const Eigen::ArrayXd signal_ref_primakoff = (Eigen::ArrayXd(29) <<
8.52269995e+00, 2.00539997e+01, 1.92433543e+01, 2.22113223e+01, 2.89487211e+01,
2.48618807e+01, 1.56554086e+01, 8.76011034e+00, 4.77088790e+00, 2.59966792e+00,
1.43054774e+00, 7.84191139e-01, 4.15162321e-01, 2.10029374e-01, 1.02847245e-01,
5.27333566e-02, 2.93001979e-02, 1.70759274e-02, 1.08781046e-02, 7.76743790e-03,
6.24916022e-03, 5.51837192e-03, 5.15693524e-03, 4.97968515e-03, 4.88717867e-03,
4.84242242e-03, 4.82038071e-03, 2.38442778e-03, 0).finished();
static const Eigen::ArrayXd signal_ref_fe57 = (Eigen::ArrayXd(29) <<
4.64697658e-22, 1.14417236e-18, 1.48421451e-15, 1.01555196e-12, 3.67061998e-10,
7.02067458e-08, 7.12116995e-06, 3.84028003e-04, 1.10431167e-02, 1.69885040e-01,
1.40292258e+00, 6.23942228e+00, 1.49856121e+01, 1.94718769e+01, 1.36959639e+01,
5.21070939e+00, 1.07020697e+00, 1.18324090e-01, 7.01902932e-03, 2.22639151e-04,
3.76396817e-06, 3.38186408e-08, 1.61083936e-10, 4.05908328e-13, 5.40175385e-16,
3.79105023e-19, 1.40152641e-22, 2.72678128e-26, 2.78978087e-30).finished();
static const Eigen::ArrayXd signal_ref_ABC_inv_p = (Eigen::ArrayXd(29) <<
6.90095435e+00, 1.39022260e+01, 1.15151093e+01, 7.97641672e+00, 5.52073353e+00,
4.15542096e+00, 2.85016524e+00, 1.77391643e+00, 1.07580666e+00, 6.28453363e-01,
3.26090878e-01, 1.31638303e-01, 3.77207469e-02, 7.52278937e-03, 1.06834615e-03,
1.12228106e-04, 9.07094259e-06, 5.85004243e-07, 3.10665723e-08, 1.39581067e-09,
5.42752207e-11, 1.86190569e-12, 5.72719620e-14, 1.60150437e-15, 4.11906863e-17,
9.84235043e-19, 2.20372014e-20, 4.65786516e-22, 9.35349973e-24).finished();
static const Eigen::ArrayXd signal_ref_primakoff_inv_p = (Eigen::ArrayXd(29) <<
8.80070525e-01, 3.29923191e+00, 4.56900949e+00, 4.56444853e+00, 3.82216381e+00,
2.86024934e+00, 1.98175269e+00, 1.29748018e+00, 8.13307028e-01, 4.92544613e-01,
2.90070188e-01, 1.66927792e-01, 9.42159402e-02, 5.23046562e-02, 2.86265445e-02,
1.54743414e-02, 8.27421198e-03, 4.38184245e-03, 2.30069622e-03, 1.19872791e-03,
6.20254770e-04, 3.18925270e-04, 1.63051423e-04, 8.29261227e-05, 4.19736901e-05,
2.11518187e-05, 1.06157318e-05, 5.30780091e-06, 2.64457315e-06).finished();
static const Eigen::ArrayXd signal_ref_fe57_inv_p = (Eigen::ArrayXd(29) <<
2.68534579e-23, 1.02403478e-19, 1.64401745e-16, 1.34346994e-13, 5.65333857e-11,
1.23357889e-08, 1.40118942e-06, 8.30825834e-05, 2.57975431e-03, 4.20954642e-02,
3.62322333e-01, 1.65087221e+00, 3.99391811e+00, 5.14060546e+00, 3.52226638e+00,
1.28362583e+00, 2.48258237e-01, 2.54009765e-02, 1.36995132e-03, 3.88033329e-05,
5.75235843e-07, 4.44951177e-09, 1.79118821e-11, 3.74451860e-14, 4.05797696e-17,
2.27644923e-20, 6.60289329e-24, 9.89294778e-28, 7.65058090e-32).finished();
static const double asimov = (observed * observed.log() - observed).sum();
const Eigen::ArrayXd bkg = x_3H * bkg_tritium + bkg_other;
Eigen::ArrayXd signal = gae * gae * (
signal_ref_ABC * gae * gae +
signal_ref_primakoff * gagamma * gagamma +
signal_ref_fe57 * gaN * gaN);
if (include_inverse_Primakoff)
{
signal = signal + gagamma * gagamma * (
signal_ref_ABC_inv_p * gae * gae +
signal_ref_primakoff_inv_p * gagamma * gagamma +
signal_ref_fe57_inv_p * gaN * gaN);
}
const Eigen::ArrayXd expected = eff * (bkg_scale * bkg + signal);
result = (observed * expected.log() - expected).sum() - asimov;
}
struct dRdE_params { double m; double sigma; };
double dRdE (double E, void * params)
{
struct dRdE_params * par = (struct dRdE_params *)params;
// Efficiency of the Xenon1T experiment from arXiv:2006.09721
// Columns: Energy [keV] | Efficiency [dimensionless]
static AxionInterpolator efficiency (GAMBIT_DIR "/DarkBit/data/XENON1T/efficiency.txt", InterpolationOptions1D::cspline);
return std::exp(-0.5*pow((E - par->m)/par->sigma,2))*efficiency.interpolate(E);
}
void calc_lnL_XENON1T_DM_Anomaly(double &result)
{
using namespace Pipes::calc_lnL_XENON1T_DM_Anomaly;
result = 0;
// Rescale couplings and 3H abundance to the reference values used in 2006.10035 for convenience.
double gae = std::fabs(*Param["gaee"]);
double ma = *Param["ma0"] / 1.0e3;
double x_3H = *Param["x_3H"] / 6.2e-25;
double bkg_scale = 1.0 + *Param["delta_bkg"];
double rel_eff = 1.0 + *Param["delta_eff"];
double dm_fraction = *Param["eta"];
LocalMaxwellianHalo LocalHaloParameters = *Dep::LocalHalo;
double rho0 = LocalHaloParameters.rho0;
// XENON1T 2020 data (based on 2006.10035 and using an exposure of 0.65 tonne-years)
static const Eigen::ArrayXd observed = (Eigen::ArrayXd(29) <<
26., 61., 55., 47., 49.,
47., 44., 41., 40., 37.,
51., 41., 42., 51., 47.,
48., 24., 43., 42., 34.,
42., 40., 38., 53., 41.,
57., 39., 46., 35.).finished();
static const Eigen::ArrayXd bkg_tritium = (Eigen::ArrayXd(29) <<
4.54543769e+00, 8.60509728e+00, 8.94256482e+00, 8.61684767e+00, 8.02464466e+00,
7.29168481e+00, 6.48068011e+00, 5.65037508e+00, 4.81438779e+00, 3.97835836e+00,
3.17827210e+00, 2.43987288e+00, 1.78022901e+00, 1.21426641e+00, 7.54437371e-01,
4.13276721e-01, 1.95253807e-01, 7.58276424e-02, 2.37741495e-02, 1.17262241e-02,
4.76851304e-03, 1.65434695e-04, 5.42752619e-05, 5.22947241e-05, 5.03141863e-05,
4.83336485e-05, 4.63531107e-05, 4.43725729e-05, 4.23920351e-05).finished();
static const Eigen::ArrayXd bkg_other = (Eigen::ArrayXd(29) <<
22.07404375, 39.45186703, 41.83417331, 42.46968003, 42.78761694,
42.96532578, 43.13847573, 43.56505579, 44.1162301 , 44.04330642,
43.60594679, 43.40248223, 43.45031774, 43.49263918, 43.57084528,
43.66270961, 43.75990478, 43.85453193, 43.95159076, 44.04782058,
44.14510208, 44.24450247, 44.3406822 , 44.43638726, 44.5331988 ,
44.62865958, 44.72654689, 44.82382807, 44.91842725).finished();
// Photoelectric cross section for Xe from https://dx.doi.org/10.18434/T48G6X
// Columns: Photon energy [MeV] | Photoelectric absorption [10^-28 m^2/atom]
static AxionInterpolator sigma_pe (GAMBIT_DIR "/DarkBit/data/XENON1T/photoelectric.txt");
gsl_integration_workspace * w = gsl_integration_workspace_alloc(1000);
if ( (ma >= 1.0) && (ma <= 30.0) )
{
// Energy resolution from 2003.03825
double energy_resolution = 0.15 + 31.71/sqrt(ma);
double sigma = ma * energy_resolution / 100.0;
const double exposure = 0.647309514*1000.0*365.0;
const double photoel_eff_conversion = 1.5e19/131.0;
double amplitude = dm_fraction * (rho0/0.3) * exposure * gae*gae * ma * photoel_eff_conversion*sigma_pe.interpolate(ma/1000.0);
gsl_function f;
struct dRdE_params params = {ma, sigma};
f.function = &dRdE;
f.params = ¶ms;
double dRdE_result, error;
std::vector<double> signal_vec;
for (int i = 0; i < 29; i++)
{
gsl_integration_qags (&f, 1.+i, 2.+i, 0, 1e-7, 1000, w, &dRdE_result, &error);
double s = amplitude * 1/sqrt(2*pi)/sigma * dRdE_result;
signal_vec.push_back(s);
}
gsl_integration_workspace_free (w);
static const double asimov = (observed * observed.log() - observed).sum();
const Eigen::ArrayXd bkg = x_3H * bkg_tritium + bkg_other;
const Eigen::ArrayXd signal = Eigen::ArrayXd::Map(signal_vec.data(), signal_vec.size());
const Eigen::ArrayXd expected = rel_eff * (bkg_scale * bkg + signal);
result = (observed * expected.log() - expected).sum() - asimov;
}
}
void calc_lnL_XENON1T_Anomaly_NuisanceParameters(double &result)
{
using namespace Pipes::calc_lnL_XENON1T_Anomaly_NuisanceParameters;
result = -0.5 * ( gsl_pow_2(*Param["delta_bkg"]/0.026) + gsl_pow_2(*Param["delta_eff"]/0.030) );
}
} // namespace DarkBit
} // namespace Gambit
Updated on 2024-07-18 at 13:53:34 +0000