diff options
-rw-r--r-- | src/Makefile | 4 | ||||
-rw-r--r-- | src/e_water_liquid.txt | 89 | ||||
-rw-r--r-- | src/electron.c | 189 | ||||
-rw-r--r-- | src/electron.h | 9 | ||||
-rw-r--r-- | src/fit.c | 3 | ||||
-rw-r--r-- | src/likelihood.c | 239 | ||||
-rw-r--r-- | src/likelihood.h | 14 | ||||
-rw-r--r-- | src/muon.c | 169 | ||||
-rw-r--r-- | src/muon.h | 15 | ||||
-rw-r--r-- | src/path.c | 25 | ||||
-rw-r--r-- | src/path.h | 4 | ||||
-rw-r--r-- | src/pdg.c | 3 | ||||
-rw-r--r-- | src/pdg.h | 2 | ||||
-rw-r--r-- | src/proton.c | 180 | ||||
-rw-r--r-- | src/proton.h | 9 | ||||
-rw-r--r-- | src/proton_water_liquid.txt | 140 | ||||
-rw-r--r-- | src/test-path.c | 8 | ||||
-rw-r--r-- | src/test.c | 174 |
18 files changed, 1013 insertions, 263 deletions
diff --git a/src/Makefile b/src/Makefile index 377388c..62a95af 100644 --- a/src/Makefile +++ b/src/Makefile @@ -14,7 +14,7 @@ calculate_limits: calculate_limits.c solid_angle.o: solid_angle.c -test: test.o solid_angle.o optics.o muon.o vector.o quantum_efficiency.o pdg.o scattering.o misc.o mt19937ar.o sno_charge.o path.o random.o pmt_response.o db.o dict.o siphash.o +test: test.o solid_angle.o optics.o muon.o vector.o quantum_efficiency.o pdg.o scattering.o misc.o mt19937ar.o sno_charge.o path.o random.o pmt_response.o db.o dict.o siphash.o electron.o proton.o likelihood.o pmt.o test-vector: test-vector.o vector.o mt19937ar.o @@ -26,7 +26,7 @@ test-charge: test-charge.o sno_charge.o misc.o vector.o test-zebra: test-zebra.o zebra.o pack2b.o -fit: fit.o zebra.o likelihood.o pmt.o vector.o misc.o muon.o optics.o quantum_efficiency.o solid_angle.o pdg.o scattering.o zdab_utils.o pack2b.o sno_charge.o db.o dqxx.o dict.o siphash.o path.o pmt_response.o release.o +fit: fit.o zebra.o likelihood.o pmt.o vector.o misc.o muon.o optics.o quantum_efficiency.o solid_angle.o pdg.o scattering.o zdab_utils.o pack2b.o sno_charge.o db.o dqxx.o dict.o siphash.o path.o pmt_response.o release.o electron.o proton.o clean: rm -f *.o calculate_limits test Makefile.dep diff --git a/src/e_water_liquid.txt b/src/e_water_liquid.txt new file mode 100644 index 0000000..4e29104 --- /dev/null +++ b/src/e_water_liquid.txt @@ -0,0 +1,89 @@ +ESTAR: Stopping Powers and Range Tables for Electrons + +WATER, LIQUID + +Kinetic Collision Radiative Total CSDA Radiation D. Effect +Energy Stp. Pow. Stp. Pow. Stp. Pow. Range Yield Parameter +MeV MeV cm2/g MeV cm2/g MeV cm2/g g/cm2 + +1.000E-02 2.256E+01 3.898E-03 2.256E+01 2.515E-04 9.408E-05 0.000E+00 +1.250E-02 1.897E+01 3.927E-03 1.898E+01 3.728E-04 1.133E-04 0.000E+00 +1.500E-02 1.647E+01 3.944E-03 1.647E+01 5.147E-04 1.316E-04 0.000E+00 +1.750E-02 1.461E+01 3.955E-03 1.461E+01 6.762E-04 1.493E-04 0.000E+00 +2.000E-02 1.317E+01 3.963E-03 1.318E+01 8.566E-04 1.663E-04 0.000E+00 +2.500E-02 1.109E+01 3.974E-03 1.110E+01 1.272E-03 1.990E-04 0.000E+00 +3.000E-02 9.653E+00 3.984E-03 9.657E+00 1.756E-03 2.301E-04 0.000E+00 +3.500E-02 8.592E+00 3.994E-03 8.596E+00 2.306E-03 2.599E-04 0.000E+00 +4.000E-02 7.777E+00 4.005E-03 7.781E+00 2.919E-03 2.886E-04 0.000E+00 +4.500E-02 7.130E+00 4.018E-03 7.134E+00 3.591E-03 3.165E-04 0.000E+00 +5.000E-02 6.603E+00 4.031E-03 6.607E+00 4.320E-03 3.435E-04 0.000E+00 +5.500E-02 6.166E+00 4.046E-03 6.170E+00 5.103E-03 3.698E-04 0.000E+00 +6.000E-02 5.797E+00 4.062E-03 5.801E+00 5.940E-03 3.955E-04 0.000E+00 +7.000E-02 5.207E+00 4.098E-03 5.211E+00 7.762E-03 4.453E-04 0.000E+00 +8.000E-02 4.757E+00 4.138E-03 4.761E+00 9.773E-03 4.931E-04 0.000E+00 +9.000E-02 4.402E+00 4.181E-03 4.407E+00 1.196E-02 5.393E-04 0.000E+00 +1.000E-01 4.115E+00 4.228E-03 4.119E+00 1.431E-02 5.842E-04 0.000E+00 +1.250E-01 3.591E+00 4.355E-03 3.596E+00 2.083E-02 6.912E-04 0.000E+00 +1.500E-01 3.238E+00 4.494E-03 3.242E+00 2.817E-02 7.926E-04 0.000E+00 +1.750E-01 2.984E+00 4.643E-03 2.988E+00 3.622E-02 8.894E-04 0.000E+00 +2.000E-01 2.793E+00 4.801E-03 2.798E+00 4.488E-02 9.826E-04 0.000E+00 +2.500E-01 2.528E+00 5.141E-03 2.533E+00 6.372E-02 1.161E-03 0.000E+00 +3.000E-01 2.355E+00 5.514E-03 2.360E+00 8.421E-02 1.331E-03 0.000E+00 +3.500E-01 2.235E+00 5.914E-03 2.241E+00 1.060E-01 1.496E-03 0.000E+00 +4.000E-01 2.148E+00 6.339E-03 2.154E+00 1.288E-01 1.658E-03 0.000E+00 +4.500E-01 2.083E+00 6.787E-03 2.090E+00 1.523E-01 1.818E-03 0.000E+00 +5.000E-01 2.034E+00 7.257E-03 2.041E+00 1.766E-01 1.976E-03 0.000E+00 +5.500E-01 1.995E+00 7.747E-03 2.003E+00 2.013E-01 2.134E-03 1.103E-02 +6.000E-01 1.963E+00 8.254E-03 1.972E+00 2.265E-01 2.292E-03 2.938E-02 +7.000E-01 1.917E+00 9.313E-03 1.926E+00 2.778E-01 2.608E-03 7.435E-02 +8.000E-01 1.886E+00 1.042E-02 1.896E+00 3.302E-01 2.928E-03 1.267E-01 +9.000E-01 1.864E+00 1.159E-02 1.876E+00 3.832E-01 3.251E-03 1.835E-01 +1.000E+00 1.849E+00 1.280E-02 1.862E+00 4.367E-01 3.579E-03 2.428E-01 +1.250E+00 1.829E+00 1.600E-02 1.845E+00 5.717E-01 4.416E-03 3.944E-01 +1.500E+00 1.822E+00 1.942E-02 1.841E+00 7.075E-01 5.281E-03 5.437E-01 +1.750E+00 1.821E+00 2.303E-02 1.844E+00 8.432E-01 6.171E-03 6.866E-01 +2.000E+00 1.824E+00 2.678E-02 1.850E+00 9.785E-01 7.085E-03 8.218E-01 +2.500E+00 1.834E+00 3.468E-02 1.868E+00 1.247E+00 8.969E-03 1.069E+00 +3.000E+00 1.846E+00 4.299E-02 1.889E+00 1.514E+00 1.092E-02 1.288E+00 +3.500E+00 1.858E+00 5.164E-02 1.910E+00 1.777E+00 1.291E-02 1.484E+00 +4.000E+00 1.870E+00 6.058E-02 1.931E+00 2.037E+00 1.495E-02 1.660E+00 +4.500E+00 1.882E+00 6.976E-02 1.951E+00 2.295E+00 1.702E-02 1.821E+00 +5.000E+00 1.892E+00 7.917E-02 1.971E+00 2.550E+00 1.911E-02 1.967E+00 +5.500E+00 1.902E+00 8.876E-02 1.991E+00 2.802E+00 2.123E-02 2.102E+00 +6.000E+00 1.911E+00 9.854E-02 2.010E+00 3.052E+00 2.336E-02 2.227E+00 +7.000E+00 1.928E+00 1.185E-01 2.047E+00 3.545E+00 2.766E-02 2.453E+00 +8.000E+00 1.943E+00 1.391E-01 2.082E+00 4.030E+00 3.200E-02 2.652E+00 +9.000E+00 1.956E+00 1.601E-01 2.116E+00 4.506E+00 3.636E-02 2.831E+00 +1.000E+01 1.968E+00 1.814E-01 2.149E+00 4.975E+00 4.072E-02 2.992E+00 +1.250E+01 1.993E+00 2.362E-01 2.230E+00 6.117E+00 5.163E-02 3.341E+00 +1.500E+01 2.014E+00 2.926E-01 2.306E+00 7.219E+00 6.243E-02 3.633E+00 +1.750E+01 2.031E+00 3.501E-01 2.381E+00 8.286E+00 7.309E-02 3.885E+00 +2.000E+01 2.046E+00 4.086E-01 2.454E+00 9.320E+00 8.355E-02 4.107E+00 +2.500E+01 2.070E+00 5.277E-01 2.598E+00 1.130E+01 1.039E-01 4.487E+00 +3.000E+01 2.089E+00 6.489E-01 2.738E+00 1.317E+01 1.233E-01 4.806E+00 +3.500E+01 2.105E+00 7.716E-01 2.876E+00 1.496E+01 1.418E-01 5.082E+00 +4.000E+01 2.118E+00 8.955E-01 3.013E+00 1.665E+01 1.594E-01 5.326E+00 +4.500E+01 2.129E+00 1.021E+00 3.150E+00 1.828E+01 1.762E-01 5.544E+00 +5.000E+01 2.139E+00 1.146E+00 3.286E+00 1.983E+01 1.923E-01 5.741E+00 +5.500E+01 2.148E+00 1.273E+00 3.421E+00 2.132E+01 2.076E-01 5.921E+00 +6.000E+01 2.156E+00 1.400E+00 3.556E+00 2.276E+01 2.222E-01 6.087E+00 +7.000E+01 2.170E+00 1.656E+00 3.827E+00 2.547E+01 2.496E-01 6.383E+00 +8.000E+01 2.182E+00 1.914E+00 4.096E+00 2.799E+01 2.747E-01 6.641E+00 +9.000E+01 2.193E+00 2.173E+00 4.366E+00 3.035E+01 2.978E-01 6.871E+00 +1.000E+02 2.202E+00 2.434E+00 4.636E+00 3.258E+01 3.192E-01 7.077E+00 +1.250E+02 2.222E+00 3.089E+00 5.311E+00 3.761E+01 3.662E-01 7.516E+00 +1.500E+02 2.238E+00 3.749E+00 5.987E+00 4.204E+01 4.060E-01 7.876E+00 +1.750E+02 2.251E+00 4.412E+00 6.663E+00 4.600E+01 4.401E-01 8.182E+00 +2.000E+02 2.263E+00 5.078E+00 7.341E+00 4.957E+01 4.698E-01 8.447E+00 +2.500E+02 2.282E+00 6.416E+00 8.698E+00 5.582E+01 5.190E-01 8.891E+00 +3.000E+02 2.297E+00 7.760E+00 1.006E+01 6.116E+01 5.584E-01 9.254E+00 +3.500E+02 2.311E+00 9.107E+00 1.142E+01 6.583E+01 5.908E-01 9.561E+00 +4.000E+02 2.322E+00 1.046E+01 1.278E+01 6.996E+01 6.180E-01 9.827E+00 +4.500E+02 2.332E+00 1.181E+01 1.414E+01 7.368E+01 6.412E-01 1.006E+01 +5.000E+02 2.341E+00 1.317E+01 1.551E+01 7.706E+01 6.613E-01 1.027E+01 +5.500E+02 2.349E+00 1.453E+01 1.688E+01 8.014E+01 6.789E-01 1.046E+01 +6.000E+02 2.357E+00 1.589E+01 1.824E+01 8.299E+01 6.945E-01 1.064E+01 +7.000E+02 2.370E+00 1.861E+01 2.098E+01 8.810E+01 7.209E-01 1.094E+01 +8.000E+02 2.381E+00 2.133E+01 2.371E+01 9.258E+01 7.425E-01 1.121E+01 +9.000E+02 2.391E+00 2.406E+01 2.645E+01 9.657E+01 7.605E-01 1.145E+01 +1.000E+03 2.400E+00 2.679E+01 2.919E+01 1.002E+02 7.759E-01 1.166E+01 diff --git a/src/electron.c b/src/electron.c new file mode 100644 index 0000000..e8e24d3 --- /dev/null +++ b/src/electron.c @@ -0,0 +1,189 @@ +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_spline.h> +#include <math.h> +#include "optics.h" +#include "quantum_efficiency.h" +#include "solid_angle.h" +#include "pdg.h" +#include "vector.h" +#include "electron.h" +#include "sno.h" +#include "scattering.h" +#include "pmt_response.h" +#include "misc.h" + +static int initialized = 0; + +static double *x, *dEdx, *csda_range; +static size_t size; + +static gsl_interp_accel *acc_dEdx; +static gsl_spline *spline_dEdx; + +static gsl_interp_accel *acc_range; +static gsl_spline *spline_range; + +/* Electron critical energy in H2O and D2O. + * + * These values come from + * http://pdgprod.lbl.gov/~deg/AtomicNuclearProperties/HTML/deuterium_oxide_liquid.html for D2O and + * http://pdg.lbl.gov/2018/AtomicNuclearProperties/HTML/water_liquid.html for H2O. + */ +static const double ELECTRON_CRITICAL_ENERGY_H2O = 78.33; +static const double ELECTRON_CRITICAL_ENERGY_D2O = 78.33; + +static int init() +{ + int i, j; + char line[256]; + char *str; + double value; + int n; + + FILE *f = fopen("e_water_liquid.txt", "r"); + + if (!f) { + fprintf(stderr, "failed to open e_water_liquid.txt: %s\n", strerror(errno)); + return -1; + } + + i = 0; + n = 0; + /* For the first pass, we just count how many values there are. */ + while (fgets(line, sizeof(line), f)) { + size_t len = strlen(line); + if (len && (line[len-1] != '\n')) { + fprintf(stderr, "got incomplete line on line %i: '%s'\n", i, line); + goto err; + } + + i += 1; + + /* Skip the first 8 lines since it's just a header. */ + if (i <= 8) continue; + + if (!len) continue; + else if (line[0] == '#') continue; + + str = strtok(line," \n"); + + while (str) { + value = strtod(str, NULL); + str = strtok(NULL," \n"); + } + + n += 1; + } + + x = malloc(sizeof(double)*n); + dEdx = malloc(sizeof(double)*n); + csda_range = malloc(sizeof(double)*n); + size = n; + + i = 0; + n = 0; + /* Now, we actually store the values. */ + rewind(f); + while (fgets(line, sizeof(line), f)) { + size_t len = strlen(line); + if (len && (line[len-1] != '\n')) { + fprintf(stderr, "got incomplete line on line %i: '%s'\n", i, line); + goto err; + } + + i += 1; + + /* Skip the first 8 lines since it's just a header. */ + if (i <= 8) continue; + + if (!len) continue; + else if (line[0] == '#') continue; + + str = strtok(line," \n"); + + j = 0; + while (str) { + value = strtod(str, NULL); + switch (j) { + case 0: + x[n] = value; + break; + case 3: + dEdx[n] = value; + break; + case 4: + csda_range[n] = value; + break; + } + j += 1; + str = strtok(NULL," \n"); + } + + n += 1; + } + + fclose(f); + + acc_dEdx = gsl_interp_accel_alloc(); + spline_dEdx = gsl_spline_alloc(gsl_interp_linear, size); + gsl_spline_init(spline_dEdx, x, dEdx, size); + + acc_range = gsl_interp_accel_alloc(); + spline_range = gsl_spline_alloc(gsl_interp_linear, size); + gsl_spline_init(spline_range, x, csda_range, size); + + initialized = 1; + + return 0; + +err: + fclose(f); + + return -1; +} + +double electron_get_range(double T, double rho) +{ + /* Returns the approximate range a electron with kinetic energy `T` will travel + * in water before losing all of its energy. This range is interpolated + * based on data from the PDG which uses the continuous slowing down + * approximation. + * + * `T` should be in MeV, and `rho` should be in g/cm^3. + * + * Return value is in cm. + * + * See http://pdg.lbl.gov/2018/AtomicNuclearProperties/adndt.pdf. */ + if (!initialized) { + if (init()) { + exit(1); + } + } + + return gsl_spline_eval(spline_range, T, acc_range)/rho; +} + +double electron_get_dEdx(double T, double rho) +{ + /* Returns the approximate dE/dx for a electron in water with kinetic energy + * `T`. + * + * `T` should be in MeV and `rho` in g/cm^3. + * + * Return value is in MeV/cm. + * + * See http://pdg.lbl.gov/2018/AtomicNuclearProperties/adndt.pdf. */ + if (!initialized) { + if (init()) { + exit(1); + } + } + + if (T < spline_dEdx->x[0]) return spline_dEdx->y[0]; + + return gsl_spline_eval(spline_dEdx, T, acc_dEdx)*rho; +} diff --git a/src/electron.h b/src/electron.h new file mode 100644 index 0000000..1bb589f --- /dev/null +++ b/src/electron.h @@ -0,0 +1,9 @@ +#ifndef ELECTRON_H +#define ELECTRON_H + +#include <stddef.h> /* for size_t */ + +double electron_get_range(double T, double rho); +double electron_get_dEdx(double T, double rho); + +#endif @@ -21,6 +21,7 @@ #include "pmt_response.h" #include <signal.h> /* for signal() */ #include "release.h" +#include "id_particles.h" char *GitSHA1(void); char *GitDirty(void); @@ -4995,7 +4996,7 @@ double nll(unsigned int n, const double *x, double *grad, void *params) z2[0] = x[8]; gettimeofday(&tv_start, NULL); - fval = nll_muon(fpars->ev, T, pos, dir, t0, z1, z2, 1, fpars->epsrel, fpars->fast); + fval = nll_muon(fpars->ev, IDP_MU_MINUS, T, pos, dir, t0, z1, z2, 1, fpars->epsrel, fpars->fast); gettimeofday(&tv_stop, NULL); long long elapsed = (tv_stop.tv_sec - tv_start.tv_sec)*1000 + (tv_stop.tv_usec - tv_start.tv_usec)/1000; diff --git a/src/likelihood.c b/src/likelihood.c index 5f66a72..7da81bb 100644 --- a/src/likelihood.c +++ b/src/likelihood.c @@ -18,12 +18,164 @@ #include <gsl/gsl_roots.h> #include <gsl/gsl_errno.h> #include "pmt_response.h" +#include "electron.h" +#include "proton.h" +#include "id_particles.h" typedef struct intParams { path *p; int i; } intParams; +particle *particle_init(int id, double T0, double rho, size_t n) +{ + /* Returns a struct with arrays of the particle position and kinetic + * energy. This struct can then be passed to particle_get_energy() to + * interpolate the particle's kinetic energy at any point along the track. + * For example: + * + * particle *p = particle_init(IDP_MU_MINUS, 1000.0, HEAVY_WATER_DENSITY, 100); + * double T = particle_get_energy(x, p); + * + * To compute the kinetic energy as a function of distance we need to solve + * the following integral equation: + * + * T(x) = T0 - \int_0^x dT(T(x))/dx + * + * which we solve by dividing the track up into `n` segments and then + * numerically computing the energy at each step. */ + size_t i; + double dEdx; + + particle *p = malloc(sizeof(particle)); + p->x = malloc(sizeof(double)*n); + p->T = malloc(sizeof(double)*n); + p->n = n; + + p->x[0] = 0; + p->T[0] = T0; + + switch (id) { + case IDP_E_MINUS: + case IDP_E_PLUS: + p->mass = ELECTRON_MASS; + p->range = electron_get_range(T0, rho); + + /* Now we loop over the points along the track and assume dE/dx is + * constant between points. */ + for (i = 1; i < n; i++) { + p->x[i] = p->range*i/(n-1); + dEdx = electron_get_dEdx(p->T[i-1], rho); + p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]); + } + + break; + case IDP_MU_MINUS: + case IDP_MU_PLUS: + p->mass = MUON_MASS; + p->range = muon_get_range(T0, rho); + + /* Now we loop over the points along the track and assume dE/dx is + * constant between points. */ + for (i = 1; i < n; i++) { + p->x[i] = p->range*i/(n-1); + dEdx = muon_get_dEdx(p->T[i-1], rho); + p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]); + } + + break; + case IDP_PROTON: + p->mass = PROTON_MASS; + p->range = proton_get_range(T0, rho); + + /* Now we loop over the points along the track and assume dE/dx is + * constant between points. */ + for (i = 1; i < n; i++) { + p->x[i] = p->range*i/(n-1); + dEdx = proton_get_dEdx(p->T[i-1], rho); + p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]); + } + + break; + default: + fprintf(stderr, "unknown particle id %i\n", id); + exit(1); + } + + return p; +} + +double particle_get_energy(double x, particle *p) +{ + /* Returns the approximate kinetic energy of a particle in water after + * travelling `x` cm with an initial kinetic energy `T`. + * + * Return value is in MeV. */ + double T; + + T = interp1d(x,p->x,p->T,p->n); + + if (T < 0) return 0; + + return T; +} + +void particle_free(particle *p) +{ + free(p->x); + free(p->T); + free(p); +} + +double get_expected_charge(double x, double beta, double theta0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r, int reflected) +{ + double pmt_dir[3], cos_theta, n, wavelength0, omega, z, R, f, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic; + + z = 1.0; + + SUB(pmt_dir,pmt_pos,pos); + + normalize(pmt_dir); + + cos_theta_pmt = DOT(pmt_dir,pmt_normal); + + if (cos_theta_pmt > 0) return 0; + + /* Calculate the cosine of the angle between the track direction and the + * vector to the PMT. */ + cos_theta = DOT(dir,pmt_dir); + + omega = get_solid_angle_approx(pos,pmt_pos,pmt_normal,r); + + R = NORM(pos); + + /* FIXME: I just calculate delta assuming 400 nm light. */ + wavelength0 = 400.0; + + if (R <= AV_RADIUS) { + n = get_index_snoman_d2o(wavelength0); + } else { + n = get_index_snoman_h2o(wavelength0); + } + + if (beta < 1/n) return 0; + + if (reflected) + f = get_weighted_pmt_reflectivity(acos(-cos_theta_pmt)); + else + f = get_weighted_pmt_response(acos(-cos_theta_pmt)); + + absorption_length_d2o = get_weighted_absorption_length_snoman_d2o(); + absorption_length_h2o = get_weighted_absorption_length_snoman_h2o(); + absorption_length_acrylic = get_weighted_absorption_length_snoman_acrylic(); + + get_path_length(pos,pmt_pos,AV_RADIUS,&l_d2o,&l_h2o); + + l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER; + + return f*exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic)*omega*FINE_STRUCTURE_CONSTANT*z*z*(1-(1/(beta*beta*n*n)))*get_probability(beta, cos_theta, theta0); +} + double F(double t, double mu_noise, double mu_indirect, double *mu_direct, size_t n, double *ts, double tmean, double sigma) { /* Returns the CDF for the time distribution of photons at time `t`. */ @@ -86,12 +238,12 @@ double log_pt(double t, size_t n, double mu_noise, double mu_indirect, double *m return ln(n) + (n-1)*log1p(-F(t,mu_noise,mu_indirect,mu_direct,n2,ts,tmean,sigma)) + log(f(t,mu_noise,mu_indirect,mu_direct,n2,ts,tmean,sigma)); } -static double gsl_muon_time(double x, void *params) +static double gsl_time(double x, void *params) { intParams *pars = (intParams *) params; - double dir[3], pos[3], n_d2o, n_h2o, wavelength0, T, t, theta0, l_d2o, l_h2o; + double dir[3], pos[3], n_d2o, n_h2o, wavelength0, t, theta0, beta, l_d2o, l_h2o; - path_eval(pars->p, x, pos, dir, &T, &t, &theta0); + path_eval(pars->p, x, pos, dir, &beta, &t, &theta0); get_path_length(pos,pmts[pars->i].pos,AV_RADIUS,&l_d2o,&l_h2o); @@ -102,30 +254,30 @@ static double gsl_muon_time(double x, void *params) t += l_d2o*n_d2o/SPEED_OF_LIGHT + l_h2o*n_h2o/SPEED_OF_LIGHT; - return t*get_expected_charge(x, T, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS); + return t*get_expected_charge(x, beta, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS, 0); } -static double gsl_muon_reflected_charge(double x, void *params) +static double gsl_reflected_charge(double x, void *params) { intParams *pars = (intParams *) params; - double dir[3], pos[3], T, t, theta0; + double dir[3], pos[3], t, theta0, beta; - path_eval(pars->p, x, pos, dir, &T, &t, &theta0); + path_eval(pars->p, x, pos, dir, &beta, &t, &theta0); - return get_expected_reflected_charge(x, T, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS); + return get_expected_charge(x, beta, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS, 1); } -static double gsl_muon_charge(double x, void *params) +static double gsl_charge(double x, void *params) { intParams *pars = (intParams *) params; - double dir[3], pos[3], T, t, theta0; + double dir[3], pos[3], t, theta0, beta; - path_eval(pars->p, x, pos, dir, &T, &t, &theta0); + path_eval(pars->p, x, pos, dir, &beta, &t, &theta0); - return get_expected_charge(x, T, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS); + return get_expected_charge(x, beta, theta0, pos, dir, pmts[pars->i].pos, pmts[pars->i].normal, PMT_RADIUS, 0); } -double get_total_charge_approx(double T0, double *pos, double *dir, muon_energy *m, int i, double smax, double theta0, double *t, double *mu_reflected) +double get_total_charge_approx(double T0, double *pos, double *dir, particle *p, int i, double smax, double theta0, double *t, double *mu_reflected) { /* Returns the approximate expected number of photons seen by PMT `i` using * an analytic formula. @@ -156,11 +308,11 @@ double get_total_charge_approx(double T0, double *pos, double *dir, muon_energy * * `smax` is currently calculated as the point where the particle velocity * drops to 0.8 times the speed of light. */ - double pmt_dir[3], tmp[3], R, cos_theta, x, z, s, a, b, beta, E, p, T, omega, cos_theta_cerenkov, sin_theta_cerenkov, n_d2o, n_h2o, sin_theta, E0, p0, beta0, f, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic, wavelength0, f_reflected; + double pmt_dir[3], tmp[3], R, cos_theta, x, z, s, a, b, beta, E, mom, T, omega, cos_theta_cerenkov, sin_theta_cerenkov, n_d2o, n_h2o, sin_theta, E0, p0, beta0, f, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic, wavelength0, f_reflected; /* Calculate beta at the start of the track. */ - E0 = T0 + MUON_MASS; - p0 = sqrt(E0*E0 - MUON_MASS*MUON_MASS); + E0 = T0 + p->mass; + p0 = sqrt(E0*E0 - p->mass*p->mass); beta0 = p0/E0; /* First, we find the point along the track where the PMT is at the @@ -219,12 +371,12 @@ double get_total_charge_approx(double T0, double *pos, double *dir, muon_energy b = (s-z)/a; /* Compute the kinetic energy at the point `s` along the track. */ - T = muon_get_energy(s,m); + T = particle_get_energy(s,p); /* Calculate the particle velocity at the point `s`. */ - E = T + MUON_MASS; - p = sqrt(E*E - MUON_MASS*MUON_MASS); - beta = p/E; + E = T + p->mass; + mom = sqrt(E*E - p->mass*p->mass); + beta = mom/E; if (beta < 1/n_d2o) return 0.0; @@ -275,7 +427,7 @@ double get_total_charge_approx(double T0, double *pos, double *dir, muon_energy } typedef struct betaRootParams { - muon_energy *m; + particle *p; double beta_min; } betaRootParams; @@ -283,22 +435,22 @@ static double beta_root(double x, void *params) { /* Function used to find at what point along a track a particle crosses * some threshold in beta. */ - double T, E, p, beta; + double T, E, mom, beta; betaRootParams *pars; pars = (betaRootParams *) params; - T = muon_get_energy(x, pars->m); + T = particle_get_energy(x, pars->p); /* Calculate total energy */ - E = T + MUON_MASS; - p = sqrt(E*E - MUON_MASS*MUON_MASS); - beta = p/E; + E = T + pars->p->mass; + mom = sqrt(E*E - pars->p->mass*pars->p->mass); + beta = mom/E; return beta - pars->beta_min; } -static int get_smax(muon_energy *m, double beta_min, double range, double *smax) +static int get_smax(particle *p, double beta_min, double range, double *smax) { /* Find the point along the track at which the particle's velocity drops to * `beta_min`. */ @@ -311,7 +463,7 @@ static int get_smax(muon_energy *m, double beta_min, double range, double *smax) s = gsl_root_fsolver_alloc(gsl_root_fsolver_brent); - pars.m = m; + pars.p = p; pars.beta_min = beta_min; F.function = &beta_root; @@ -339,17 +491,17 @@ static int get_smax(muon_energy *m, double beta_min, double range, double *smax) return status; } -double getKineticEnergy(double x, void *params) +static double getKineticEnergy(double x, void *p) { - return muon_get_energy(x,(muon_energy *) params); + return particle_get_energy(x, (particle *) p); } -double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, double *z1, double *z2, size_t n, double epsrel, int fast) +double nll_muon(event *ev, int id, double T0, double *pos, double *dir, double t0, double *z1, double *z2, size_t n, double epsrel, int fast) { size_t i, j, nhit; intParams params; double logp[MAX_PE], nll[MAX_PMTS], range, theta0, E0, p0, beta0, smax, log_mu, max_logp; - muon_energy *m; + particle *p; double pmt_dir[3], R, cos_theta, theta, wavelength0, n_d2o, n_h2o, theta_cerenkov, s, l_h2o, l_d2o; double logp_path; double a, b; @@ -369,22 +521,21 @@ double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, doubl gsl_function F; F.params = ¶ms; - range = get_range(T0, HEAVY_WATER_DENSITY); + p = particle_init(id, T0, HEAVY_WATER_DENSITY, 10000); + range = p->range; /* Calculate total energy */ - E0 = T0 + MUON_MASS; - p0 = sqrt(E0*E0 - MUON_MASS*MUON_MASS); + E0 = T0 + p->mass; + p0 = sqrt(E0*E0 - p->mass*p->mass); beta0 = p0/E0; /* FIXME: is this formula valid for muons? */ theta0 = get_scattering_rms(range/2,p0,beta0,1.0,HEAVY_WATER_DENSITY)/sqrt(range/2); - m = muon_init_energy(T0,HEAVY_WATER_DENSITY,10000); - - params.p = path_init(pos, dir, T0, range, theta0, getKineticEnergy, m, z1, z2, n, MUON_MASS); + params.p = path_init(pos, dir, T0, range, theta0, getKineticEnergy, p, z1, z2, n, p->mass); if (beta0 > BETA_MIN) - get_smax(m, BETA_MIN, range, &smax); + get_smax(p, BETA_MIN, range, &smax); else smax = 0.0; @@ -399,7 +550,7 @@ double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, doubl params.i = i; if (fast) { - mu_direct[i] = get_total_charge_approx(T0, pos, dir, m, i, smax, theta0, &ts[i], &mu_reflected); + mu_direct[i] = get_total_charge_approx(T0, pos, dir, p, i, smax, theta0, &ts[i], &mu_reflected); ts[i] += t0; mu_indirect += mu_reflected; } else { @@ -456,17 +607,17 @@ double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, doubl if (a < 0.0) a = 0.0; if (b > range) b = range; - F.function = &gsl_muon_charge; + F.function = &gsl_charge; gsl_integration_cquad(&F, a, b, 0, epsrel, w, &result, &error, &nevals); mu_direct[i] = result; - F.function = &gsl_muon_reflected_charge; + F.function = &gsl_reflected_charge; gsl_integration_cquad(&F, a, b, 0, epsrel, w, &result, &error, &nevals); mu_indirect += result; - F.function = &gsl_muon_time; + F.function = &gsl_time; if (mu_direct[i] > 1e-9) { gsl_integration_cquad(&F, a, b, 0, epsrel, w, &result, &error, &nevals); @@ -496,7 +647,7 @@ double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, doubl path_free(params.p); - muon_free_energy(m); + particle_free(p); gsl_integration_cquad_workspace_free(w); diff --git a/src/likelihood.h b/src/likelihood.h index d708f19..945fc46 100644 --- a/src/likelihood.h +++ b/src/likelihood.h @@ -33,6 +33,18 @@ #define GTVALID 400.0 #define BETA_MIN 0.8 -double nll_muon(event *ev, double T0, double *pos, double *dir, double t0, double *z1, double *z2, size_t n, double epsrel, int fast); +typedef struct particle { + double mass; + double range; + double *x; + double *T; + size_t n; +} particle; + +particle *particle_init(int id, double T0, double rho, size_t n); +double particle_get_energy(double x, particle *p); +void particle_free(particle *p); +double get_expected_charge(double x, double T, double T0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r, int reflected); +double nll_muon(event *ev, int id, double T0, double *pos, double *dir, double t0, double *z1, double *z2, size_t n, double epsrel, int fast); #endif @@ -151,7 +151,7 @@ err: return -1; } -double get_range(double T, double rho) +double muon_get_range(double T, double rho) { /* Returns the approximate range a muon with kinetic energy `T` will travel * in water before losing all of its energy. This range is interpolated @@ -172,70 +172,7 @@ double get_range(double T, double rho) return gsl_spline_eval(spline_range, T, acc_range)/rho; } -muon_energy *muon_init_energy(double T0, double rho, size_t n) -{ - /* Returns a struct with arrays of the muon position and kinetic energy. - * This struct can then be passed to muon_get_energy() to interpolate the - * muon's kinetic energy at any point along the track. For example: - * - * muon_energy *m = muon_init_energy(1000.0, HEAVY_WATER_DENSITY, 100); - * double T = muon_get_energy(x, m); - * - * To compute the kinetic energy as a function of distance we need to solve - * the following integral equation: - * - * T(x) = T0 - \int_0^x dT(T(x))/dx - * - * which we solve by dividing the track up into `n` segments and then - * numerically computing the energy at each step. */ - size_t i; - double range, dEdx; - - /* Get the range of the muon. */ - range = get_range(T0, rho); - - muon_energy *m = malloc(sizeof(muon_energy)); - m->x = malloc(sizeof(double)*n); - m->T = malloc(sizeof(double)*n); - m->n = n; - - m->x[0] = 0; - m->T[0] = T0; - - /* Now we loop over the points along the track and assume dE/dx is constant - * between points. */ - for (i = 1; i < n; i++) { - m->x[i] = range*i/(n-1); - dEdx = get_dEdx(m->T[i-1], rho); - m->T[i] = m->T[i-1] - dEdx*(m->x[i]-m->x[i-1]); - } - - return m; -} - -double muon_get_energy(double x, muon_energy *m) -{ - /* Returns the approximate kinetic energy of a muon in water after - * travelling `x` cm with an initial kinetic energy `T`. - * - * Return value is in MeV. */ - double T; - - T = interp1d(x,m->x,m->T,m->n); - - if (T < 0) return 0; - - return T; -} - -void muon_free_energy(muon_energy *m) -{ - free(m->x); - free(m->T); - free(m); -} - -double get_dEdx(double T, double rho) +double muon_get_dEdx(double T, double rho) { /* Returns the approximate dE/dx for a muon in water with kinetic energy * `T`. @@ -255,105 +192,3 @@ double get_dEdx(double T, double rho) return gsl_spline_eval(spline_dEdx, T, acc_dEdx)*rho; } - -double get_expected_reflected_charge(double x, double T, double theta0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r) -{ - double pmt_dir[3], cos_theta, n, wavelength0, omega, E, p, beta, z, R, f, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic; - - z = 1.0; - - SUB(pmt_dir,pmt_pos,pos); - - normalize(pmt_dir); - - cos_theta_pmt = DOT(pmt_dir,pmt_normal); - - if (cos_theta_pmt > 0) return 0; - - /* Calculate the cosine of the angle between the track direction and the - * vector to the PMT. */ - cos_theta = DOT(dir,pmt_dir); - - /* Calculate total energy */ - E = T + MUON_MASS; - p = sqrt(E*E - MUON_MASS*MUON_MASS); - beta = p/E; - - omega = get_solid_angle_approx(pos,pmt_pos,pmt_normal,r); - - R = NORM(pos); - - /* FIXME: I just calculate delta assuming 400 nm light. */ - wavelength0 = 400.0; - - if (R <= AV_RADIUS) { - n = get_index_snoman_d2o(wavelength0); - } else { - n = get_index_snoman_h2o(wavelength0); - } - - if (beta < 1/n) return 0; - - f = get_weighted_pmt_reflectivity(acos(-cos_theta_pmt)); - - absorption_length_d2o = get_weighted_absorption_length_snoman_d2o(); - absorption_length_h2o = get_weighted_absorption_length_snoman_h2o(); - absorption_length_acrylic = get_weighted_absorption_length_snoman_acrylic(); - - get_path_length(pos,pmt_pos,AV_RADIUS,&l_d2o,&l_h2o); - - l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER; - - return f*exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic)*omega*FINE_STRUCTURE_CONSTANT*z*z*(1-(1/(beta*beta*n*n)))*get_probability(beta, cos_theta, theta0); -} - -double get_expected_charge(double x, double T, double theta0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r) -{ - double pmt_dir[3], cos_theta, n, wavelength0, omega, E, p, beta, z, R, f, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic; - - z = 1.0; - - SUB(pmt_dir,pmt_pos,pos); - - normalize(pmt_dir); - - cos_theta_pmt = DOT(pmt_dir,pmt_normal); - - if (cos_theta_pmt > 0) return 0; - - /* Calculate the cosine of the angle between the track direction and the - * vector to the PMT. */ - cos_theta = DOT(dir,pmt_dir); - - /* Calculate total energy */ - E = T + MUON_MASS; - p = sqrt(E*E - MUON_MASS*MUON_MASS); - beta = p/E; - - omega = get_solid_angle_approx(pos,pmt_pos,pmt_normal,r); - - R = NORM(pos); - - /* FIXME: I just calculate delta assuming 400 nm light. */ - wavelength0 = 400.0; - - if (R <= AV_RADIUS) { - n = get_index_snoman_d2o(wavelength0); - } else { - n = get_index_snoman_h2o(wavelength0); - } - - if (beta < 1/n) return 0; - - f = get_weighted_pmt_response(acos(-cos_theta_pmt)); - - absorption_length_d2o = get_weighted_absorption_length_snoman_d2o(); - absorption_length_h2o = get_weighted_absorption_length_snoman_h2o(); - absorption_length_acrylic = get_weighted_absorption_length_snoman_acrylic(); - - get_path_length(pos,pmt_pos,AV_RADIUS,&l_d2o,&l_h2o); - - l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER; - - return f*exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic)*omega*FINE_STRUCTURE_CONSTANT*z*z*(1-(1/(beta*beta*n*n)))*get_probability(beta, cos_theta, theta0); -} @@ -5,18 +5,7 @@ #define EULER_CONSTANT 0.57721 -typedef struct muon_energy { - double *x; - double *T; - size_t n; -} muon_energy; - -muon_energy *muon_init_energy(double T0, double rho, size_t n); -double muon_get_energy(double x, muon_energy *m); -void muon_free_energy(muon_energy *m); -double get_range(double T, double rho); -double get_dEdx(double T, double rho); -double get_expected_reflected_charge(double x, double T, double theta0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r); -double get_expected_charge(double x, double T, double T0, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r); +double muon_get_range(double T, double rho); +double muon_get_dEdx(double T, double rho); #endif @@ -35,7 +35,7 @@ double path_get_coefficient(unsigned int k, double *s, double *x, double theta0, path *path_init(double *pos, double *dir, double T0, double range, double theta0, getKineticEnergyFunc *fun, void *params, double *z1, double *z2, size_t n, double m) { size_t i, j; - double E, mom, beta, theta, phi; + double T, E, mom, theta, phi; double normal[3], k[3], tmp[3], tmp2[3]; path *p = malloc(sizeof(path)); @@ -56,7 +56,7 @@ path *path_init(double *pos, double *dir, double T0, double range, double theta0 double *x = calloc(N,sizeof(double)); double *y = calloc(N,sizeof(double)); double *z = calloc(N,sizeof(double)); - double *T = calloc(N,sizeof(double)); + double *beta = calloc(N,sizeof(double)); double *t = calloc(N,sizeof(double)); double *dx = calloc(N,sizeof(double)); double *dy = calloc(N,sizeof(double)); @@ -69,7 +69,12 @@ path *path_init(double *pos, double *dir, double T0, double range, double theta0 theta1[i] += z1[j]*foo(s[i],range,theta0,j+1); theta2[i] += z2[j]*foo(s[i],range,theta0,j+1); } - T[i] = fun(s[i],params); + + T = fun(s[i],params); + E = T + m; + mom = sqrt(E*E - m*m); + beta[i] = mom/E; + if (i > 0) { theta = sqrt(theta1[i]*theta1[i] + theta2[i]*theta2[i]); phi = atan2(theta2[i],theta1[i]); @@ -79,11 +84,7 @@ path *path_init(double *pos, double *dir, double T0, double range, double theta0 dz[i] = (s[i]-s[i-1])*cos(theta); /* Calculate total energy */ - E = T[i] + m; - mom = sqrt(E*E - m*m); - beta = mom/E; - - t[i] = t[i-1] + (s[i]-s[i-1])/(beta*SPEED_OF_LIGHT); + t[i] = t[i-1] + (s[i]-s[i-1])/(beta[i]*SPEED_OF_LIGHT); x[i] = x[i-1] + dx[i]; y[i] = y[i-1] + dy[i]; @@ -168,7 +169,7 @@ path *path_init(double *pos, double *dir, double T0, double range, double theta0 p->x = x; p->y = y; p->z = z; - p->T = T; + p->beta = beta; p->t = t; p->dx = dx; p->dy = dy; @@ -180,13 +181,13 @@ path *path_init(double *pos, double *dir, double T0, double range, double theta0 return p; } -void path_eval(path *p, double s, double *pos, double *dir, double *T, double *t, double *theta0) +void path_eval(path *p, double s, double *pos, double *dir, double *beta, double *t, double *theta0) { pos[0] = interp1d(s,p->s,p->x,N); pos[1] = interp1d(s,p->s,p->y,N); pos[2] = interp1d(s,p->s,p->z,N); - *T = interp1d(s,p->s,p->T,N); + *beta = interp1d(s,p->s,p->beta,N); *t = interp1d(s,p->s,p->t,N); /* Technically we should be interpolating between the direction vectors @@ -214,7 +215,7 @@ void path_free(path *p) free(p->x); free(p->y); free(p->z); - free(p->T); + free(p->beta); free(p->t); free(p->dx); free(p->dy); @@ -27,7 +27,7 @@ typedef struct path { double *x; double *y; double *z; - double *T; + double *beta; double *t; double *dx; double *dy; @@ -36,7 +36,7 @@ typedef struct path { double path_get_coefficient(unsigned int k, double *s, double *x, double theta0, size_t n); path *path_init(double *pos, double *dir, double T0, double range, double theta0, getKineticEnergyFunc *fun, void *params, double *z1, double *z2, size_t n, double m); -void path_eval(path *p, double s, double *pos, double *dir, double *T, double *t, double *theta0); +void path_eval(path *p, double s, double *pos, double *dir, double *beta, double *t, double *theta0); void path_free(path *p); #endif @@ -16,6 +16,9 @@ double get_scattering_rms(double x, double p, double beta, double z, double rho) * since the radiation length is only discussed in terms of an * electromagnetic shower induced by electrons (see Section 33.4.2). * + * Update: MicroBooNE uses this formula for muons in this paper: + * https://arxiv.org/abs/1703.06187. + * * See Equation 33.15 in * http://pdg.lbl.gov/2018/reviews/rpp2018-rev-passage-particles-matter.pdf. */ if (x == 0.0) return 0.0; @@ -4,7 +4,9 @@ #define SPEED_OF_LIGHT 29.9792458 /* cm/ns */ /* From http://pdg.lbl.gov/2017/AtomicNuclearProperties/HTML/water_liquid.html */ #define RADIATION_LENGTH 36.08 /* g/cm^2 */ +#define ELECTRON_MASS 0.5109989461 /* MeV */ #define MUON_MASS 105.6583745 /* MeV */ +#define PROTON_MASS 938.272081 /* MeV */ #define FINE_STRUCTURE_CONSTANT 7.297352566417e-3 double get_scattering_rms(double x, double p, double beta, double z, double rho); diff --git a/src/proton.c b/src/proton.c new file mode 100644 index 0000000..922c552 --- /dev/null +++ b/src/proton.c @@ -0,0 +1,180 @@ +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <gsl/gsl_errno.h> +#include <gsl/gsl_spline.h> +#include <math.h> +#include "optics.h" +#include "quantum_efficiency.h" +#include "solid_angle.h" +#include "pdg.h" +#include "vector.h" +#include "proton.h" +#include "sno.h" +#include "scattering.h" +#include "pmt_response.h" +#include "misc.h" + +static int initialized = 0; + +static double *x, *dEdx, *csda_range; +static size_t size; + +static gsl_interp_accel *acc_dEdx; +static gsl_spline *spline_dEdx; + +static gsl_interp_accel *acc_range; +static gsl_spline *spline_range; + +static int init() +{ + int i, j; + char line[256]; + char *str; + double value; + int n; + + FILE *f = fopen("proton_water_liquid.txt", "r"); + + if (!f) { + fprintf(stderr, "failed to open proton_water_liquid.txt: %s\n", strerror(errno)); + return -1; + } + + i = 0; + n = 0; + /* For the first pass, we just count how many values there are. */ + while (fgets(line, sizeof(line), f)) { + size_t len = strlen(line); + if (len && (line[len-1] != '\n')) { + fprintf(stderr, "got incomplete line on line %i: '%s'\n", i, line); + goto err; + } + + i += 1; + + /* Skip the first 8 lines since it's just a header. */ + if (i <= 8) continue; + + if (!len) continue; + else if (line[0] == '#') continue; + + str = strtok(line," \n"); + + while (str) { + value = strtod(str, NULL); + str = strtok(NULL," \n"); + } + + n += 1; + } + + x = malloc(sizeof(double)*n); + dEdx = malloc(sizeof(double)*n); + csda_range = malloc(sizeof(double)*n); + size = n; + + i = 0; + n = 0; + /* Now, we actually store the values. */ + rewind(f); + while (fgets(line, sizeof(line), f)) { + size_t len = strlen(line); + if (len && (line[len-1] != '\n')) { + fprintf(stderr, "got incomplete line on line %i: '%s'\n", i, line); + goto err; + } + + i += 1; + + /* Skip the first 8 lines since it's just a header. */ + if (i <= 8) continue; + + if (!len) continue; + else if (line[0] == '#') continue; + + str = strtok(line," \n"); + + j = 0; + while (str) { + value = strtod(str, NULL); + switch (j) { + case 0: + x[n] = value; + break; + case 3: + dEdx[n] = value; + break; + case 4: + csda_range[n] = value; + break; + } + j += 1; + str = strtok(NULL," \n"); + } + + n += 1; + } + + fclose(f); + + acc_dEdx = gsl_interp_accel_alloc(); + spline_dEdx = gsl_spline_alloc(gsl_interp_linear, size); + gsl_spline_init(spline_dEdx, x, dEdx, size); + + acc_range = gsl_interp_accel_alloc(); + spline_range = gsl_spline_alloc(gsl_interp_linear, size); + gsl_spline_init(spline_range, x, csda_range, size); + + initialized = 1; + + return 0; + +err: + fclose(f); + + return -1; +} + +double proton_get_range(double T, double rho) +{ + /* Returns the approximate range a proton with kinetic energy `T` will travel + * in water before losing all of its energy. This range is interpolated + * based on data from the PDG which uses the continuous slowing down + * approximation. + * + * `T` should be in MeV, and `rho` should be in g/cm^3. + * + * Return value is in cm. + * + * See http://pdg.lbl.gov/2018/AtomicNuclearProperties/adndt.pdf. */ + if (!initialized) { + if (init()) { + exit(1); + } + } + + return gsl_spline_eval(spline_range, T, acc_range)/rho; +} + +double proton_get_dEdx(double T, double rho) +{ + /* Returns the approximate dE/dx for a proton in water with kinetic energy + * `T`. + * + * `T` should be in MeV and `rho` in g/cm^3. + * + * Return value is in MeV/cm. + * + * See http://pdg.lbl.gov/2018/AtomicNuclearProperties/adndt.pdf. */ + if (!initialized) { + if (init()) { + exit(1); + } + } + + if (T < spline_dEdx->x[0]) return spline_dEdx->y[0]; + + return gsl_spline_eval(spline_dEdx, T, acc_dEdx)*rho; +} diff --git a/src/proton.h b/src/proton.h new file mode 100644 index 0000000..8e19e9c --- /dev/null +++ b/src/proton.h @@ -0,0 +1,9 @@ +#ifndef PROTON_H +#define PROTON_H + +#include <stddef.h> /* for size_t */ + +double proton_get_range(double T, double rho); +double proton_get_dEdx(double T, double rho); + +#endif diff --git a/src/proton_water_liquid.txt b/src/proton_water_liquid.txt new file mode 100644 index 0000000..ef774db --- /dev/null +++ b/src/proton_water_liquid.txt @@ -0,0 +1,140 @@ +PSTAR: Stopping Powers and Range Tables for Protons + +WATER, LIQUID + +Kinetic Electron. Nuclear Total CSDA Projected Detour +Energy Stp. Pow. Stp. Pow. Stp. Pow. Range Range Factor +MeV MeV cm2/g MeV cm2/g MeV cm2/g g/cm2 g/cm2 + +1.000E-03 1.337E+02 4.315E+01 1.769E+02 6.319E-06 2.878E-06 0.4555 +1.500E-03 1.638E+02 3.460E+01 1.984E+02 8.969E-06 4.400E-06 0.4906 +2.000E-03 1.891E+02 2.927E+01 2.184E+02 1.137E-05 5.909E-06 0.5197 +2.500E-03 2.114E+02 2.557E+01 2.370E+02 1.357E-05 7.380E-06 0.5440 +3.000E-03 2.316E+02 2.281E+01 2.544E+02 1.560E-05 8.811E-06 0.5647 +4.000E-03 2.675E+02 1.894E+01 2.864E+02 1.930E-05 1.155E-05 0.5986 +5.000E-03 2.990E+02 1.631E+01 3.153E+02 2.262E-05 1.415E-05 0.6254 +6.000E-03 3.276E+02 1.439E+01 3.420E+02 2.567E-05 1.661E-05 0.6473 +7.000E-03 3.538E+02 1.292E+01 3.667E+02 2.849E-05 1.896E-05 0.6656 +8.000E-03 3.782E+02 1.175E+01 3.900E+02 3.113E-05 2.121E-05 0.6813 +9.000E-03 4.012E+02 1.080E+01 4.120E+02 3.363E-05 2.337E-05 0.6950 +1.000E-02 4.229E+02 1.000E+01 4.329E+02 3.599E-05 2.545E-05 0.7070 +1.250E-02 4.660E+02 8.485E+00 4.745E+02 4.150E-05 3.037E-05 0.7318 +1.500E-02 5.036E+02 7.400E+00 5.110E+02 4.657E-05 3.499E-05 0.7514 +1.750E-02 5.372E+02 6.581E+00 5.437E+02 5.131E-05 3.938E-05 0.7674 +2.000E-02 5.673E+02 5.939E+00 5.733E+02 5.578E-05 4.356E-05 0.7808 +2.250E-02 5.946E+02 5.421E+00 6.001E+02 6.005E-05 4.757E-05 0.7923 +2.500E-02 6.195E+02 4.993E+00 6.245E+02 6.413E-05 5.144E-05 0.8022 +2.750E-02 6.421E+02 4.633E+00 6.467E+02 6.806E-05 5.519E-05 0.8109 +3.000E-02 6.628E+02 4.325E+00 6.671E+02 7.187E-05 5.883E-05 0.8187 +3.500E-02 6.989E+02 3.826E+00 7.028E+02 7.916E-05 6.585E-05 0.8319 +4.000E-02 7.290E+02 3.437E+00 7.324E+02 8.613E-05 7.259E-05 0.8429 +4.500E-02 7.538E+02 3.126E+00 7.569E+02 9.284E-05 7.911E-05 0.8522 +5.000E-02 7.740E+02 2.870E+00 7.768E+02 9.935E-05 8.547E-05 0.8602 +5.500E-02 7.901E+02 2.655E+00 7.927E+02 1.057E-04 9.169E-05 0.8673 +6.000E-02 8.026E+02 2.473E+00 8.050E+02 1.120E-04 9.782E-05 0.8735 +6.500E-02 8.119E+02 2.316E+00 8.142E+02 1.182E-04 1.039E-04 0.8791 +7.000E-02 8.183E+02 2.178E+00 8.205E+02 1.243E-04 1.099E-04 0.8842 +7.500E-02 8.223E+02 2.058E+00 8.243E+02 1.303E-04 1.159E-04 0.8889 +8.000E-02 8.241E+02 1.951E+00 8.260E+02 1.364E-04 1.218E-04 0.8931 +8.500E-02 8.239E+02 1.855E+00 8.258E+02 1.425E-04 1.278E-04 0.8971 +9.000E-02 8.222E+02 1.769E+00 8.239E+02 1.485E-04 1.338E-04 0.9007 +9.500E-02 8.190E+02 1.691E+00 8.206E+02 1.546E-04 1.398E-04 0.9041 +1.000E-01 8.145E+02 1.620E+00 8.161E+02 1.607E-04 1.458E-04 0.9073 +1.250E-01 7.801E+02 1.343E+00 7.814E+02 1.920E-04 1.767E-04 0.9207 +1.500E-01 7.360E+02 1.152E+00 7.371E+02 2.249E-04 2.094E-04 0.9310 +1.750E-01 6.959E+02 1.010E+00 6.969E+02 2.598E-04 2.440E-04 0.9393 +2.000E-01 6.604E+02 9.016E-01 6.613E+02 2.966E-04 2.806E-04 0.9460 +2.250E-01 6.286E+02 8.152E-01 6.294E+02 3.354E-04 3.191E-04 0.9515 +2.500E-01 5.999E+02 7.447E-01 6.006E+02 3.761E-04 3.596E-04 0.9562 +2.750E-01 5.737E+02 6.855E-01 5.744E+02 4.186E-04 4.019E-04 0.9601 +3.000E-01 5.497E+02 6.351E-01 5.504E+02 4.631E-04 4.462E-04 0.9635 +3.500E-01 5.075E+02 5.545E-01 5.080E+02 5.577E-04 5.404E-04 0.9689 +4.000E-01 4.714E+02 4.928E-01 4.719E+02 6.599E-04 6.422E-04 0.9731 +4.500E-01 4.401E+02 4.439E-01 4.406E+02 7.697E-04 7.515E-04 0.9764 +5.000E-01 4.128E+02 4.043E-01 4.132E+02 8.869E-04 8.683E-04 0.9790 +5.500E-01 3.888E+02 3.715E-01 3.891E+02 1.012E-03 9.926E-04 0.9811 +6.000E-01 3.676E+02 3.438E-01 3.680E+02 1.144E-03 1.124E-03 0.9829 +6.500E-01 3.489E+02 3.201E-01 3.492E+02 1.283E-03 1.263E-03 0.9844 +7.000E-01 3.322E+02 2.996E-01 3.325E+02 1.430E-03 1.410E-03 0.9857 +7.500E-01 3.172E+02 2.817E-01 3.175E+02 1.584E-03 1.563E-03 0.9868 +8.000E-01 3.037E+02 2.658E-01 3.039E+02 1.745E-03 1.724E-03 0.9877 +8.500E-01 2.914E+02 2.516E-01 2.917E+02 1.913E-03 1.891E-03 0.9886 +9.000E-01 2.803E+02 2.390E-01 2.805E+02 2.088E-03 2.066E-03 0.9893 +9.500E-01 2.700E+02 2.276E-01 2.702E+02 2.270E-03 2.247E-03 0.9899 +1.000E+00 2.606E+02 2.173E-01 2.608E+02 2.458E-03 2.435E-03 0.9905 +1.250E+00 2.228E+02 1.775E-01 2.229E+02 3.499E-03 3.472E-03 0.9925 +1.500E+00 1.955E+02 1.504E-01 1.957E+02 4.698E-03 4.669E-03 0.9938 +1.750E+00 1.748E+02 1.307E-01 1.749E+02 6.052E-03 6.020E-03 0.9946 +2.000E+00 1.585E+02 1.157E-01 1.586E+02 7.555E-03 7.519E-03 0.9952 +2.250E+00 1.453E+02 1.038E-01 1.454E+02 9.203E-03 9.164E-03 0.9957 +2.500E+00 1.343E+02 9.428E-02 1.344E+02 1.099E-02 1.095E-02 0.9960 +2.750E+00 1.250E+02 8.637E-02 1.251E+02 1.292E-02 1.288E-02 0.9963 +3.000E+00 1.171E+02 7.972E-02 1.172E+02 1.499E-02 1.494E-02 0.9965 +3.500E+00 1.041E+02 6.916E-02 1.042E+02 1.952E-02 1.946E-02 0.9968 +4.000E+00 9.398E+01 6.113E-02 9.404E+01 2.458E-02 2.451E-02 0.9971 +4.500E+00 8.580E+01 5.481E-02 8.586E+01 3.015E-02 3.007E-02 0.9973 +5.000E+00 7.906E+01 4.970E-02 7.911E+01 3.623E-02 3.613E-02 0.9974 +5.500E+00 7.339E+01 4.549E-02 7.343E+01 4.279E-02 4.268E-02 0.9975 +6.000E+00 6.854E+01 4.195E-02 6.858E+01 4.984E-02 4.972E-02 0.9976 +6.500E+00 6.434E+01 3.894E-02 6.438E+01 5.737E-02 5.724E-02 0.9977 +7.000E+00 6.068E+01 3.634E-02 6.071E+01 6.537E-02 6.522E-02 0.9977 +7.500E+00 5.744E+01 3.407E-02 5.747E+01 7.384E-02 7.368E-02 0.9978 +8.000E+00 5.456E+01 3.208E-02 5.460E+01 8.277E-02 8.259E-02 0.9978 +8.500E+00 5.199E+01 3.031E-02 5.202E+01 9.215E-02 9.196E-02 0.9979 +9.000E+00 4.966E+01 2.873E-02 4.969E+01 1.020E-01 1.018E-01 0.9979 +9.500E+00 4.756E+01 2.731E-02 4.759E+01 1.123E-01 1.120E-01 0.9979 +1.000E+01 4.564E+01 2.603E-02 4.567E+01 1.230E-01 1.228E-01 0.9980 +1.250E+01 3.813E+01 2.111E-02 3.815E+01 1.832E-01 1.828E-01 0.9981 +1.500E+01 3.290E+01 1.778E-02 3.292E+01 2.539E-01 2.535E-01 0.9982 +1.750E+01 2.904E+01 1.538E-02 2.905E+01 3.350E-01 3.344E-01 0.9982 +2.000E+01 2.605E+01 1.356E-02 2.607E+01 4.260E-01 4.252E-01 0.9983 +2.500E+01 2.174E+01 1.098E-02 2.175E+01 6.370E-01 6.359E-01 0.9983 +2.750E+01 2.012E+01 1.003E-02 2.013E+01 7.566E-01 7.553E-01 0.9984 +3.000E+01 1.875E+01 9.239E-03 1.876E+01 8.853E-01 8.839E-01 0.9984 +3.500E+01 1.656E+01 7.983E-03 1.656E+01 1.170E+00 1.168E+00 0.9984 +4.000E+01 1.487E+01 7.034E-03 1.488E+01 1.489E+00 1.486E+00 0.9985 +4.500E+01 1.353E+01 6.290E-03 1.354E+01 1.841E+00 1.839E+00 0.9985 +5.000E+01 1.244E+01 5.691E-03 1.245E+01 2.227E+00 2.224E+00 0.9985 +5.500E+01 1.154E+01 5.199E-03 1.154E+01 2.644E+00 2.641E+00 0.9985 +6.000E+01 1.078E+01 4.786E-03 1.078E+01 3.093E+00 3.089E+00 0.9986 +6.500E+01 1.012E+01 4.435E-03 1.013E+01 3.572E+00 3.567E+00 0.9986 +7.000E+01 9.555E+00 4.134E-03 9.559E+00 4.080E+00 4.075E+00 0.9986 +7.500E+01 9.059E+00 3.871E-03 9.063E+00 4.618E+00 4.611E+00 0.9986 +8.000E+01 8.622E+00 3.641E-03 8.625E+00 5.184E+00 5.176E+00 0.9986 +8.500E+01 8.233E+00 3.437E-03 8.236E+00 5.777E+00 5.769E+00 0.9986 +9.000E+01 7.884E+00 3.255E-03 7.888E+00 6.398E+00 6.389E+00 0.9986 +9.500E+01 7.570E+00 3.092E-03 7.573E+00 7.045E+00 7.035E+00 0.9986 +1.000E+02 7.286E+00 2.944E-03 7.289E+00 7.718E+00 7.707E+00 0.9987 +1.250E+02 6.190E+00 2.381E-03 6.192E+00 1.146E+01 1.144E+01 0.9987 +1.500E+02 5.443E+00 2.001E-03 5.445E+00 1.577E+01 1.576E+01 0.9987 +1.750E+02 4.901E+00 1.728E-03 4.903E+00 2.062E+01 2.060E+01 0.9988 +2.000E+02 4.491E+00 1.522E-03 4.492E+00 2.596E+01 2.593E+01 0.9988 +2.250E+02 4.169E+00 1.361E-03 4.170E+00 3.174E+01 3.171E+01 0.9988 +2.500E+02 3.910E+00 1.231E-03 3.911E+00 3.794E+01 3.790E+01 0.9989 +2.750E+02 3.697E+00 1.124E-03 3.698E+00 4.452E+01 4.447E+01 0.9989 +3.000E+02 3.519E+00 1.035E-03 3.520E+00 5.145E+01 5.139E+01 0.9989 +3.500E+02 3.240E+00 8.936E-04 3.241E+00 6.628E+01 6.621E+01 0.9989 +4.000E+02 3.031E+00 7.870E-04 3.032E+00 8.225E+01 8.217E+01 0.9990 +4.500E+02 2.870E+00 7.037E-04 2.871E+00 9.921E+01 9.912E+01 0.9990 +5.000E+02 2.743E+00 6.367E-04 2.743E+00 1.170E+02 1.169E+02 0.9990 +5.500E+02 2.640E+00 5.816E-04 2.640E+00 1.356E+02 1.355E+02 0.9991 +6.000E+02 2.555E+00 5.355E-04 2.556E+00 1.549E+02 1.547E+02 0.9991 +6.500E+02 2.485E+00 4.963E-04 2.485E+00 1.747E+02 1.746E+02 0.9991 +7.000E+02 2.426E+00 4.626E-04 2.426E+00 1.951E+02 1.949E+02 0.9991 +7.500E+02 2.375E+00 4.333E-04 2.376E+00 2.159E+02 2.158E+02 0.9991 +8.000E+02 2.333E+00 4.076E-04 2.333E+00 2.372E+02 2.370E+02 0.9992 +8.500E+02 2.296E+00 3.849E-04 2.296E+00 2.588E+02 2.586E+02 0.9992 +9.000E+02 2.264E+00 3.646E-04 2.264E+00 2.807E+02 2.805E+02 0.9992 +9.500E+02 2.236E+00 3.464E-04 2.236E+00 3.029E+02 3.027E+02 0.9992 +1.000E+03 2.211E+00 3.300E-04 2.211E+00 3.254E+02 3.252E+02 0.9992 +1.500E+03 2.070E+00 2.249E-04 2.070E+00 5.605E+02 5.601E+02 0.9993 +2.000E+03 2.021E+00 1.715E-04 2.021E+00 8.054E+02 8.049E+02 0.9994 +2.500E+03 2.004E+00 1.390E-04 2.004E+00 1.054E+03 1.053E+03 0.9995 +3.000E+03 2.001E+00 1.171E-04 2.001E+00 1.304E+03 1.303E+03 0.9995 +4.000E+03 2.012E+00 8.939E-05 2.012E+00 1.802E+03 1.802E+03 0.9996 +5.000E+03 2.031E+00 7.251E-05 2.031E+00 2.297E+03 2.296E+03 0.9996 +6.000E+03 2.052E+00 6.112E-05 2.052E+00 2.787E+03 2.786E+03 0.9997 +7.000E+03 2.072E+00 5.291E-05 2.072E+00 3.272E+03 3.271E+03 0.9997 +8.000E+03 2.091E+00 4.669E-05 2.091E+00 3.752E+03 3.751E+03 0.9997 +9.000E+03 2.109E+00 4.181E-05 2.109E+00 4.228E+03 4.227E+03 0.9997 +1.000E+04 2.126E+00 3.788E-05 2.126E+00 4.700E+03 4.699E+03 0.9998 diff --git a/src/test-path.c b/src/test-path.c index 523ebd4..62994d6 100644 --- a/src/test-path.c +++ b/src/test-path.c @@ -139,7 +139,7 @@ void usage(void) int main(int argc, char **argv) { size_t i, j, n, N, z, bins; - double pos0[3], dir0[3], theta0, range, pos2[3], dir[3], T, t, tmp; + double pos0[3], dir0[3], theta0, range, pos2[3], dir[3], beta, t, tmp; double *theta1, *theta2; z = 2; @@ -211,7 +211,7 @@ int main(int argc, char **argv) gsl_histogram_increment(h2[j], simvalue->z2[j]); } for (j = 0; j < N; j++) { - path_eval(simvalue->p,range*j/(N-1),pos2,dir,&T,&t,&tmp); + path_eval(simvalue->p,range*j/(N-1),pos2,dir,&beta,&t,&tmp); theta1[j*n+i] = simvalue->dir[j*3+1] - dir[1]; theta2[j*n+i] = simvalue->dir[j*3+2] - dir[2]; } @@ -282,7 +282,7 @@ int main(int argc, char **argv) fprintf(pipe, "e\n"); sim *simvalue = simulate_path(pos0,dir0,range,theta0,N,z); for (i = 0; i < N; i++) { - path_eval(simvalue->p,range*i/(N-1),pos2,dir,&T,&t,&tmp); + path_eval(simvalue->p,range*i/(N-1),pos2,dir,&beta,&t,&tmp); fprintf(pipe, "%g %g\n", i*range/(N-1), tmp); } @@ -307,7 +307,7 @@ int main(int argc, char **argv) fprintf(pipe,"e\n"); for (i = 0; i < N; i++) { - path_eval(simvalue->p,i*range/(N-1),pos2,dir,&T,&t,&theta0); + path_eval(simvalue->p,i*range/(N-1),pos2,dir,&beta,&t,&theta0); fprintf(pipe,"%.10g %.10g %.10g\n", pos2[0], pos2[1], pos2[2]); } fprintf(pipe,"e\n"); @@ -14,6 +14,10 @@ #include <gsl/gsl_spline.h> #include "vector.h" #include <gsl/gsl_statistics.h> +#include "electron.h" +#include "proton.h" +#include "likelihood.h" +#include "id_particles.h" typedef int testFunction(char *err); @@ -111,17 +115,89 @@ struct solid_angle_results { {2.0,2.0,0.282707} }; +int test_proton_get_energy(char *err) +{ + /* A very simple test to make sure the energy as a function of distance + * along the track makes sense. Should include more detailed tests later. */ + double T0, T; + particle *p; + + /* Assume initial kinetic energy is 1 GeV. */ + T0 = 1000.0; + p = particle_init(IDP_PROTON, T0,1.0,10000); + T = particle_get_energy(1e-9,p); + + /* At the beginning of the track we should have roughly the same energy. */ + if (!isclose(T, T0, 1e-5, 0)) { + sprintf(err, "KE = %.5f, but expected %.5f", T, T0); + goto err; + } + + T = particle_get_energy(p->range,p); + + /* At the end of the track the energy should be approximately 0. */ + if (!isclose(T, 0, 1e-5, 1e-5)) { + sprintf(err, "KE = %.5f, but expected %.5f", T, 0.0); + goto err; + } + + particle_free(p); + + return 0; + +err: + particle_free(p); + + return 1; +} + +int test_electron_get_energy(char *err) +{ + /* A very simple test to make sure the energy as a function of distance + * along the track makes sense. Should include more detailed tests later. */ + double T0, T; + particle *p; + + /* Assume initial kinetic energy is 1 GeV. */ + T0 = 1000.0; + p = particle_init(IDP_E_MINUS, T0,1.0,10000); + T = particle_get_energy(1e-9,p); + + /* At the beginning of the track we should have roughly the same energy. */ + if (!isclose(T, T0, 1e-5, 0)) { + sprintf(err, "KE = %.5f, but expected %.5f", T, T0); + goto err; + } + + T = particle_get_energy(p->range,p); + + /* At the end of the track the energy should be approximately 0. */ + if (!isclose(T, 0, 1e-5, 1e-5)) { + sprintf(err, "KE = %.5f, but expected %.5f", T, 0.0); + goto err; + } + + particle_free(p); + + return 0; + +err: + particle_free(p); + + return 1; +} + int test_muon_get_energy(char *err) { /* A very simple test to make sure the energy as a function of distance * along the track makes sense. Should include more detailed tests later. */ - double T0, T, range; - muon_energy *m; + double T0, T; + particle *p; /* Assume initial kinetic energy is 1 GeV. */ T0 = 1000.0; - m = muon_init_energy(T0,1.0,10000); - T = muon_get_energy(1e-9,m); + p = particle_init(IDP_MU_MINUS, T0,1.0,10000); + T = particle_get_energy(1e-9,p); /* At the beginning of the track we should have roughly the same energy. */ if (!isclose(T, T0, 1e-5, 0)) { @@ -129,8 +205,7 @@ int test_muon_get_energy(char *err) goto err; } - range = get_range(T0,1.0); - T = muon_get_energy(range,m); + T = particle_get_energy(p->range,p); /* At the end of the track the energy should be approximately 0. */ if (!isclose(T, 0, 1e-5, 1e-5)) { @@ -138,15 +213,44 @@ int test_muon_get_energy(char *err) goto err; } - muon_free_energy(m); + particle_free(p); return 0; err: - muon_free_energy(m); + particle_free(p); return 1; +} +int test_proton_get_range(char *err) +{ + /* A very simple test to make sure we read in the PDG table correctly. */ + double value; + + value = proton_get_range(1.0,1.0); + + if (!isclose(value, 2.458E-03, 1e-5, 0)) { + sprintf(err, "range = %.5f, but expected %.5f", value, 2.458E-03); + return 1; + } + + return 0; +} + +int test_electron_get_range(char *err) +{ + /* A very simple test to make sure we read in the PDG table correctly. */ + double value; + + value = electron_get_range(1.0,1.0); + + if (!isclose(value, 4.367e-01, 1e-5, 0)) { + sprintf(err, "range = %.5f, but expected %.5f", value, 4.367e-01); + return 1; + } + + return 0; } int test_muon_get_range(char *err) @@ -154,7 +258,7 @@ int test_muon_get_range(char *err) /* A very simple test to make sure we read in the PDG table correctly. */ double value; - value = get_range(1.0,1.0); + value = muon_get_range(1.0,1.0); if (!isclose(value, 2.071e-3, 1e-5, 0)) { sprintf(err, "range = %.5f, but expected %.5f", value, 1.863e-3); @@ -164,12 +268,42 @@ int test_muon_get_range(char *err) return 0; } +int test_proton_get_dEdx(char *err) +{ + /* A very simple test to make sure we read in the PDG table correctly. */ + double value; + + value = proton_get_dEdx(1.0,1.0); + + if (!isclose(value, 2.608E+02, 1e-5, 0)) { + sprintf(err, "dE/dx = %.5f, but expected %.5f", value, 2.608E+02); + return 1; + } + + return 0; +} + +int test_electron_get_dEdx(char *err) +{ + /* A very simple test to make sure we read in the PDG table correctly. */ + double value; + + value = electron_get_dEdx(1.0,1.0); + + if (!isclose(value, 1.862, 1e-5, 0)) { + sprintf(err, "dE/dx = %.5f, but expected %.5f", value, 1.862); + return 1; + } + + return 0; +} + int test_muon_get_dEdx(char *err) { /* A very simple test to make sure we read in the PDG table correctly. */ double value; - value = get_dEdx(1.0,1.0); + value = muon_get_dEdx(1.0,1.0); if (!isclose(value, 5.471, 1e-5, 0)) { sprintf(err, "dE/dx = %.5f, but expected %.5f", value, 6.097); @@ -341,10 +475,10 @@ int test_path(char *err) /* Tests the logsumexp() function. */ int i, j, k; double pos0[3], dir0[3], T0, range, m; - double T, t; + double beta, t; double pos_expected[3], t_expected; double pos[3], dir[3]; - double E, mom, beta; + double E, mom, beta0; double s; double theta0; @@ -372,11 +506,11 @@ int test_path(char *err) /* Calculate total energy */ E = T0 + m; mom = sqrt(E*E - m*m); - beta = mom/E; + beta0 = mom/E; - t_expected = s/(beta*SPEED_OF_LIGHT); + t_expected = s/(beta0*SPEED_OF_LIGHT); - path_eval(p,s,pos,dir,&T,&t,&theta0); + path_eval(p,s,pos,dir,&beta,&t,&theta0); for (k = 0; k < 3; k++) { if (!isclose(pos[k], pos_expected[k], 1e-9, 1e-9)) { @@ -392,8 +526,8 @@ int test_path(char *err) } } - if (!isclose(T, 1.0, 1e-9, 1e-9)) { - sprintf(err, "path_eval(%.2g) returned T = %.5g, but expected %.5g", s, T, 1.0); + if (!isclose(beta, beta0, 1e-9, 1e-9)) { + sprintf(err, "path_eval(%.2g) returned beta = %.5g, but expected %.5g", s, beta, beta0); return 1; } @@ -737,6 +871,12 @@ struct tests { {test_get_path_length, "test_get_path_length"}, {test_mean, "test_mean"}, {test_std, "test_std"}, + {test_electron_get_dEdx, "test_electron_get_dEdx"}, + {test_electron_get_range, "test_electron_get_range"}, + {test_electron_get_energy, "test_electron_get_energy"}, + {test_proton_get_dEdx, "test_proton_get_dEdx"}, + {test_proton_get_range, "test_proton_get_range"}, + {test_proton_get_energy, "test_proton_get_energy"}, }; int main(int argc, char **argv) |