/* Copyright (c) 2019, Anthony Latorre * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "misc.h" #include #include /* for size_t */ #include #include "vector.h" #include #include static struct { int n; double f; } ln_table[LN_MAX + 1] = { {0,-INFINITY}, {1,0}, {2,0.69314718055994529}, {3,1.0986122886681098}, {4,1.3862943611198906}, {5,1.6094379124341003}, {6,1.791759469228055}, {7,1.9459101490553132}, {8,2.0794415416798357}, {9,2.1972245773362196}, {10,2.3025850929940459}, {11,2.3978952727983707}, {12,2.4849066497880004}, {13,2.5649493574615367}, {14,2.6390573296152584}, {15,2.7080502011022101}, {16,2.7725887222397811}, {17,2.8332133440562162}, {18,2.8903717578961645}, {19,2.9444389791664403}, {20,2.9957322735539909}, {21,3.044522437723423}, {22,3.0910424533583161}, {23,3.1354942159291497}, {24,3.1780538303479458}, {25,3.2188758248682006}, {26,3.2580965380214821}, {27,3.2958368660043291}, {28,3.3322045101752038}, {29,3.3672958299864741}, {30,3.4011973816621555}, {31,3.4339872044851463}, {32,3.4657359027997265}, {33,3.4965075614664802}, {34,3.5263605246161616}, {35,3.5553480614894135}, {36,3.5835189384561099}, {37,3.6109179126442243}, {38,3.6375861597263857}, {39,3.6635616461296463}, {40,3.6888794541139363}, {41,3.713572066704308}, {42,3.7376696182833684}, {43,3.7612001156935624}, {44,3.784189633918261}, {45,3.8066624897703196}, {46,3.8286413964890951}, {47,3.8501476017100584}, {48,3.8712010109078911}, {49,3.8918202981106265}, {50,3.912023005428146}, {51,3.9318256327243257}, {52,3.9512437185814275}, {53,3.970291913552122}, {54,3.9889840465642745}, {55,4.0073331852324712}, {56,4.0253516907351496}, {57,4.0430512678345503}, {58,4.0604430105464191}, {59,4.0775374439057197}, {60,4.0943445622221004}, {61,4.1108738641733114}, {62,4.1271343850450917}, {63,4.1431347263915326}, {64,4.1588830833596715}, {65,4.1743872698956368}, {66,4.1896547420264252}, {67,4.2046926193909657}, {68,4.219507705176107}, {69,4.2341065045972597}, {70,4.2484952420493594}, {71,4.2626798770413155}, {72,4.2766661190160553}, {73,4.290459441148391}, {74,4.3040650932041702}, {75,4.3174881135363101}, {76,4.3307333402863311}, {77,4.3438054218536841}, {78,4.3567088266895917}, {79,4.3694478524670215}, {80,4.3820266346738812}, {81,4.3944491546724391}, {82,4.4067192472642533}, {83,4.4188406077965983}, {84,4.4308167988433134}, {85,4.4426512564903167}, {86,4.4543472962535073}, {87,4.4659081186545837}, {88,4.4773368144782069}, {89,4.4886363697321396}, {90,4.499809670330265}, {91,4.5108595065168497}, {92,4.5217885770490405}, {93,4.5325994931532563}, {94,4.5432947822700038}, {95,4.5538768916005408}, {96,4.5643481914678361}, {97,4.5747109785033828}, {98,4.5849674786705723}, {99,4.5951198501345898}, {100,4.6051701859880918}, }; static struct { int n; double f; } ln_fact_table[LNFACT_MAX + 1] = { {0,0}, {1,0}, {2,0.69314718055994529}, {3,1.791759469228055}, {4,3.1780538303479458}, {5,4.7874917427820458}, {6,6.5792512120101012}, {7,8.5251613610654147}, {8,10.604602902745251}, {9,12.801827480081469}, {10,15.104412573075516}, {11,17.502307845873887}, {12,19.987214495661885}, {13,22.552163853123425}, {14,25.19122118273868}, {15,27.89927138384089}, {16,30.671860106080672}, {17,33.505073450136891}, {18,36.395445208033053}, {19,39.339884187199495}, {20,42.335616460753485}, {21,45.380138898476908}, {22,48.471181351835227}, {23,51.606675567764377}, {24,54.784729398112319}, {25,58.003605222980518}, {26,61.261701761002001}, {27,64.557538627006338}, {28,67.88974313718154}, {29,71.257038967168015}, {30,74.658236348830158}, {31,78.092223553315307}, {32,81.557959456115043}, {33,85.054467017581516}, {34,88.580827542197682}, {35,92.136175603687093}, {36,95.719694542143202}, {37,99.330612454787428}, {38,102.96819861451381}, {39,106.63176026064346}, {40,110.32063971475739}, {41,114.03421178146171}, {42,117.77188139974507}, {43,121.53308151543864}, {44,125.3172711493569}, {45,129.12393363912722}, {46,132.95257503561632}, {47,136.80272263732635}, {48,140.67392364823425}, {49,144.5657439463449}, {50,148.47776695177302}, {51,152.40959258449735}, {52,156.3608363030788}, {53,160.3311282166309}, {54,164.32011226319517}, {55,168.32744544842765}, {56,172.35279713916279}, {57,176.39584840699735}, {58,180.45629141754378}, {59,184.53382886144948}, {60,188.6281734236716}, {61,192.7390472878449}, {62,196.86618167289001}, {63,201.00931639928152}, {64,205.1681994826412}, {65,209.34258675253685}, {66,213.53224149456327}, {67,217.73693411395422}, {68,221.95644181913033}, {69,226.1905483237276}, {70,230.43904356577696}, {71,234.70172344281826}, {72,238.97838956183432}, {73,243.26884900298271}, {74,247.57291409618688}, {75,251.89040220972319}, {76,256.22113555000954}, {77,260.56494097186322}, {78,264.92164979855278}, {79,269.29109765101981}, {80,273.67312428569369}, {81,278.06757344036612}, {82,282.4742926876304}, {83,286.89313329542699}, {84,291.32395009427029}, {85,295.76660135076065}, {86,300.22094864701415}, {87,304.68685676566872}, {88,309.1641935801469}, {89,313.65282994987905}, {90,318.1526396202093}, {91,322.66349912672615}, {92,327.1852877037752}, {93,331.71788719692847}, {94,336.26118197919845}, {95,340.81505887079902}, {96,345.37940706226686}, {97,349.95411804077025}, {98,354.53908551944079}, {99,359.1342053695754}, {100,363.73937555556347}, }; double trapz(const double *y, double dx, size_t n) { /* Returns the integral of `y` using the trapezoidal rule assuming a * constant grid spacing `dx`. * * See https://en.wikipedia.org/wiki/Trapezoidal_rule. */ size_t i; double sum = 0.0; if (n < 2) return 0.0; sum = y[0]; for (i = 1; i < n-1; i++) { sum += 2*y[i]; } sum += y[n-1]; return sum*dx/2.0; } /* Compute the first intersection of a ray starting at `pos` with direction * `dir` and a sphere centered at the origin with radius `R`. The distance to * the intersection is stored in `l`. * * Returns 1 if the ray intersects the sphere, and 0 if it doesn't. * * Example: * * double l; * double pos[0] = {0,0,0}; * double dir[3] = {1,0,0}; * * if (intersect_sphere(pos,dir,PSUP_RADIUS,&l)) { * hit[0] = pos[0] + l*dir[0]; * hit[1] = pos[1] + l*dir[1]; * hit[2] = pos[2] + l*dir[2]; * printf("ray intersects sphere at %.2f %.2f %.2f\n", hit[0], hit[1], hit[2]); * } else { * printf("ray didn't intersect sphere\n"); * } * */ int intersect_sphere(double *pos, double *dir, double R, double *l) { double b, c; b = 2*DOT(dir,pos); c = DOT(pos,pos) - R*R; if (b*b - 4*c <= 0) { /* Ray doesn't intersect the sphere. */ *l = 0.0; return 0; } /* First, check the shorter solution. */ *l = (-b - sqrt(b*b - 4*c))/2; /* If the shorter solution is less than 0, check the second solution. */ if (*l < 0) *l = (-b + sqrt(b*b - 4*c))/2; /* If the distance is still negative, we didn't intersect the sphere. */ if (*l < 0) return 0; return 1; } void get_path_length(double *pos1, double *pos2, double R, double *l1, double *l2) { /* Returns the path length inside and outside a circle of radius `R` for a * ray starting at position `pos1` and ending at position `pos2`. * * The path length inside the sphere is stored in `l1` and the path length * outside the sphere is stored in `l2`. */ double dir[3], l, b, c, d1, d2; /* Calculate the vector from `pos1` to `pos2`. */ SUB(dir,pos2,pos1); l = NORM(dir); DIV(dir,l); b = 2*DOT(dir,pos1); c = DOT(pos1,pos1) - R*R; if (b*b - 4*c <= 0) { /* Ray doesn't intersect the sphere. */ *l1 = 0.0; *l2 = l; return; } d1 = (-b + sqrt(b*b - 4*c))/2; d2 = (-b - sqrt(b*b - 4*c))/2; if (d1 < 0) { /* Ray also doesn't intersect sphere. */ *l1 = 0.0; *l2 = l; } else if (d1 >= l && d2 < 0) { /* Ray also doesn't intersect sphere. */ *l1 = l; *l2 = 0.0; } else if (d2 < 0) { /* Ray intersects sphere once. */ *l1 = d1; *l2 = l-d1; } else if (d1 >= l && d2 >= l) { /* Ray doesn't intersect the sphere. */ *l1 = 0.0; *l2 = l; } else if (d1 >= l && d2 < l) { /* Ray intersects the sphere once. */ *l2 = d1; *l1 = l-d1; } else if (d1 < l && d2 < l) { /* Ray intersects the sphere twice. */ *l1 = d1-d2; *l2 = l-(d1-d2); } } double ln(unsigned int n) { /* Returns the logarithm of n. * * Uses a lookup table to return results for n < 100. */ if (n <= LN_MAX) return ln_table[n].f; return log(n); } double lnfact(unsigned int n) { /* Returns the logarithm of n!. * * Uses a lookup table to return results for n < 100. */ if (n <= LNFACT_MAX) return ln_fact_table[n].f; return gsl_sf_lnfact(n); } double kahan_sum(double *x, size_t n) { /* Returns the sum of the elements of `x` using the Kahan summation algorithm. * * See https://en.wikipedia.org/wiki/Kahan_summation_algorithm. */ size_t i; double sum, c, y, t; sum = 0.0; c = 0.0; for (i = 0; i < n; i++) { y = x[i] - c; t = sum + y; c = (t - sum) - y; sum = t; } return sum; } double interp2d(double x, double y, double *xp, double *yp, double *zp, size_t n1, size_t n2) { /* A fast bilinear interpolation routine which assumes that the values in * `xp` and `yp` are evenly spaced. * * `zp` should be a 2D array indexed as zp[i,j] = zp[i*n2 + j]. * * If x < xp[0], x > xp[n-1], y < yp[0], or y > yp[n-1] prints an error and * exits. * * See https://en.wikipedia.org/wiki/Bilinear_interpolation. */ size_t i, j; double q11, q12, q21, q22; double idx, idy; if (x < xp[0]) { fprintf(stderr, "x < xp[0]!\n"); exit(1); } if (y < yp[0]) { fprintf(stderr, "y < yp[0]!\n"); exit(1); } idx = 1.0/(xp[1]-xp[0]); i = (x-xp[0])*idx; if (i > n1-2) { fprintf(stderr, "i > n1 - 2!\n"); exit(1); } idy = 1.0/(yp[1]-yp[0]); j = (y-yp[0])*idy; if (j > n2-2) { fprintf(stderr, "j > n2 - 2!\n"); exit(1); } q11 = zp[i*n2+j]; q12 = zp[i*n2+j+1]; q21 = zp[(i+1)*n2+j]; q22 = zp[(i+1)*n2+j+1]; return (q11*(xp[i+1]-x)*(yp[j+1]-y) + q21*(x-xp[i])*(yp[j+1]-y) + q12*(xp[i+1]-x)*(y-yp[j]) + q22*(x-xp[i])*(y-yp[j]))*idx*idy; } double interp1d(double x, double *xp, double *yp, size_t n) { /* A fast interpolation routine which assumes that the values in `xp` are * evenly spaced. * * If x < xp[0] returns yp[0] and if x > xp[n-1] returns yp[n-1]. */ size_t i; double idx; if (x <= xp[0]) return yp[0]; idx = 1.0/(xp[1]-xp[0]); i = (x-xp[0])*idx; if (i > n-2) return yp[n-1]; return yp[i] + (yp[i+1]-yp[i])*(x-xp[i])*idx; } int isclose(double a, double b, double rel_tol, double abs_tol) { /* Returns 1 if a and b are "close". This algorithm is taken from Python's * math.isclose() function. * * See https://www.python.org/dev/peps/pep-0485/. */ return fabs(a-b) <= fmax(rel_tol*fmax(fabs(a),fabs(b)),abs_tol); } int allclose(double *a, double *b, size_t n, double rel_tol, double abs_tol) { /* Returns 1 if all the elements of a and b are "close". This algorithm is * taken from Python's math.isclose() function. * * See https://www.python.org/dev/peps/pep-0485/. */ size_t i; for (i = 0; i < n; i++) { if (!isclose(a[i],b[i],rel_tol,abs_tol)) return 0; } return 1; } double logsumexp(dou
#include <stdio.h>
#include <gsl/gsl_integration.h>
#include <math.h> /* For M_PI */

