aboutsummaryrefslogtreecommitdiff
path: root/src/calculate_limits.c
blob: c4e0528fe55ab3b780269cdf33da8413c987cbaf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/* Copyright (c) 2019, Anthony Latorre <tlatorre at uchicago>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include "sort.h"

typedef struct fargsort_element {
    double value;
    size_t index;
} fargsort_element;

typedef struct argsort_element {
    int value;
    size_t index;
} argsort_element;

int fargsort_compare(const void *a, const void *b)
{
    const double da =
/* Copyright (c) 2019, Anthony Latorre <tlatorre at uchicago>
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#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;
}