aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/electron.c39
-rw-r--r--src/electron.h8
-rw-r--r--src/likelihood.c149
-rw-r--r--src/likelihood.h12
-rw-r--r--src/muon.c110
-rw-r--r--src/muon.h14
-rw-r--r--src/proton.h9
7 files changed, 281 insertions, 60 deletions
diff --git a/src/electron.c b/src/electron.c
index 261ba39..c6b5ed8 100644
--- a/src/electron.c
+++ b/src/electron.c
@@ -161,6 +161,45 @@ static double electron_get_angular_pdf_norm(double alpha, double beta, double mu
return result;
}
+double electron_get_angular_pdf_delta_ray(double cos_theta, double alpha, double beta, double mu)
+{
+ /* Returns the probability density that a photon in the wavelength range
+ * 200 nm - 800 nm is emitted at an angle cos_theta.
+ *
+ * The angular distribution is modelled by the pdf:
+ *
+ * f(cos_theta) ~ exp(-abs(cos_theta-mu)^alpha/beta)
+ *
+ * where alpha and beta are constants which depend on the initial energy of
+ * the particle.
+ *
+ * There is no nice closed form solution for the integral of this function,
+ * so we just compute it on the fly. To make things faster, we keep track
+ * of the last alpha, beta, and mu parameters that were passed in so we
+ * avoid recomputing the normalization factor. */
+ size_t i;
+ static double last_alpha, last_beta, last_mu, norm;
+ static int first = 1;
+ static double x[10000], y[10000];
+
+ if (first || alpha != last_alpha || beta != last_beta || mu != last_mu) {
+ norm = electron_get_angular_pdf_norm(alpha, beta, mu);
+
+ last_alpha = alpha;
+ last_beta = beta;
+ last_mu = mu;
+
+ for (i = 0; i < LEN(x); i++) {
+ x[i] = -1.0 + 2.0*i/(LEN(x)-1);
+ y[i] = electron_get_angular_pdf_no_norm(x[i], alpha, beta, mu)/norm;
+ }
+
+ first = 0;
+ }
+
+ return interp1d(cos_theta,x,y,LEN(x));
+}
+
double electron_get_angular_pdf(double cos_theta, double alpha, double beta, double mu)
{
/* Returns the probability density that a photon in the wavelength range
diff --git a/src/electron.h b/src/electron.h
index cb3f24e..20e5ea2 100644
--- a/src/electron.h
+++ b/src/electron.h
@@ -3,9 +3,17 @@
#include <stddef.h> /* for size_t */
+/* Number of photons in the range 200 nm - 800 nm generated per MeV of energy
+ * lost to radiation for electrons.
+ *
+ * FIXME: This is just a rough estimate, should use an energy dependent
+ * quantity from simulation. */
+#define ELECTRON_PHOTONS_PER_MEV 400.0
+
void electron_get_position_distribution_parameters(double T0, double *a, double *b);
double electron_get_angular_distribution_alpha(double T0);
double electron_get_angular_distribution_beta(double T0);
+double electron_get_angular_pdf_delta_ray(double cos_theta, double alpha, double beta, double mu);
double electron_get_angular_pdf(double cos_theta, double alpha, double beta, double mu);
double electron_get_range(double T, double rho);
double electron_get_dEdx_rad(double T, double rho);
diff --git a/src/likelihood.c b/src/likelihood.c
index 786b221..8e12f57 100644
--- a/src/likelihood.c
+++ b/src/likelihood.c
@@ -42,16 +42,19 @@ particle *particle_init(int id, double T0, size_t n)
* 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;
+ double dEdx, rad;
particle *p = malloc(sizeof(particle));
p->id = id;
p->x = malloc(sizeof(double)*n);
p->T = malloc(sizeof(double)*n);
p->n = n;
- p->rad = 0.0;
p->a = 0.0;
p->b = 0.0;
+ p->shower_photons = 0.0;
+ p->delta_ray_a = 0.0;
+ p->delta_ray_b = 0.0;
+ p->delta_ray_photons = 0.0;
p->x[0] = 0;
p->T[0] = T0;
@@ -67,14 +70,17 @@ particle *particle_init(int id, double T0, size_t n)
p->a = electron_get_angular_distribution_alpha(T0);
p->b = electron_get_angular_distribution_beta(T0);
+ p->delta_ray_photons = 0.0;
+
/* Now we loop over the points along the track and assume dE/dx is
* constant between points. */
+ rad = 0.0;
for (i = 1; i < n; i++) {
p->x[i] = p->range*i/(n-1);
dEdx = electron_get_dEdx(p->T[i-1], WATER_DENSITY);
p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]);
dEdx = electron_get_dEdx_rad(p->T[i-1], WATER_DENSITY);
- p->rad += dEdx*(p->x[i]-p->x[i-1]);
+ rad += dEdx*(p->x[i]-p->x[i-1]);
}
/* Make sure that the energy is zero at the last step. This is so that
* when we try to bisect the point along the track where the speed of
@@ -85,6 +91,8 @@ particle *particle_init(int id, double T0, size_t n)
* range here using the dE/dx table instead of reading in the range. */
p->T[n-1] = 0;
+ p->shower_photons = rad*ELECTRON_PHOTONS_PER_MEV;
+
break;
case IDP_MU_MINUS:
case IDP_MU_PLUS:
@@ -96,14 +104,21 @@ particle *particle_init(int id, double T0, size_t n)
* load the correct table based on the media. */
p->range = muon_get_range(T0, HEAVY_WATER_DENSITY);
+ p->a = muon_get_angular_distribution_alpha(T0);
+ p->b = muon_get_angular_distribution_beta(T0);
+
+ muon_get_delta_ray_distribution_parameters(T0, &p->delta_ray_a, &p->delta_ray_b);
+ p->delta_ray_photons = muon_get_delta_ray_photons(T0);
+
/* Now we loop over the points along the track and assume dE/dx is
* constant between points. */
+ rad = 0.0;
for (i = 1; i < n; i++) {
p->x[i] = p->range*i/(n-1);
dEdx = muon_get_dEdx(p->T[i-1], HEAVY_WATER_DENSITY);
p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]);
dEdx = muon_get_dEdx_rad(p->T[i-1], WATER_DENSITY);
- p->rad += dEdx*(p->x[i]-p->x[i-1]);
+ rad += dEdx*(p->x[i]-p->x[i-1]);
}
/* Make sure that the energy is zero at the last step. This is so that
* when we try to bisect the point along the track where the speed of
@@ -114,6 +129,8 @@ particle *particle_init(int id, double T0, size_t n)
* range here using the dE/dx table instead of reading in the range. */
p->T[n-1] = 0;
+ p->shower_photons = rad*MUON_PHOTONS_PER_MEV;
+
break;
case IDP_PROTON:
p->mass = PROTON_MASS;
@@ -121,14 +138,17 @@ particle *particle_init(int id, double T0, size_t n)
* tables for heavy water for protons. */
p->range = proton_get_range(T0, WATER_DENSITY);
+ p->delta_ray_photons = 0.0;
+
/* Now we loop over the points along the track and assume dE/dx is
* constant between points. */
+ rad = 0.0;
for (i = 1; i < n; i++) {
p->x[i] = p->range*i/(n-1);
dEdx = proton_get_dEdx(p->T[i-1], WATER_DENSITY);
p->T[i] = p->T[i-1] - dEdx*(p->x[i]-p->x[i-1]);
dEdx = proton_get_dEdx_rad(p->T[i-1], WATER_DENSITY);
- p->rad += dEdx*(p->x[i]-p->x[i-1]);
+ rad += dEdx*(p->x[i]-p->x[i-1]);
}
/* Make sure that the energy is zero at the last step. This is so that
* when we try to bisect the point along the track where the speed of
@@ -139,6 +159,8 @@ particle *particle_init(int id, double T0, size_t n)
* range here using the dE/dx table instead of reading in the range. */
p->T[n-1] = 0;
+ p->shower_photons = rad*PROTON_PHOTONS_PER_MEV;
+
break;
default:
fprintf(stderr, "unknown particle id %i\n", id);
@@ -170,9 +192,9 @@ void particle_free(particle *p)
free(p);
}
-static double get_expected_charge_shower(double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r, double *reflected, double n_d2o, double n_h2o, double l_d2o, double l_h2o, double alpha, double beta, double rad)
+static double get_expected_charge_shower(particle *p, double *pos, double *dir, double *pmt_pos, double *pmt_normal, double r, double *reflected, double n_d2o, double n_h2o, double l_d2o, double l_h2o, double *q_delta_ray, double *q_indirect_delta_ray)
{
- double pmt_dir[3], cos_theta, omega, f, f_reflec, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_acrylic, theta_pmt, charge, scattering_length_h2o, scattering_length_d2o, scatter;
+ double pmt_dir[3], cos_theta, omega, f, f_reflec, cos_theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_acrylic, theta_pmt, charge, scattering_length_h2o, scattering_length_d2o, scatter, constant;
SUB(pmt_dir,pmt_pos,pos);
@@ -180,6 +202,9 @@ static double get_expected_charge_shower(double *pos, double *dir, double *pmt_p
cos_theta_pmt = DOT(pmt_dir,pmt_normal);
+ charge = 0.0;
+ *q_delta_ray = 0.0;
+ *q_indirect_delta_ray = 0.0;
*reflected = 0.0;
if (cos_theta_pmt > 0) return 0.0;
@@ -201,11 +226,26 @@ static double get_expected_charge_shower(double *pos, double *dir, double *pmt_p
l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER;
- charge = exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic)*get_weighted_quantum_efficiency()*rad*PHOTONS_PER_MEV*electron_get_angular_pdf(cos_theta,alpha,beta,1/n_d2o)*omega/(2*M_PI);
+ constant = exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic)*get_weighted_quantum_efficiency()*omega/(2*M_PI);
+
+ /* Note: We assume here that the peak of the angular distribution is at the
+ * Cerenkov angle for a particle with beta = 1. This seems to be an OK
+ * assumption for high energy showers, but is not exactly correct for
+ * showers from lower energy electrons or for delta rays from lower energy
+ * muons. It seems good enough, but in the future it would be nice to
+ * parameterize this. */
+ if (p->shower_photons > 0)
+ charge = constant*p->shower_photons*electron_get_angular_pdf(cos_theta,p->a,p->b,1/n_d2o);
+
+ if (p->delta_ray_photons > 0)
+ *q_delta_ray = constant*p->delta_ray_photons*electron_get_angular_pdf_delta_ray(cos_theta,p->delta_ray_a,p->delta_ray_b,1/n_d2o);
scatter = exp(-l_d2o/scattering_length_d2o-l_h2o/scattering_length_h2o);
*reflected = (f_reflec + 1.0 - scatter)*charge;
+ *q_indirect_delta_ray = (f_reflec + 1.0 - scatter)*(*q_delta_ray);
+
+ *q_delta_ray *= f*scatter;
return f*charge*scatter;
}
@@ -345,14 +385,14 @@ double log_pt(double t, size_t n, double mu_noise, double mu_indirect, double *m
return ln(n) + (n-1)*log1p(-time_cdf(t,mu_noise,mu_indirect,mu_direct,mu_shower,n2,ts,ts_shower,tmean,sigma,ts_sigma)) + log(time_pdf(t,mu_noise,mu_indirect,mu_direct,mu_shower,n2,ts,ts_shower,tmean,sigma,ts_sigma));
}
-static void integrate_path_shower(particle *p, double *x, double *pdf, double T0, double *pos0, double *dir0, int pmt, size_t n, double *mu_direct, double *mu_indirect, double *time, double *sigma, double rad)
+static void integrate_path_shower(particle *p, double *x, double *pdf, double T0, double *pos0, double *dir0, int pmt, size_t n, double *mu_direct, double *mu_indirect, double *time, double *sigma)
{
size_t i;
static double qs[MAX_NPOINTS];
static double q_indirects[MAX_NPOINTS];
static double ts[MAX_NPOINTS];
static double ts2[MAX_NPOINTS];
- double pos[3], n_d2o, n_h2o, wavelength0, t, l_d2o, l_h2o;
+ double pos[3], n_d2o, n_h2o, wavelength0, t, l_d2o, l_h2o, q_delta_ray, q_indirect_delta_ray;
if (n > MAX_NPOINTS) {
fprintf(stderr, "number of points is greater than MAX_NPOINTS!\n");
@@ -364,6 +404,8 @@ static void integrate_path_shower(particle *p, double *x, double *pdf, double T0
n_d2o = get_index_snoman_d2o(wavelength0);
n_h2o = get_index_snoman_h2o(wavelength0);
+ double range = x[n-1] - x[0];
+
for (i = 0; i < n; i++) {
pos[0] = pos0[0] + x[i]*dir0[0];
pos[1] = pos0[1] + x[i]*dir0[1];
@@ -373,8 +415,9 @@ static void integrate_path_shower(particle *p, double *x, double *pdf, double T0
t = x[i]/SPEED_OF_LIGHT + l_d2o*n_d2o/SPEED_OF_LIGHT + l_h2o*n_h2o/SPEED_OF_LIGHT;
- qs[i] = get_expected_charge_shower(pos, dir0, pmts[pmt].pos, pmts[pmt].normal, PMT_RADIUS, q_indirects+i, n_d2o, n_h2o, l_d2o, l_h2o, p->a, p->b, rad)*pdf[i];
- q_indirects[i] *= pdf[i];
+ qs[i] = get_expected_charge_shower(p, pos, dir0, pmts[pmt].pos, pmts[pmt].normal, PMT_RADIUS, q_indirects+i, n_d2o, n_h2o, l_d2o, l_h2o, &q_delta_ray, &q_indirect_delta_ray);
+ qs[i] = qs[i]*pdf[i] + q_delta_ray/range;
+ q_indirects[i] = q_indirects[i]*pdf[i] + q_indirect_delta_ray/range;
ts[i] = t*qs[i];
ts2[i] = t*t*qs[i];
}
@@ -593,10 +636,11 @@ static double get_total_charge_approx(double beta0, double *pos, double *dir, pa
charge = abs_prob*n_d2o*x*beta0*prob*(1/sin_theta)*omega*(erf((a+b*(smax-s)+n_d2o*(smax-z)*beta0)/frac) + erf((-a+b*s+n_d2o*z*beta0)/frac))/(b+n_d2o*beta0)/(4*M_PI);
- /* Add expected number of photons from electromagnetic shower for electrons
- * and positrons. */
- if (p->id == IDP_E_MINUS || p->id == IDP_E_PLUS)
- charge += abs_prob*get_weighted_quantum_efficiency()*p->rad*PHOTONS_PER_MEV*electron_get_angular_pdf(cos_theta,p->a,p->b,1.0/n_d2o)*omega/(2*M_PI);
+ /* Add expected number of photons from electromagnetic shower. */
+ if (p->shower_photons > 0)
+ charge += abs_prob*get_weighted_quantum_efficiency()*p->shower_photons*electron_get_angular_pdf(cos_theta,p->a,p->b,1.0/n_d2o)*omega/(2*M_PI);
+ if (p->delta_ray_photons > 0)
+ charge += abs_prob*get_weighted_quantum_efficiency()*p->delta_ray_photons*electron_get_angular_pdf_delta_ray(cos_theta,p->delta_ray_a,p->delta_ray_b,1.0/n_d2o)*omega/(2*M_PI);
*mu_reflected = f_reflected*charge;
@@ -891,6 +935,7 @@ double nll(event *ev, vertex *v, size_t n, double dx, double dx_shower, int fast
npoints = (size_t) (range/dx + 0.5);
if (npoints < MIN_NPOINTS) npoints = MIN_NPOINTS;
+ if (npoints > MAX_NPOINTS) npoints = MAX_NPOINTS;
/* Calculate total energy */
E0 = v[j].T0 + p->mass;
@@ -903,32 +948,43 @@ double nll(event *ev, vertex *v, size_t n, double dx, double dx_shower, int fast
if (!fast) {
path = path_init(v[j].pos, v[j].dir, v[j].T0, range, npoints, theta0, getKineticEnergy, p, v[j].z1, v[j].z2, v[j].n, p->mass);
- if (v[j].id == IDP_E_MINUS || v[j].id == IDP_E_PLUS) {
+ switch (v[j].id) {
+ case IDP_E_MINUS:
+ case IDP_E_PLUS:
electron_get_position_distribution_parameters(v[j].T0, &pos_a, &pos_b);
+ break;
+ case IDP_MU_MINUS:
+ case IDP_MU_PLUS:
+ muon_get_position_distribution_parameters(v[j].T0, &pos_a, &pos_b);
+ break;
+ default:
+ fprintf(stderr, "unknown particle id %i\n", v[j].id);
+ exit(1);
+ }
- /* Lower and upper limits to integrate for the electromagnetic
- * shower. */
- xlo = 0.0;
- xhi = gsl_cdf_gamma_Pinv(0.99,pos_a,pos_b);
+ /* Lower and upper limits to integrate for the electromagnetic
+ * shower. */
+ xlo = 0.0;
+ xhi = gsl_cdf_gamma_Pinv(0.99,pos_a,pos_b);
- /* Number of points to sample along the longitudinal direction for
- * the electromagnetic shower. */
- npoints_shower = (size_t) ((xhi-xlo)/dx_shower + 0.5);
+ /* Number of points to sample along the longitudinal direction for
+ * the electromagnetic shower. */
+ npoints_shower = (size_t) ((xhi-xlo)/dx_shower + 0.5);
- if (npoints_shower < MIN_NPOINTS) npoints_shower = MIN_NPOINTS;
+ if (npoints_shower < MIN_NPOINTS) npoints_shower = MIN_NPOINTS;
+ if (npoints_shower > MAX_NPOINTS) npoints_shower = MAX_NPOINTS;
- x = malloc(sizeof(double)*npoints_shower);
- pdf = malloc(sizeof(double)*npoints_shower);
- for (i = 0; i < npoints_shower; i++) {
- x[i] = xlo + i*(xhi-xlo)/(npoints_shower-1);
- pdf[i] = gamma_pdf(x[i],pos_a,pos_b);
- }
+ x = malloc(sizeof(double)*npoints_shower);
+ pdf = malloc(sizeof(double)*npoints_shower);
+ for (i = 0; i < npoints_shower; i++) {
+ x[i] = xlo + i*(xhi-xlo)/(npoints_shower-1);
+ pdf[i] = gamma_pdf(x[i],pos_a,pos_b);
+ }
- /* Make sure the PDF is normalized. */
- double norm = trapz(pdf,x[1]-x[0],npoints_shower);
- for (i = 0; i < npoints_shower; i++) {
- pdf[i] /= norm;
- }
+ /* Make sure the PDF is normalized. */
+ double norm = trapz(pdf,x[1]-x[0],npoints_shower);
+ for (i = 0; i < npoints_shower; i++) {
+ pdf[i] /= norm;
}
}
@@ -982,27 +1038,16 @@ double nll(event *ev, vertex *v, size_t n, double dx, double dx_shower, int fast
ts[i][j] = v[j].t0 + guess_time(v[j].pos,v[j].dir,pmts[i].pos,smax,n_d2o,n_h2o,cos_theta_cerenkov,sin_theta_cerenkov);
}
- if (v[j].id == IDP_E_MINUS || v[j].id == IDP_E_PLUS) {
- /* If we are fitting for an electron or positron, we need to
- * estimate the expected number of photons produced by the
- * electromagnetic shower.
- *
- * Technically we should also have a term for the electromagnetic
- * shower produced by muons, but for the energy range I'm
- * considering right now, it is very small. */
- integrate_path_shower(p,x,pdf,v[j].T0,v[j].pos,v[j].dir,i,npoints_shower,&mu_shower[i][j],&q_indirect,&result,&ts_sigma[i][j],p->rad);
- mu_indirect[j] += q_indirect;
- ts_shower[i][j] = v[j].t0 + result;
- }
+ integrate_path_shower(p,x,pdf,v[j].T0,v[j].pos,v[j].dir,i,npoints_shower,&mu_shower[i][j],&q_indirect,&result,&ts_sigma[i][j]);
+ mu_indirect[j] += q_indirect;
+ ts_shower[i][j] = v[j].t0 + result;
}
if (!fast) {
path_free(path);
- if (v[j].id == IDP_E_MINUS || v[j].id == IDP_E_PLUS) {
- free(x);
- free(pdf);
- }
+ free(x);
+ free(pdf);
}
particle_free(p);
diff --git a/src/likelihood.h b/src/likelihood.h
index 84fa7ff..4b72742 100644
--- a/src/likelihood.h
+++ b/src/likelihood.h
@@ -45,13 +45,6 @@
#define GTVALID 400.0
#define BETA_MIN 0.8
-/* Number of photons in the range 200 nm - 800 nm generated per MeV of energy
- * lost to radiation for electrons.
- *
- * FIXME: This is just a rough estimate, should use an energy dependent
- * quantity from simulation. */
-#define PHOTONS_PER_MEV 400.0
-
/* Maximum number of vertices to fit. */
#define MAX_VERTICES 10
@@ -70,12 +63,15 @@ typedef struct particle {
int id;
double mass;
double range;
- double rad;
double *x;
double *T;
size_t n;
double a;
double b;
+ double shower_photons;
+ double delta_ray_a;
+ double delta_ray_b;
+ double delta_ray_photons;
} particle;
particle *particle_init(int id, double T0, size_t n);
diff --git a/src/muon.c b/src/muon.c
index 76e5223..6ab0aa7 100644
--- a/src/muon.c
+++ b/src/muon.c
@@ -40,6 +40,116 @@ static gsl_spline *spline_range;
static const double MUON_CRITICAL_ENERGY_H2O = 1029.0e6;
static const double MUON_CRITICAL_ENERGY_D2O = 967.0e3;
+void muon_get_position_distribution_parameters(double T0, double *a, double *b)
+{
+ /* Computes the gamma distribution parameters describing the longitudinal
+ * profile of the photons generated from an electromagnetic shower.
+ *
+ * From the PDG "Passage of Particles" section 32.5:
+ *
+ * "The mean longitudinal profile of the energy deposition in an
+ * electromagnetic cascade is reasonably well described by a gamma
+ * distribution."
+ *
+ * Here we use a slightly different form of the gamma distribution:
+ *
+ * f(x) = x**(a-1)*exp(-x/b)/(Gamma(a)*b**a)
+ *
+ * I determined the b parameter by simulating high energy muons using
+ * RAT-PAC and determined that it's roughly equal to the radiation length.
+ * To calculate the a parameter we use the formula from the PDG, i.e.
+ *
+ * tmax = (a-1)/b = ln(E/E_C) - 0.5
+ *
+ * Therefore, we calculate a as:
+ *
+ * a = tmax*b+1.
+ *
+ * `T` should be in units of MeV.
+ *
+ * Example:
+ *
+ * double a, b;
+ * muon_get_position_distribution_parameters(1000.0, &a, &b);
+ * double pdf = gamma_pdf(x, a, b);
+ *
+ * See http://pdg.lbl.gov/2014/reviews/rpp2014-rev-passage-particles-matter.pdf.
+ *
+ * FIXME: Double check that this is correct for muons. */
+ double tmax;
+
+ *b = RADIATION_LENGTH;
+ tmax = log(T0/MUON_CRITICAL_ENERGY_D2O) - 0.5;
+ *a = fmax(1.1,tmax*(*b)/RADIATION_LENGTH + 1);
+}
+
+double muon_get_angular_distribution_alpha(double T0)
+{
+ /* To describe the angular distribution of photons in an electromagnetic
+ * shower I came up with the heuristic form:
+ *
+ * f(cos_theta) ~ exp(-abs(cos_theta-mu)^alpha/beta)
+ *
+ * I simulated high energy muons using RAT-PAC in heavy water to fit
+ * for the alpha and beta parameters as a function of energy and determined
+ * that a reasonably good fit to the data was:
+ *
+ * alpha = c0 + c1/log(c2*T0 + c3)
+ *
+ * where T0 is the initial energy of the muon in MeV and c0, c1, c2,
+ * and c3 are constants which I fit out. */
+ return 8.238633e-01 + 3.896665e-03/log(1.581060e-05*T0 + 9.991576e-01);
+}
+
+double muon_get_angular_distribution_beta(double T0)
+{
+ /* To describe the angular distribution of photons in an electromagnetic
+ * shower I came up with the heuristic form:
+ *
+ * f(cos_theta) ~ exp(-abs(cos_theta-mu)^alpha/beta)
+ *
+ * I simulated high energy muons using RAT-PAC in heavy water to fit
+ * for the alpha and beta parameters as a function of energy and determined
+ * that a reasonably good fit to the data was:
+ *
+ * beta = c0 + c1/log(c2*T0 + c3)
+ *
+ * where T0 is the initial energy of the muon in MeV and c0, c1, c2,
+ * and c3 are constants which I fit out. */
+ return 2.236058e-01 + 5.376336e-03/log(1.200215e-05*T0 + 1.002540e+00);
+}
+
+double muon_get_delta_ray_photons(double T0)
+{
+ /* Returns the number of photons in the range 200-800 nm produced by delta
+ * rays.
+ *
+ * This comes from a simple polynomial fit to the number of photons
+ * generated by simulating muons using RAT-PAC in heavy water. */
+ return fmax(0.0,-7532.39 + 39.4548*T0);
+}
+
+void muon_get_delta_ray_distribution_parameters(double T0, double *a, double *b)
+{
+ /* To describe the angular distribution of photons from delta rays I use
+ * the same heuristic form as used for electromagnetic showers:
+ *
+ * f(cos_theta) ~ exp(-abs(cos_theta-mu)^alpha/beta)
+ *
+ * I simulated high energy muons using RAT-PAC in heavy water to fit
+ * for the alpha and beta parameters as a function of energy and determined
+ * that a reasonably good fit to the data was:
+ *
+ * beta = c0 + c1/log(c2*T0 + c3)
+ *
+ * where T0 is the initial energy of the muon in MeV and c0, c1, c2,
+ * and c3 are constants which I fit out. */
+ *a = 3.463093e-01 + 1.110835e-02/log(5.662948e-06*T0 + 1.009215e+00);
+ *b = 2.297358e-01 + 4.085721e-03/log(8.218774e-06*T0 + 1.007135e+00);
+
+ return;
+}
+
static int init()
{
int i, j;
diff --git a/src/muon.h b/src/muon.h
index 586d67d..456c11e 100644
--- a/src/muon.h
+++ b/src/muon.h
@@ -5,6 +5,20 @@
#define EULER_CONSTANT 0.57721
+/* Number of photons in the range 200 nm - 800 nm generated per MeV of energy
+ * lost to radiation for electrons.
+ *
+ * FIXME: This is just a rough estimate, should use an energy dependent
+ * quantity from simulation.
+ *
+ * FIXME: Actually determine what this is. */
+#define MUON_PHOTONS_PER_MEV 7368.0
+
+void muon_get_position_distribution_parameters(double T0, double *a, double *b);
+double muon_get_angular_distribution_alpha(double T0);
+double muon_get_angular_distribution_beta(double T0);
+void muon_get_delta_ray_distribution_parameters(double T0, double *a, double *b);
+double muon_get_delta_ray_photons(double T0);
double muon_get_range(double T, double rho);
double muon_get_dEdx_rad(double T, double rho);
double muon_get_dEdx(double T, double rho);
diff --git a/src/proton.h b/src/proton.h
index 008f835..cffeb9f 100644
--- a/src/proton.h
+++ b/src/proton.h
@@ -3,6 +3,15 @@
#include <stddef.h> /* for size_t */
+/* Number of photons in the range 200 nm - 800 nm generated per MeV of energy
+ * lost to radiation for electrons.
+ *
+ * FIXME: This is just a rough estimate, should use an energy dependent
+ * quantity from simulation.
+ *
+ * FIXME: Actually determine what this is. */
+#define PROTON_PHOTONS_PER_MEV 400.0
+
double proton_get_range(double T, double rho);
double proton_get_dEdx_rad(double T, double rho);
double proton_get_dEdx(double T, double rho);