/* Mass of dark matter particle (MeV). */
double mass = 1000.0;

/* Decay length of mediator V (in mm). */
double decay_length = 1000e9;

/* Cross section for dark matter interaction (in mm^2). */
double dm_cross_section = 1e-30;

/* Approximate dark matter density in MeV/mm^3. From Tom Caldwell's thesis. */
double dm_density = 400e3;

/* Approximate dark matter velocity in mm/s. The true distribution is expected
 * to be a Maxwell Boltzmann distribution which is modulated annually by the
 * earth's rotation around the sun, but we just assume a single constant
 * velocity here. From Tom Caldwell's thesis page 26. */
double dm_velocity = 244e6;

/* Number density of scatterers in the Earth.
 *
 * FIXME: Currently just set to the number density of atoms in water. Need to
 * update this for rock, and in fact this will change near the detector since
 * there is water outside the AV. */
double number_density = 30e18; /* In 1/mm^3 */

/* From Google maps. Probably not very accurate, but should be good enough for
 * this calculation. */
double latitude = 46.471857;
double longitude = -81.186755;

/* Radius of the earth in mm. */
double radius_earth = 6.371e9;

/* Depth of the SNO detector in mm. Don't be fooled by all the digits. I just
 * converted 6800 feet -> mm. */
double sno_depth = 2072640;

