aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/likelihood.c80
-rw-r--r--src/optics.c600
-rw-r--r--src/optics.h31
-rw-r--r--src/sno.h1
4 files changed, 420 insertions, 292 deletions
diff --git a/src/likelihood.c b/src/likelihood.c
index 45274b7..379ac9a 100644
--- a/src/likelihood.c
+++ b/src/likelihood.c
@@ -210,7 +210,7 @@ void particle_free(particle *p)
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, constant;
+ double pmt_dir[3], cos_theta, omega, f, f_reflec, cos_theta_pmt, theta_pmt, charge, constant, prob_abs, prob_sct;
SUB(pmt_dir,pmt_pos,pos);
@@ -234,15 +234,10 @@ static double get_expected_charge_shower(particle *p, double *pos, double *dir,
f_reflec = get_weighted_pmt_reflectivity(theta_pmt);
f = get_weighted_pmt_response(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();
- scattering_length_d2o = get_weighted_rayleigh_scattering_length_snoman_d2o();
- scattering_length_h2o = get_weighted_rayleigh_scattering_length_snoman_h2o();
+ prob_abs = 1.0 - get_fabs_d2o(l_d2o)*get_fabs_h2o(l_h2o)*get_fabs_acrylic(AV_THICKNESS);
+ prob_sct = 1.0 - get_fsct_d2o(l_d2o)*get_fsct_h2o(l_h2o);
- l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER;
-
- 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);
+ constant = 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
@@ -256,19 +251,17 @@ static double get_expected_charge_shower(particle *p, double *pos, double *dir,
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);
+ *reflected = (1.0-prob_abs)*(1.0-prob_sct)*f_reflec*charge + prob_sct*charge;
+ *q_indirect_delta_ray = (1.0-prob_abs)*(1.0-prob_sct)*f_reflec*(*q_delta_ray) + prob_sct*(*q_delta_ray);
- *q_delta_ray *= f*scatter;
+ *q_delta_ray *= (1.0-prob_abs)*(1.0-prob_sct)*f;
- return f*charge*scatter;
+ return (1.0-prob_abs)*(1.0-prob_sct)*f*charge;
}
static double get_expected_charge(double x, double beta, double theta0, 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 pmt_dir[3], cos_theta, n, omega, z, R, 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, n, omega, z, R, f, f_reflec, cos_theta_pmt, theta_pmt, charge, prob_abs, prob_sct;
z = 1.0;
@@ -300,21 +293,29 @@ static double get_expected_charge(double x, double beta, double theta0, double *
f_reflec = get_weighted_pmt_reflectivity(theta_pmt);
f = get_weighted_pmt_response(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();
- scattering_length_d2o = get_weighted_rayleigh_scattering_length_snoman_d2o();
- scattering_length_h2o = get_weighted_rayleigh_scattering_length_snoman_h2o();
-
- 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)*omega*FINE_STRUCTURE_CONSTANT*z*z*(1-(1/(beta*beta*n*n)))*get_probability(beta, cos_theta, theta0);
+ /* Probability that a photon is absorbed. We calculate this by computing:
+ *
+ * 1.0 - P(not absorbed in D2O)*P(not absorbed in H2O)*P(not absorbed in acrylic)
+ *
+ * since if we worked with the absorption probabilities directly it would
+ * be more complicated, i.e.
+ *
+ * P(absorbed in D2O) + P(absorbed in acrylic|not absorbed in D2O)*P(not absorbed in D2O) + ...
+ *
+ */
+ prob_abs = 1.0 - get_fabs_d2o(l_d2o)*get_fabs_h2o(l_h2o)*get_fabs_acrylic(AV_THICKNESS);
+ /* Similiar calculation for the probability that a photon is scattered.
+ *
+ * Technically we should compute this conditionally on the probability that
+ * a photon is not absorbed, but since the probability of scattering is
+ * pretty low, this is expected to be a very small effect. */
+ prob_sct = 1.0 - get_fsct_d2o(l_d2o)*get_fsct_h2o(l_h2o);
- scatter = exp(-l_d2o/scattering_length_d2o-l_h2o/scattering_length_h2o);
+ charge = omega*FINE_STRUCTURE_CONSTANT*z*z*(1-(1/(beta*beta*n*n)))*get_probability(beta, cos_theta, theta0);
- *reflected = (f_reflec + 1.0 - scatter)*charge;
+ *reflected = (1.0-prob_abs)*(1.0-prob_sct)*f_reflec*charge + prob_sct*charge;
- return f*charge*scatter;
+ return (1.0-prob_abs)*(1.0-prob_sct)*f*charge;
}
double time_cdf(double t, double mu_noise, double mu_indirect, double *mu_direct, double *mu_shower, size_t n, double *ts, double *ts_shower, double tmean, double sigma, double *ts_sigma)
@@ -544,7 +545,7 @@ static double get_total_charge_approx(double beta0, double *pos, double *dir, pa
*
* `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, mom, T, omega, sin_theta, f, cos_theta_pmt, theta_pmt, absorption_length_h2o, absorption_length_d2o, absorption_length_acrylic, l_h2o, l_d2o, l_acrylic, f_reflected, charge, prob, frac;
+ double pmt_dir[3], tmp[3], R, cos_theta, x, z, s, a, b, beta, E, mom, T, omega, sin_theta, f, cos_theta_pmt, theta_pmt, l_h2o, l_d2o, f_reflec, charge, prob, frac, prob_abs, prob_sct;
/* First, we find the point along the track where the PMT is at the
* Cerenkov angle. */
@@ -643,32 +644,27 @@ static double get_total_charge_approx(double beta0, double *pos, double *dir, pa
theta_pmt = acos(-cos_theta_pmt);
f = get_weighted_pmt_response(theta_pmt);
- f_reflected = get_weighted_pmt_reflectivity(theta_pmt);
+ f_reflec = get_weighted_pmt_reflectivity(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();
+ prob_abs = 1.0 - get_fabs_d2o(l_d2o)*get_fabs_h2o(l_h2o)*get_fabs_acrylic(AV_THICKNESS);
+ prob_sct = 1.0 - get_fsct_d2o(l_d2o)*get_fsct_h2o(l_h2o);
get_path_length(tmp,pmts[i].pos,AV_RADIUS,&l_d2o,&l_h2o);
- l_acrylic = AV_RADIUS_OUTER - AV_RADIUS_INNER;
-
/* Assume the particle is travelling at the speed of light. */
*t = s/SPEED_OF_LIGHT + l_d2o*n_d2o/SPEED_OF_LIGHT + l_h2o*n_h2o/SPEED_OF_LIGHT;
- double abs_prob = exp(-l_d2o/absorption_length_d2o-l_h2o/absorption_length_h2o-l_acrylic/absorption_length_acrylic);
-
- 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);
+ charge = 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. */
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);
+ charge += 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);
+ charge += 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;
+ *mu_reflected = (1.0-prob_abs)*(1.0-prob_sct)*f_reflec*charge + prob_sct*charge;
- return f*charge;
+ return (1.0-prob_abs)*(1.0-prob_sct)*f*charge;
}
typedef struct betaRootParams {
diff --git a/src/optics.c b/src/optics.c
index d626bf7..830ada9 100644
--- a/src/optics.c
+++ b/src/optics.c
@@ -25,11 +25,12 @@
#include "misc.h"
#include "db.h"
#include "dict.h"
+#include "sno.h"
char optics_err[256];
-/* Absorption coefficients for H2O and D2O as a function of wavelength from
- * SNOMAN.
+/* Absorption coefficients for H2O, D2O, and acrylic as a function of
+ * wavelength from SNOMAN.
*
* Note: These numbers come from mcprod/media_qoca_d2o_20060110.cmd. */
static double absorption_coefficient_h2o_wavelengths[6] = {337.0, 365.0, 386.0, 420.0, 500.0, 620.0};
@@ -47,21 +48,32 @@ static double absorption_coefficient_acrylic[6] = {5.610e-02, 2.279e-02, 1.204e-
static int initialized = 0;
-/* D2O absorption length weighted by the Cerenkov spectrum and the quantum
- * efficiency. */
-static double absorption_length_d2o_weighted = 0.0;
-/* H2O absorption length weighted by the Cerenkov spectrum and the quantum
- * efficiency. */
-static double absorption_length_h2o_weighted = 0.0;
-/* Acrylic absorption length weighted by the Cerenkov spectrum and the quantum
- * efficiency. */
-static double absorption_length_acrylic_weighted = 0.0;
-/* D2O Rayleigh scattering length weighted by the Cerenkov spectrum and the
- * quantum efficiency. */
-static double rayleigh_scattering_length_d2o_weighted = 0.0;
-/* H2O Rayleigh scattering length weighted by the Cerenkov spectrum and the
- * quantum efficiency. */
-static double rayleigh_scattering_length_h2o_weighted = 0.0;
+/* Number of points to sample when computing the average absorption and
+ * scattering probabilities.
+ *
+ * Note that we use this array to sample for very small distances when
+ * computing the absorption in the acrylic so (xhi-xlo)/N should be less than
+ * 10 cm. */
+#define N 10000
+
+/* Lower and upper bounds for the distances used to calculate the average
+ * absorption and scattering probabilities. */
+static const double xlo = 0.0;
+static const double xhi = 2*PSUP_RADIUS;
+
+/* Array of lengths used in the lookup tables for the average absorption and
+ * scattering probabilities. */
+static double fabs_x[N];
+
+/* Average fraction of light which is not absorbed by D2O as a function of
+ * length weighted by the Cerenkov spectrum and the quantum efficiency.
+ *
+ * We calculate the fraction not absorbed instead of absorbed */
+static double fabs_d2o[N];
+static double fabs_h2o[N];
+static double fabs_acrylic[N];
+static double fsct_d2o[N];
+static double fsct_h2o[N];
/* From Table 4 in the paper. */
static double A0 = 0.243905091;
@@ -88,59 +100,329 @@ static double RIND_D2O_C3 = 0.32;
* From http://pdg.lbl.gov/2018/reviews/rpp2018-rev-phys-constants.pdf. */
static double HC = 1.239841973976e3;
+double get_index(double p, double wavelength, double T)
+{
+ /* Returns the index of refraction of pure water for a given density,
+ * wavelength, and temperature. The density should be in units of g/cm^3,
+ * the wavelength in nm, and the temperature in Celsius.
+ *
+ * See "Refractive Index of Water and Steam as a function of Wavelength,
+ * Temperature, and Density" by Schiebener et al. 1990. */
+ /* normalize the temperature and pressure */
+ wavelength = wavelength/589.0;
+ T = (T+273.15)/273.15;
+
+ /* first we compute the right hand side of Equation 7 */
+ double c = A0 + A1*p + A2*T + A3*pow(wavelength,2)*T + A4/pow(wavelength,2) + A5/(pow(wavelength,2)-pow(UV,2)) + A6/(pow(wavelength,2)-pow(IR,2)) + A7*pow(p,2);
+ c *= p;
+
+ return sqrt((2*c+1)/(1-c));
+}
+
+double get_index_snoman_h2o(double wavelength)
+{
+ /* Returns the index of refraction of light water that SNOMAN uses for a
+ * given wavelength. The wavelength should be in units of nm.
+ *
+ * The coefficients come from media.dat in SNOMAN version 5.0294 and the
+ * formula used to compute the index of refraction comes from the SNOMAN
+ * companion page for titles MEDA. */
+ double E;
+
+ /* Calculate the energy of the photon in eV. */
+ E = HC/wavelength;
+
+ return RIND_H2O_C1 + RIND_H2O_C2*exp(RIND_H2O_C3*E);
+}
+
+double get_index_snoman_d2o(double wavelength)
+{
+ /* Returns the index of refraction of heavy water that SNOMAN uses for a
+ * given wavelength. The wavelength should be in units of nm.
+ *
+ * The coefficients come from media.dat in SNOMAN version 5.0294 and the
+ * formula used to compute the index of refraction comes from the SNOMAN
+ * companion page for titles MEDA. */
+ double E;
+
+ /* Calculate the energy of the photon in eV. */
+ E = HC/wavelength;
+
+ return RIND_D2O_C1 + RIND_D2O_C2*exp(RIND_D2O_C3*E);
+}
+
+/* Returns the average probability that a photon is not absorbed after a
+ * distance `x` in cm in D2O.
+ *
+ * This average probability is found by averaging
+ *
+ * e^(-x/l(wavelength))
+ *
+ * over the wavelength range 200-800 nm weighted by the Cerenkov wavelenght
+ * distribution and the quantum efficiency. */
+double get_fabs_d2o(double x)
+{
+ if (!initialized) {
+ fprintf(stderr, "weighted absorption length hasn't been initialized!\n");
+ exit(1);
+ }
+
+ return interp1d(x,fabs_x,fabs_d2o,LEN(fabs_x));
+}
+
+/* Returns the average probability that a photon is not absorbed after a
+ * distance `x` in cm in H2O. See get_fabs_d2o() for how this is calculated. */
+double get_fabs_h2o(double x)
+{
+ if (!initialized) {
+ fprintf(stderr, "average probability hasn't been initialized!\n");
+ exit(1);
+ }
+
+ return interp1d(x,fabs_x,fabs_h2o,LEN(fabs_x));
+}
+
+/* Returns the average probability that a photon is not absorbed after a
+ * distance `x` in cm in the SNO acrylic. See get_fabs_d2o() for how this is
+ * calculated.
+ *
+ * FIXME: Should include a term for the "dark" acrylic. */
+double get_fabs_acrylic(double x)
+{
+ if (!initialized) {
+ fprintf(stderr, "weighted absorption length hasn't been initialized!\n");
+ exit(1);
+ }
+
+ return interp1d(x,fabs_x,fabs_acrylic,LEN(fabs_x));
+}
+
+/* Returns the average probability that a photon is not scattered after a
+ * distance `x` in cm in D2O. See get_fabs_d2o() for how this is calculated. */
+double get_fsct_d2o(double x)
+{
+ if (!initialized) {
+ fprintf(stderr, "weighted rayleigh scattering length hasn't been initialized!\n");
+ exit(1);
+ }
+
+ return interp1d(x,fabs_x,fsct_d2o,LEN(fabs_x));
+}
+
+/* Returns the average probability that a photon is not scattered after a
+ * distance `x` in cm in H2O. See get_fabs_d2o() for how this is calculated. */
+double get_fsct_h2o(double x)
+{
+ if (!initialized) {
+ fprintf(stderr, "weighted rayleigh scattering length hasn't been initialized!\n");
+ exit(1);
+ }
+
+ return interp1d(x,fabs_x,fsct_h2o,LEN(fabs_x));
+}
+
+/* Returns the absorption length as a function of wavelength in D2O.
+ *
+ * The wavelength should be in nm and the absorption length is in cm. */
+double get_absorption_length_snoman_d2o(double wavelength)
+{
+ /* Note: We use GSL splines here instead of interp1d() since we aren't
+ * guaranteed that the values in the lookup table are evenly spaced. */
+ static gsl_spline *spline;
+ static gsl_interp_accel *acc;
+
+ if (!spline) {
+ spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_d2o_wavelengths));
+ gsl_spline_init(spline, absorption_coefficient_d2o_wavelengths, absorption_coefficient_d2o, LEN(absorption_coefficient_d2o_wavelengths));
+ acc = gsl_interp_accel_alloc();
+ }
+
+ /* If the wavelength is outside the range of the lookup table we just
+ * return the value at the beginning or end. We do this because by default
+ * gsl_spline_eval will exit if the wavelength is out of bounds. */
+ if (wavelength < absorption_coefficient_d2o_wavelengths[0])
+ wavelength = absorption_coefficient_d2o_wavelengths[0];
+
+ if (wavelength > absorption_coefficient_d2o_wavelengths[LEN(absorption_coefficient_d2o_wavelengths)-1])
+ wavelength = absorption_coefficient_d2o_wavelengths[LEN(absorption_coefficient_d2o_wavelengths)-1];
+
+ return 1.0/gsl_spline_eval(spline, wavelength, acc);
+}
+
+/* Returns the absorption length as a function of wavelength in H2O.
+ *
+ * The wavelength should be in nm and the absorption length is in cm. */
+double get_absorption_length_snoman_h2o(double wavelength)
+{
+ /* Note: We use GSL splines here instead of interp1d() since we aren't
+ * guaranteed that the values in the lookup table are evenly spaced. */
+ static gsl_spline *spline;
+ static gsl_interp_accel *acc;
+
+ if (!spline) {
+ spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_h2o_wavelengths));
+ gsl_spline_init(spline, absorption_coefficient_h2o_wavelengths, absorption_coefficient_h2o, LEN(absorption_coefficient_h2o_wavelengths));
+ acc = gsl_interp_accel_alloc();
+ }
+
+ /* If the wavelength is outside the range of the lookup table we just
+ * return the value at the beginning or end. We do this because by default
+ * gsl_spline_eval will exit if the wavelength is out of bounds. */
+ if (wavelength < absorption_coefficient_h2o_wavelengths[0])
+ wavelength = absorption_coefficient_h2o_wavelengths[0];
+
+ if (wavelength > absorption_coefficient_h2o_wavelengths[LEN(absorption_coefficient_h2o_wavelengths)-1])
+ wavelength = absorption_coefficient_h2o_wavelengths[LEN(absorption_coefficient_h2o_wavelengths)-1];
+
+ return 1.0/gsl_spline_eval(spline, wavelength, acc);
+}
+
+/* Returns the absorption length as a function of wavelength in acrylic.
+ *
+ * The wavelength should be in nm and the absorption length is in cm. */
+double get_absorption_length_snoman_acrylic(double wavelength)
+{
+ /* Note: We use GSL splines here instead of interp1d() since we aren't
+ * guaranteed that the values in the lookup table are evenly spaced. */
+ static gsl_spline *spline;
+ static gsl_interp_accel *acc;
+
+ if (!spline) {
+ spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_acrylic_wavelengths));
+ gsl_spline_init(spline, absorption_coefficient_acrylic_wavelengths, absorption_coefficient_acrylic, LEN(absorption_coefficient_acrylic_wavelengths));
+ acc = gsl_interp_accel_alloc();
+ }
+
+ /* If the wavelength is outside the range of the lookup table we just
+ * return the value at the beginning or end. We do this because by default
+ * gsl_spline_eval will exit if the wavelength is out of bounds. */
+ if (wavelength < absorption_coefficient_acrylic_wavelengths[0])
+ wavelength = absorption_coefficient_acrylic_wavelengths[0];
+
+ if (wavelength > absorption_coefficient_acrylic_wavelengths[LEN(absorption_coefficient_acrylic_wavelengths)-1])
+ wavelength = absorption_coefficient_acrylic_wavelengths[LEN(absorption_coefficient_acrylic_wavelengths)-1];
+
+ return 1.0/gsl_spline_eval(spline, wavelength, acc);
+}
+
+/* Returns the Rayleigh scattering length at a particular wavelength in D2O.
+ *
+ * The wavelength should be in nm and the returned scattering length is in cm. */
+double get_rayleigh_scattering_length_snoman_d2o(double wavelength)
+{
+ /* Note: We use GSL splines here instead of interp1d() since we aren't
+ * guaranteed that the values in the lookup table are evenly spaced. */
+ static gsl_spline *spline;
+ static gsl_interp_accel *acc;
+
+ if (!spline) {
+ spline = gsl_spline_alloc(gsl_interp_linear, LEN(rayleigh_coefficient_wavelengths));
+ gsl_spline_init(spline, rayleigh_coefficient_wavelengths, rayleigh_coefficient_d2o, LEN(rayleigh_coefficient_wavelengths));
+ acc = gsl_interp_accel_alloc();
+ }
+
+ /* If the wavelength is outside the range of the lookup table we just
+ * return the value at the beginning or end. We do this because by default
+ * gsl_spline_eval will exit if the wavelength is out of bounds. */
+ if (wavelength < rayleigh_coefficient_wavelengths[0])
+ wavelength = rayleigh_coefficient_wavelengths[0];
+
+ if (wavelength > rayleigh_coefficient_wavelengths[LEN(rayleigh_coefficient_wavelengths)-1])
+ wavelength = rayleigh_coefficient_wavelengths[LEN(rayleigh_coefficient_wavelengths)-1];
+
+ return 1.0/gsl_spline_eval(spline, wavelength, acc);
+}
+
+/* Returns the Rayleigh scattering length at a particular wavelength in H2O.
+ *
+ * The wavelength should be in nm and the returned scattering length is in cm. */
+double get_rayleigh_scattering_length_snoman_h2o(double wavelength)
+{
+ /* Returns the Rayleigh scattering length in H2O in cm. */
+ static gsl_spline *spline;
+ static gsl_interp_accel *acc;
+
+ if (!spline) {
+ spline = gsl_spline_alloc(gsl_interp_linear, LEN(rayleigh_coefficient_wavelengths));
+ gsl_spline_init(spline, rayleigh_coefficient_wavelengths, rayleigh_coefficient_h2o, LEN(rayleigh_coefficient_wavelengths));
+ acc = gsl_interp_accel_alloc();
+ }
+
+ /* If the wavelength is outside the range of the lookup table we just
+ * return the value at the beginning or end. We do this because by default
+ * gsl_spline_eval will exit if the wavelength is out of bounds. */
+ if (wavelength < rayleigh_coefficient_wavelengths[0])
+ wavelength = rayleigh_coefficient_wavelengths[0];
+
+ if (wavelength > rayleigh_coefficient_wavelengths[LEN(rayleigh_coefficient_wavelengths)-1])
+ wavelength = rayleigh_coefficient_wavelengths[LEN(rayleigh_coefficient_wavelengths)-1];
+
+ return 1.0/gsl_spline_eval(spline, wavelength, acc);
+}
+
static double gsl_rayleigh_scattering_length_snoman_d2o(double wavelength, void *params)
{
/* Returns the Rayleigh scattering length in D2O weighted by the quantum efficiency
* and the Cerenkov spectrum. */
- double qe;
+ double qe, x;
+
+ x = *((double *) params);
qe = get_quantum_efficiency(wavelength);
- return qe*get_rayleigh_scattering_length_snoman_d2o(wavelength)/pow(wavelength,2);
+ return qe*exp(-x/get_rayleigh_scattering_length_snoman_d2o(wavelength))/pow(wavelength,2);
}
static double gsl_rayleigh_scattering_length_snoman_h2o(double wavelength, void *params)
{
/* Returns the Rayleigh scattering length in H2O weighted by the quantum efficiency
* and the Cerenkov spectrum. */
- double qe;
+ double qe, x;
+
+ x = *((double *) params);
qe = get_quantum_efficiency(wavelength);
- return qe*get_rayleigh_scattering_length_snoman_h2o(wavelength)/pow(wavelength,2);
+ return qe*exp(-x/get_rayleigh_scattering_length_snoman_h2o(wavelength))/pow(wavelength,2);
}
static double gsl_absorption_length_snoman_d2o(double wavelength, void *params)
{
/* Returns the absorption length in D2O weighted by the quantum efficiency
* and the Cerenkov spectrum. */
- double qe;
+ double qe, x;
+
+ x = *((double *) params);
qe = get_quantum_efficiency(wavelength);
- return qe*get_absorption_length_snoman_d2o(wavelength)/pow(wavelength,2);
+ return qe*exp(-x/get_absorption_length_snoman_d2o(wavelength))/pow(wavelength,2);
}
static double gsl_absorption_length_snoman_h2o(double wavelength, void *params)
{
/* Returns the absorption length in H2O weighted by the quantum efficiency
* and the Cerenkov spectrum. */
- double qe;
+ double qe, x;
+
+ x = *((double *) params);
qe = get_quantum_efficiency(wavelength);
- return qe*get_absorption_length_snoman_h2o(wavelength)/pow(wavelength,2);
+ return qe*exp(-x/get_absorption_length_snoman_h2o(wavelength))/pow(wavelength,2);
}
static double gsl_absorption_length_snoman_acrylic(double wavelength, void *params)
{
/* Returns the absorption length in acrylic weighted by the quantum
* efficiency and the Cerenkov spectrum. */
- double qe;
+ double qe, x;
+
+ x = *((double *) params);
qe = get_quantum_efficiency(wavelength);
- return qe*get_absorption_length_snoman_acrylic(wavelength)/pow(wavelength,2);
+ return qe*exp(-x/get_absorption_length_snoman_acrylic(wavelength))/pow(wavelength,2);
}
static double gsl_cerenkov(double wavelength, void *params)
@@ -172,41 +454,57 @@ int optics_init(dict *db)
status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &norm, &error, &nevals);
if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ for (i = 0; i < LEN(fabs_x); i++) {
+ fabs_x[i] = xlo + (xhi-xlo)*i/(LEN(fabs_x)-1);
}
F.function = &gsl_absorption_length_snoman_d2o;
- status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
+ for (i = 0; i < LEN(fabs_x); i++) {
+ F.params = fabs_x+i;
- absorption_length_d2o_weighted = result/norm;
+ status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
- if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ if (status) {
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ fabs_d2o[i] = result/norm;
}
F.function = &gsl_absorption_length_snoman_h2o;
- status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
+ for (i = 0; i < LEN(fabs_x); i++) {
+ F.params = fabs_x+i;
- absorption_length_h2o_weighted = result/norm;
+ status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
- if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ if (status) {
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ fabs_h2o[i] = result/norm;
}
F.function = &gsl_absorption_length_snoman_acrylic;
- status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
+ for (i = 0; i < LEN(fabs_x); i++) {
+ F.params = fabs_x+i;
- absorption_length_acrylic_weighted = result/norm;
+ status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
- if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ if (status) {
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ fabs_acrylic[i] = result/norm;
}
rspr = get_bank(db, "RSPR", 1);
@@ -224,24 +522,32 @@ int optics_init(dict *db)
F.function = &gsl_rayleigh_scattering_length_snoman_d2o;
- status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
+ for (i = 0; i < LEN(fabs_x); i++) {
+ F.params = fabs_x+i;
- rayleigh_scattering_length_d2o_weighted = result/norm;
+ status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
- if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ if (status) {
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ fsct_d2o[i] = result/norm;
}
F.function = &gsl_rayleigh_scattering_length_snoman_h2o;
- status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
+ for (i = 0; i < LEN(fabs_x); i++) {
+ F.params = fabs_x+i;
- rayleigh_scattering_length_h2o_weighted = result/norm;
+ status = gsl_integration_cquad(&F, 200, 800, 0, 1e-2, w, &result, &error, &nevals);
- if (status) {
- fprintf(stderr, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
- exit(1);
+ if (status) {
+ sprintf(optics_err, "error integrating cerenkov distribution: %s\n", gsl_strerror(status));
+ return -1;
+ }
+
+ fsct_h2o[i] = result/norm;
}
gsl_integration_cquad_workspace_free(w);
@@ -250,189 +556,3 @@ int optics_init(dict *db)
return 0;
}
-
-double get_weighted_absorption_length_snoman_h2o(void)
-{
- /* Returns the weighted absorption length in H2O in cm. */
-
- if (!initialized) {
- fprintf(stderr, "weighted absorption length hasn't been initialized!\n");
- exit(1);
- }
-
- return absorption_length_h2o_weighted;
-}
-
-double get_rayleigh_scattering_length_snoman_d2o(double wavelength)
-{
- /* Returns the Rayleigh scattering length in D2O in cm. */
- return 1.0/interp1d(wavelength,rayleigh_coefficient_wavelengths, rayleigh_coefficient_d2o,LEN(rayleigh_coefficient_wavelengths));
-}
-
-double get_rayleigh_scattering_length_snoman_h2o(double wavelength)
-{
- /* Returns the Rayleigh scattering length in H2O in cm. */
- return 1.0/interp1d(wavelength,rayleigh_coefficient_wavelengths, rayleigh_coefficient_h2o,LEN(rayleigh_coefficient_wavelengths));
-}
-
-double get_weighted_rayleigh_scattering_length_snoman_d2o(void)
-{
- /* Returns the weighted Rayleigh scattering length in D2O in cm. */
-
- if (!initialized) {
- fprintf(stderr, "weighted rayleigh scattering length hasn't been initialized!\n");
- exit(1);
- }
-
- return rayleigh_scattering_length_d2o_weighted;
-}
-
-double get_weighted_rayleigh_scattering_length_snoman_h2o(void)
-{
- /* Returns the weighted Rayleigh scattering length in H2O in cm. */
-
- if (!initialized) {
- fprintf(stderr, "weighted rayleigh scattering length hasn't been initialized!\n");
- exit(1);
- }
-
- return rayleigh_scattering_length_h2o_weighted;
-}
-
-double get_absorption_length_snoman_h2o(double wavelength)
-{
- /* Returns the absorption length in H2O in cm. */
- static gsl_spline *spline;
- static gsl_interp_accel *acc;
-
- if (!spline) {
- spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_h2o_wavelengths));
- gsl_spline_init(spline, absorption_coefficient_h2o_wavelengths, absorption_coefficient_h2o, LEN(absorption_coefficient_h2o_wavelengths));
- acc = gsl_interp_accel_alloc();
- }
-
- if (wavelength < absorption_coefficient_h2o_wavelengths[0])
- wavelength = absorption_coefficient_h2o_wavelengths[0];
-
- if (wavelength > absorption_coefficient_h2o_wavelengths[LEN(absorption_coefficient_h2o_wavelengths)-1])
- wavelength = absorption_coefficient_h2o_wavelengths[LEN(absorption_coefficient_h2o_wavelengths)-1];
-
- return 1.0/gsl_spline_eval(spline, wavelength, acc);
-}
-
-double get_weighted_absorption_length_snoman_d2o(void)
-{
- /* Returns the weighted absorption length in D2O in cm. */
-
- if (!initialized) {
- fprintf(stderr, "weighted absorption length hasn't been initialized!\n");
- exit(1);
- }
-
- return absorption_length_d2o_weighted;
-}
-
-double get_absorption_length_snoman_d2o(double wavelength)
-{
- /* Returns the absorption length in D2O in cm. */
- static gsl_spline *spline;
- static gsl_interp_accel *acc;
-
- if (!spline) {
- spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_d2o_wavelengths));
- gsl_spline_init(spline, absorption_coefficient_d2o_wavelengths, absorption_coefficient_d2o, LEN(absorption_coefficient_d2o_wavelengths));
- acc = gsl_interp_accel_alloc();
- }
-
- if (wavelength < absorption_coefficient_d2o_wavelengths[0])
- wavelength = absorption_coefficient_d2o_wavelengths[0];
-
- if (wavelength > absorption_coefficient_d2o_wavelengths[LEN(absorption_coefficient_d2o_wavelengths)-1])
- wavelength = absorption_coefficient_d2o_wavelengths[LEN(absorption_coefficient_d2o_wavelengths)-1];
-
- return 1.0/gsl_spline_eval(spline, wavelength, acc);
-}
-
-double get_weighted_absorption_length_snoman_acrylic(void)
-{
- /* Returns the weighted absorption length in acrylic in cm. */
-
- if (!initialized) {
- fprintf(stderr, "weighted absorption length hasn't been initialized!\n");
- exit(1);
- }
-
- return absorption_length_acrylic_weighted;
-}
-
-double get_absorption_length_snoman_acrylic(double wavelength)
-{
- /* Returns the absorption length in acrylic in cm. */
- static gsl_spline *spline;
- static gsl_interp_accel *acc;
-
- if (!spline) {
- spline = gsl_spline_alloc(gsl_interp_linear, LEN(absorption_coefficient_acrylic_wavelengths));
- gsl_spline_init(spline, absorption_coefficient_acrylic_wavelengths, absorption_coefficient_acrylic, LEN(absorption_coefficient_acrylic_wavelengths));
- acc = gsl_interp_accel_alloc();
- }
-
- if (wavelength < absorption_coefficient_acrylic_wavelengths[0])
- wavelength = absorption_coefficient_acrylic_wavelengths[0];
-
- if (wavelength > absorption_coefficient_acrylic_wavelengths[LEN(absorption_coefficient_acrylic_wavelengths)-1])
- wavelength = absorption_coefficient_acrylic_wavelengths[LEN(absorption_coefficient_acrylic_wavelengths)-1];
-
- return 1.0/gsl_spline_eval(spline, wavelength, acc);
-}
-
-double get_index(double p, double wavelength, double T)
-{
- /* Returns the index of refraction of pure water for a given density,
- * wavelength, and temperature. The density should be in units of g/cm^3,
- * the wavelength in nm, and the temperature in Celsius.
- *
- * See "Refractive Index of Water and Steam as a function of Wavelength,
- * Temperature, and Density" by Schiebener et al. 1990. */
- /* normalize the temperature and pressure */
- wavelength = wavelength/589.0;
- T = (T+273.15)/273.15;
-
- /* first we compute the right hand side of Equation 7 */
- double c = A0 + A1*p + A2*T + A3*pow(wavelength,2)*T + A4/pow(wavelength,2) + A5/(pow(wavelength,2)-pow(UV,2)) + A6/(pow(wavelength,2)-pow(IR,2)) + A7*pow(p,2);
- c *= p;
-
- return sqrt((2*c+1)/(1-c));
-}
-
-double get_index_snoman_h2o(double wavelength)
-{
- /* Returns the index of refraction of light water that SNOMAN uses for a
- * given wavelength. The wavelength should be in units of nm.
- *
- * The coefficients come from media.dat in SNOMAN version 5.0294 and the
- * formula used to compute the index of refraction comes from the SNOMAN
- * companion page for titles MEDA. */
- double E;
-
- /* Calculate the energy of the photon in eV. */
- E = HC/wavelength;
-
- return RIND_H2O_C1 + RIND_H2O_C2*exp(RIND_H2O_C3*E);
-}
-
-double get_index_snoman_d2o(double wavelength)
-{
- /* Returns the index of refraction of heavy water that SNOMAN uses for a
- * given wavelength. The wavelength should be in units of nm.
- *
- * The coefficients come from media.dat in SNOMAN version 5.0294 and the
- * formula used to compute the index of refraction comes from the SNOMAN
- * companion page for titles MEDA. */
- double E;
-
- /* Calculate the energy of the photon in eV. */
- E = HC/wavelength;
-
- return RIND_D2O_C1 + RIND_D2O_C2*exp(RIND_D2O_C3*E);
-}
diff --git a/src/optics.h b/src/optics.h
index 19db2b9..70ff53b 100644
--- a/src/optics.h
+++ b/src/optics.h
@@ -19,21 +19,32 @@
#include "dict.h"
+/* Global error string when optics_init() returns -1. */
extern char optics_err[256];
+/* Initialize the optics data by reading in the RSPR bank and precomputing the
+ * average absorption and scattering tables. */
int optics_init(dict *db);
-double get_rayleigh_scattering_length_snoman_d2o(double wavelength);
-double get_rayleigh_scattering_length_snoman_h2o(double wavelength);
-double get_weighted_rayleigh_scattering_length_snoman_d2o(void);
-double get_weighted_rayleigh_scattering_length_snoman_h2o(void);
-double get_weighted_absorption_length_snoman_h2o(void);
-double get_absorption_length_snoman_h2o(double wavelength);
-double get_weighted_absorption_length_snoman_d2o(void);
-double get_absorption_length_snoman_d2o(double wavelength);
-double get_weighted_absorption_length_snoman_acrylic(void);
-double get_absorption_length_snoman_acrylic(double wavelength);
+
+/* Functions for computing the index of refraction. */
double get_index(double p, double wavelength, double T);
double get_index_snoman_h2o(double wavelength);
double get_index_snoman_d2o(double wavelength);
+/* Functions for computing the probability that a photon is not absorbed or
+ * scattered after a certain distance. */
+double get_fabs_d2o(double x);
+double get_fabs_h2o(double x);
+double get_fabs_acrylic(double x);
+double get_fsct_d2o(double x);
+double get_fsct_h2o(double x);
+
+/* Functions for computing the absorption and scattering length as a funcion of
+ * wavelength. */
+double get_absorption_length_snoman_d2o(double wavelength);
+double get_absorption_length_snoman_h2o(double wavelength);
+double get_absorption_length_snoman_acrylic(double wavelength);
+double get_rayleigh_scattering_length_snoman_d2o(double wavelength);
+double get_rayleigh_scattering_length_snoman_h2o(double wavelength);
+
#endif
diff --git a/src/sno.h b/src/sno.h
index 0a605c9..bf0a1eb 100644
--- a/src/sno.h
+++ b/src/sno.h
@@ -31,6 +31,7 @@
#define AV_RADIUS_INNER 600.50
#define AV_RADIUS_OUTER 606.00
#define AV_RADIUS 603.25
+#define AV_THICKNESS 6.5
/* FIXME: is this right? */
#define PSUP_RADIUS 800.0