/* Fiducial volume in mm. */
double radius_fiducial = 5000;

/* Cartesian coordinates of SNO in earth frame. They need to be global since
 * they are used in some functions. */
double x_sno[3];

double epsabs = 1e-1;
double epsrel = 1e-1;

double deg2rad(double deg)
{
    return deg*M_PI/180.0;
}

double rad2deg(double rad)
{
    return rad*180.0/M_PI;
}

/* Convert spherical coordinates to cartesian coordinates.
 *
 * See https://en.wikipedia.org/wiki/Spherical_coordinate_system. */
void sphere2cartesian(double r, double theta, double phi, double *x, double *y, double *z)
{
    *x = r*sin(theta)*cos(phi);
    *y = r*sin(theta)*sin(phi);
    *z = r*cos(theta);
}

/* Convert cartesian coordinates to spherical coordinates.
 *
 * See https://en.wikipedia.org/wiki/Spherical_coordinate_system. */
void cartesian2sphere(double x, double y, double z, double *r, double *theta, double *phi)
{
    *r = sqrt(x*x + y*y + z*z);
    *theta = acos(z/(*r));
    *phi = atan2(y,x);
}

void cross(double *a, double *b, double *c)
{
    c[0] = a[1]*b[2] - a[2]*b[1];
    c[1] = a[2]*b[0] - a[0]*b[2];
    c[2] = a[0]*b[1] - a[1]*b[0];
}

double dot(double *a, double *b)
{
    return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
}

double norm(double *a)
{
    return sqrt(dot(a,a));
}

void normalize(double *a)
{
    double n = norm(a);
    a[0] /= n;
    a[1] /= n;
    a[2] /= n;
}

/* Rotate a vector x around the vector dir by an angle theta. */
void rotate(double *result, double *x, double *dir, double theta)
{
    double a = dot(dir,x);
    double b[3];

    double sin_theta = sin(theta);
    double cos_theta = cos(theta);

    /* Make sure the direction vector is normalized. */
    normalize(dir);

    cross(x,dir,b);

    result[0] = x[0]*cos_theta + dir[0]*a*(1-cos_theta) + b[0]*sin_theta;
    result[1] = x[1]*cos_theta + dir[1]*a*(1-cos_theta) + b[1]*sin_theta;
    result[2] = x[2]*cos_theta + dir[2]*a*(1-cos_theta) + b[2]*sin_theta;
}

/* Rotate a vector in earth centered coordinates to SNO coordinates (doesn't do
 * the translation). */
void rotate_earth_to_sno(double *x_earth, double *x_sno)
{
    double dir[3];
    double z[3] = {0,0,1};

    cross(x_sno, z, dir);

    /* Normalize. */
    normalize(dir);

    double theta = acos(dot(x_sno,z)/norm(x_sno));

    rotate(x_sno, x_earth, dir, theta);
}

/* Integral over phi. */
double f3(double phi, void *params)
{
    double result, error;
    gsl_function F;
    double *data = (double *) params;
    data[5] = phi;
    double x[3];
    double r[3];
    double distance;

    /* Compute cartesian position in local SNO coordinates. */
    sphere2cartesian(data[3], data[4], data[5], &x[0], &x[1], &x[2]);

    /* Cartesian coordinates of gamma production offset in earth centered
     * coordinates .*/
    double *gamma_offset = data+6;

    /* Vector distance between integration in local coordinates and gamma
     * production point .*/
    r[0] = x_sno[0] + x[0] - gamma_offset[0];
    r[1] = x_sno[1] + x[1] - gamma_offset[1];
    r[2] = x_sno[2] + x[2] - gamma_offset[2];

    distance = norm(r);

    return exp(-distance/decay_length)/(4*M_PI*distance*distance*decay_length)*data[3]*data[3]*sin(data[4]);
}

/* Integral over theta. */
double f2(double theta, void *params)
{
    double result, error;
    gsl_function F;
    double *data = (double *) params;
    data[4] = theta;

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f3;
    F.params = params;

    gsl_integration_qags(&F, 0, 2*M_PI, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    return result;
}

/* Integral over r. */
double f1(double r, void *params)
{
    double result, error;
    gsl_function F;
    double *data = (double *) params;
    data[3] = r;

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f2;
    F.params = params;

    gsl_integration_qags(&F, 0, M_PI, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    return result;
}

double f4_earth(double phi_earth, void *params)
{
    double result, error;
    gsl_function F;
    double *data = (double *) params;
    data[2] = phi_earth;
    double gamma_offset[3];

    /* Compute the cartesian coordinates of the gamma production point in the
     * earth centered coordinates. */
    sphere2cartesian(data[0], data[1], data[2], &data[6], &data[7], &data[8]);

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f1;
    F.params = params;

    gsl_integration_qags(&F, 0, radius_fiducial, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    /* For now we assume the event rate is constant throughout the earth, so we
     * are implicitly assuming that the cross section is pretty small. */
    double flux = dm_velocity*dm_density/mass;

    return dm_cross_section*number_density*flux*result*data[0]*data[0]*sin(data[1]);
}

double f3_earth(double theta_earth, void *params)
{
    double result, error;
    gsl_function F;
    double *data = (double *) params;
    data[1] = theta_earth;

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f4_earth;
    F.params = params;

    gsl_integration_qags(&F, 0, 2*M_PI, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    return result;
}

double f2_earth(double r_earth, void *params)
{
    double result, error;
    double data[9];
    gsl_function F;
    data[0] = r_earth;

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f3_earth;
    F.params = (void *) data;

    gsl_integration_qags(&F, 0, M_PI, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    return result;
}

/* Returns the event rate in SNO for a self-destructing dark matter particle
 * with a mass of dm_mass, a dark photon decay length of gamma_length, and a
 * cross section of cs (in mm^2). */
double get_event_rate(double dm_mass, double gamma_length, double cs)
{
    double result, error;
    gsl_function F;

    gsl_integration_workspace *w = gsl_integration_workspace_alloc(1000);

    F.function = &f2_earth;
    F.params = NULL;

    /* For now we just use global variables. */
    mass = dm_mass;
    decay_length = gamma_length;
    dm_cross_section = cs;

    gsl_integration_qags(&F, 0, radius_earth, epsabs, epsrel, 1000, w, &result, &error);

    gsl_integration_workspace_free(w);

    return result;
}

int main(int argc, char **argv)
{
    /* Spherical angles for the SNO detector in the earth frame which has z
     * along the north and south poles and the x axis passing through Greenwich.
     * Should double check this. */
    double sno_theta = deg2rad(latitude + 90.0);
    double sno_phi = deg2rad(longitude);

    sphere2cartesian(radius_earth - sno_depth, sno_theta, sno_phi, x_sno, x_sno+1, x_sno+2);

    /* Calculate the event rate for a standard DM candidate with a mass of 1
     * GeV, and a mediator decay length of 1 m. */
    printf("event rate = %.18e Hz\n", get_event_rate(1000, 1000e9, 1e-30));

    return 0;
}