diff options
| author | tlatorre <tlatorre@uchicago.edu> | 2019-11-06 11:28:27 -0600 | 
|---|---|---|
| committer | tlatorre <tlatorre@uchicago.edu> | 2019-11-06 11:28:27 -0600 | 
| commit | b78b16d32f4ed170f4a24d290348765da198d5f0 (patch) | |
| tree | 52206c009c858785655fea22cd7963433dcfad1c /src | |
| parent | 86f64fd828259760ce7003416b70769cf5ba200d (diff) | |
| download | sddm-b78b16d32f4ed170f4a24d290348765da198d5f0.tar.gz sddm-b78b16d32f4ed170f4a24d290348765da198d5f0.tar.bz2 sddm-b78b16d32f4ed170f4a24d290348765da198d5f0.zip | |
add a couple of improvements to the quad fitter and fix a bug in get_hough_transform()
This commit adds two improvements to the quad fitter:
1. I updated quad to weight the random PMT hit selection by the probability
that the PMT hit is a multiphoton hit. The idea here is that we really only
want to sample direct light and for high energy events the reflected and
scattered light is usually single photon.
2. I added an option to quad to only use points in the quad cloud which are
below a given quantile of t0. The idea here is that for particles like muons
which travel more than a few centimeters in the detector the quad cloud usually
looks like the whole track. Since we want the QUAD fitter to find the position
of the *start* of the track we select only those quad cloud points with an
early time so the position is closer to the position of the start of the track.
Also, I fixed a major bug in get_hough_transform() in which I was using the
wrong index variable when checking if a PMT was not flagged, a normal PMT, and
was hit. This was causing the algorithm to completely miss finding more than
one ring while I was testing it.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 4 | ||||
| -rw-r--r-- | src/find_peaks.c | 5 | ||||
| -rw-r--r-- | src/fit.c | 20 | ||||
| -rw-r--r-- | src/quad.c | 126 | ||||
| -rw-r--r-- | src/quad.h | 6 | ||||
| -rw-r--r-- | src/random.c | 40 | ||||
| -rw-r--r-- | src/random.h | 2 | ||||
| -rw-r--r-- | src/sno_charge.c | 20 | ||||
| -rw-r--r-- | src/sno_charge.h | 2 | ||||
| -rw-r--r-- | src/test-find-peaks.c | 66 | ||||
| -rw-r--r-- | src/test.c | 82 | 
11 files changed, 317 insertions, 56 deletions
| diff --git a/src/Makefile b/src/Makefile index 7787558..cb701cb 100644 --- a/src/Makefile +++ b/src/Makefile @@ -32,9 +32,9 @@ test-time-pdf: test-time-pdf.o zebra.o likelihood.o pmt.o vector.o misc.o muon.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 electron.o proton.o find_peaks.o quad.o dc.o sort.o util.o hdf5_utils.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 find_peaks.o quad.o dc.o sort.o util.o hdf5_utils.o random.o mt19937ar.o -test-find-peaks: test-find-peaks.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 find_peaks.o util.o quad.o +test-find-peaks: test-find-peaks.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 find_peaks.o util.o quad.o random.o mt19937ar.o  calculate-csda-range: calculate-csda-range.o diff --git a/src/find_peaks.c b/src/find_peaks.c index 7d350d5..6109896 100644 --- a/src/find_peaks.c +++ b/src/find_peaks.c @@ -197,8 +197,9 @@ void get_hough_transform(event *ev, double *pos, double *x, double *y, size_t n,      /* Subtract off previous rings. */      for (i = 0; i < len; i++) {          qhs_avg = get_qhs_avg(ev,pos,last+3*i); +          for (j = 0; j < MAX_PMTS; j++) { -            if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL || !ev->pmt_hits[i].hit) continue; +            if (ev->pmt_hits[j].flags || pmts[j].pmt_type != PMT_NORMAL || !ev->pmt_hits[j].hit) continue;              SUB(pmt_dir,pmts[j].pos,pos); @@ -304,7 +305,7 @@ void find_peaks(event *ev, double *pos, size_t n, size_t m, double *peak_theta,          /* Only add the latest peak to the results if it's more than `delta`           * radians away from all other results so far. */          unique = 1; -        for (j = 0; j < i; j++) { +        for (j = 0; j < *npeaks; j++) {              /* If the angle between the latest peak and a previous peak is               * within `delta` radians, it's not unique. */              if (acos(DOT(dir+i*3,dir+j*3)) < delta) unique = 0; @@ -5298,11 +5298,6 @@ int bisect_energy(double qhs_sum, double distance, int id, double cos_theta, dou      pars.qhs_sum = qhs_sum;      pars.cos_theta = cos_theta; -    s = gsl_root_fsolver_alloc(gsl_root_fsolver_brent); - -    F.function = &get_expected_photons_root; -    F.params = &pars; -      switch (id) {      case IDP_E_MINUS:      case IDP_E_PLUS: @@ -5326,6 +5321,17 @@ int bisect_energy(double qhs_sum, double distance, int id, double cos_theta, dou      /* Calculate the Cerenkov threshold for a muon. */      Tmin = mass/sqrt(1.0-1.0/(n_d2o*n_d2o)) - mass; +    if (get_expected_photons(id,Tmin,distance,cos_theta) > qhs_sum) +        return Tmin; + +    if (get_expected_photons(id,Tmax,distance,cos_theta) < qhs_sum) +        return Tmax; + +    s = gsl_root_fsolver_alloc(gsl_root_fsolver_brent); + +    F.function = &get_expected_photons_root; +    F.params = &pars; +      gsl_root_fsolver_set(s, &F, Tmin, Tmax);      do { @@ -5432,7 +5438,7 @@ int fit_event2(event *ev, double *xopt, double *fmin, int *id, size_t n, double      nlopt_set_min_objective(opt,nlopt_nll2,&fpars);      /* Guess the position and t0 of the event using the QUAD fitter. */ -    status = quad(ev,pos,&t0,10000); +    status = quad(ev,pos,&t0,10000,0.1);      if (status || t0 > GTVALID || NORM(pos) > PSUP_RADIUS) {          /* If the QUAD fitter fails or returns something outside the PSUP or @@ -5474,7 +5480,7 @@ int fit_event2(event *ev, double *xopt, double *fmin, int *id, size_t n, double      ub[3] = GTVALID;      /* Find the peaks in the Hough transform of the event. */ -    find_peaks(ev, pos, 100, 100, peak_theta, peak_phi, &npeaks, LEN(peak_theta),0.1); +    find_peaks(ev, pos, 100, 100, peak_theta, peak_phi, &npeaks, LEN(peak_theta), 0.1);      /* Don't fit more than 3 peaks for now. */      if (npeaks > 3) npeaks = 3; @@ -31,6 +31,10 @@  #include <gsl/gsl_cblas.h>  #include "misc.h"  #include <gsl/gsl_errno.h> +#include "sno_charge.h" +#include "sno.h" +#include <gsl/gsl_permute.h> +#include "random.h"  char quad_err[256]; @@ -54,6 +58,11 @@ void my_handler(const char *reason, const char *file, int line, int gsl_errno)   * density of points, but since we are dealing with mostly high nhit events we   * just take the median because it is easier and quicker.   * + * Update: This version of quad now also weights the PMT hits it selects by the + * probability that they are more than 1 PE. The idea here is that we'd like to + * ignore scattered and reflected light when selecting these points and most + * scattered and reflected light is single photons. + *   * `ev` should be a pointer to the event.   *   * `pos` is a pointer to a double array of length 3. @@ -64,6 +73,15 @@ void my_handler(const char *reason, const char *file, int line, int gsl_errno)   * try to compute this many points, but it will stop trying after 10*npoints   * times to avoid an infinite loop.   * + * `f` is the quantile of t0 to cut on. It should be between 0 and 1. The + * reason to cut on a quantile of t0 is that for particles with tracks much + * longer than a centimeter, the quad cloud of points generally follows the + * whole track. Since we are interested in finding the position and time of the + * start of the track, we'd like to only select the quad points at the start of + * the track. To do this we only include quad cloud points in the first `f` + * quantile when computing the position and time. For the default quad behavior + * without cutting on t0, you should set `f` to 1.0. + *   * Returns 0 on success and `pos` and `t0` will be set to the approximate   * vertex position and time respectively. On error, returns -1 and sets the   * `quad_err` string. @@ -80,11 +98,10 @@ void my_handler(const char *reason, const char *file, int line, int gsl_errno)   * is Stephen Brice's PHD thesis which cites three SNO technical reports, but I   * haven't been able to find these. The first two of these reports are by Bill   * Frati who I assume wrote the initial implementation. */ -int quad(event *ev, double *pos, double *t0, size_t npoints) +int quad(event *ev, double *pos, double *t0, size_t npoints, double f)  {      size_t i, j, k;      static int index[MAX_PMTS]; -    static int index2[MAX_PMTS];      int nhit;      const gsl_rng_type *T;      gsl_rng *r; @@ -96,21 +113,20 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)      double pos1[3], pos2[3];      static double results[QUAD_MAX][4];      double c2; -    double wavelength0;      double n_d2o;      double t1, t2;      int s;      double pmt_dir[3];      double expected; -    static double t[MAX_PMTS]; -    static size_t p2[MAX_PMTS]; +    static double w[MAX_PMTS];      double tmin;      size_t max_tries;      gsl_error_handler_t *old_handler;      int status; +    size_t reorder[QUAD_MAX]; +    double pq = 0.0; -    wavelength0 = 400.0; -    n_d2o = get_index_snoman_d2o(wavelength0); +    n_d2o = get_avg_index_d2o();      c2 = SPEED_OF_LIGHT*SPEED_OF_LIGHT/pow(n_d2o,2); @@ -119,31 +135,54 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)          return 1;      } +    double expected_pe = 0.0;      nhit = 0;      for (i = 0; i < MAX_PMTS; i++) {          if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL) continue;          if (ev->pmt_hits[i].hit) { -            index[nhit] = i; -            t[nhit] = ev->pmt_hits[i].t; -            nhit++; +            expected_pe += ev->pmt_hits[i].q; +            nhit += 1;          }      } +    expected_pe /= nhit; -    /* Only use the first 50% of the hits to avoid counting late/reflected -     * light. */ -    k = nhit > 10 ? ceil(nhit/2.0) : nhit; -    gsl_sort_smallest_index(p2,k,t,1,nhit); +    nhit = 0; +    for (i = 0; i < MAX_PMTS; i++) { +        if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL) continue; -    /* Reorder the hits. */ -    for (i = 0; i < k; i++) { -        index2[i] = index[p2[i]]; -    } +        if (ev->pmt_hits[i].hit) { +            index[nhit] = i; -    nhit = k; +            /* Weights are equal to the probability that the hit is > 1 photon. +             * This is supposed to be a rough proxy for the probability that +             * it's not reflected and/or scattered light (which is typically +             * single photons). */ +            if (ev->pmt_hits[i].q > QUAD_MAX_PE*get_qmean()/2) { +                /* If the charge is greater than QUAD_MAX_PE, it's almost +                 * certainly multiple photons so we don't waste time calculating P(1 PE|q). */ +                w[nhit] = 1.0; +            } else { +                /* We want to calculate P(multiple photons|q) which we calculate as: +                 * +                 *     P(multiple photons|q) = 1 - P(1 PE|q) = 1 - P(q|1 PE)P(1 PE)/P(q) +                 * +                 * To calculate the second two quantities we assume the number +                 * of PE is Poisson distributed with a mean equal to +                 * expected_photons. */ +                pq = 0.0; +                for (j = 1; j < QUAD_MAX_PE; j++) { +                    pq += get_pq(ev->pmt_hits[i].q,j)*gsl_ran_poisson_pdf(j,expected_pe); +                } + +                w[nhit] = 1-get_pq(ev->pmt_hits[i].q,1)*gsl_ran_poisson_pdf(1,expected_pe)/pq; +            } +            nhit++; +        } +    }      if (nhit < 5) { -        sprintf(quad_err, "only %i pmt hits. quad needs at least 5 points!", nhit); +        sprintf(quad_err, "only %i pmt hit(s). quad needs at least 5 points!", nhit);          return 1;      } @@ -157,7 +196,7 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)      nresults = 0;      while (nresults < npoints && i++ < max_tries) {          /* Choose 4 random hits. */ -        gsl_ran_choose(r,xs,4,index2,nhit,sizeof(int)); +        ran_choose_weighted(xs,w,4,index,nhit);          /* Shuffle them since GSL always returns the random choices in order.           * @@ -224,11 +263,13 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)          }          if (t1 < tmin && isclose(ev->pmt_hits[xs[0]].t,expected,0,1e-2)) { -            results[nresults][0] = pos1[0]; -            results[nresults][1] = pos1[1]; -            results[nresults][2] = pos1[2]; -            results[nresults][3] = t1; -            nresults++; +            if (NORM(pos1) < PSUP_RADIUS) { +                results[nresults][0] = pos1[0]; +                results[nresults][1] = pos1[1]; +                results[nresults][2] = pos1[2]; +                results[nresults][3] = t1; +                nresults++; +            }          } else {              /* Check the second solution. */              t2 = (-b - sqrt(b*b - 4*a*c))/(2*a); @@ -240,11 +281,13 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)              expected = t2 + NORM(pmt_dir)*n_d2o/SPEED_OF_LIGHT;              if (t2 < tmin && isclose(ev->pmt_hits[xs[0]].t,expected,0,1e-2)) { -                results[nresults][0] = pos2[0]; -                results[nresults][1] = pos2[1]; -                results[nresults][2] = pos2[2]; -                results[nresults][3] = t2; -                nresults++; +                if (NORM(pos2) < PSUP_RADIUS) { +                    results[nresults][0] = pos2[0]; +                    results[nresults][1] = pos2[1]; +                    results[nresults][2] = pos2[2]; +                    results[nresults][3] = t2; +                    nresults++; +                }              }          }      } @@ -254,6 +297,27 @@ int quad(event *ev, double *pos, double *t0, size_t npoints)          goto err;      } +    /* Compute the permutation required to sort the results by t0. */ +    gsl_sort_index(reorder,&results[0][3],4,nresults); + +    /* Sort the results by t0. */ +    gsl_permute(reorder,&results[0][0],4,nresults); +    gsl_permute(reorder,&results[0][1],4,nresults); +    gsl_permute(reorder,&results[0][2],4,nresults); +    gsl_permute(reorder,&results[0][3],4,nresults); + +    if (f > 0.0 && f < 1.0) { +        /* Now, we filter only the results with t0 less than the quantile given +         * by `f`. The idea here is that for high energy particles which travel +         * macroscopic distances in the detector we want to only sample the +         * quad points near the start of the track, i.e. the points with the +         * earliest times. */ +        nresults = (int) f*nresults; +    } + +    /* Sort the x, y, z, and t0 columns so we can calculate the median. Note: +     * The rows of the results array don't represent the quad cloud anymore +     * since x, y, z, and t0 are mixed up! */      gsl_sort(&results[0][0],4,nresults);      gsl_sort(&results[0][1],4,nresults);      gsl_sort(&results[0][2],4,nresults); @@ -19,12 +19,16 @@  #include "event.h"  #include <stdlib.h> /* for size_t */ +#include <gsl/gsl_rng.h>  /* Maximum number of quad cloud points. */  #define QUAD_MAX 10000 +/* Maximum number of PE to consider when calculating P(> 1 hit|q) */ +#define QUAD_MAX_PE 10 +  extern char quad_err[256]; -int quad(event *ev, double *pos, double *t0, size_t npoints); +int quad(event *ev, double *pos, double *t0, size_t npoints, double f);  #endif diff --git a/src/random.c b/src/random.c index 0112ebd..482e102 100644 --- a/src/random.c +++ b/src/random.c @@ -16,6 +16,10 @@  #include "random.h"  #include <math.h> +#include <gsl/gsl_rng.h> +#include <gsl/gsl_randist.h> + +static gsl_rng *r;  double randn(void)  { @@ -44,3 +48,39 @@ void rand_sphere(double *dir)      dir[1] = sin(theta)*sin(phi);      dir[2] = cos(theta);  } + +/* Randomly choose `k` elements from array `src` with `n` elements with + * probabilities proportional to `w`. The elements are sampled without + * replacement and placed into the `dest` array. */ +void ran_choose_weighted(int *dest, double *w, size_t k, int *src, size_t n) +{ +    int i, j; +    gsl_ran_discrete_t *g; +    size_t index, result; +    int duplicate; + +    if (!r) +        r = gsl_rng_alloc(gsl_rng_default); + +    g = gsl_ran_discrete_preproc(n, w); + +    i = 0; +    while (i < k) { +        index = gsl_ran_discrete(r, g); + +        result = src[index]; + +        duplicate = 0; +        for (j = 0; j < i; j++) { +            if (result == dest[j]) { +                duplicate = 1; +                break; +            } +        } + +        if (!duplicate) dest[i++] = result; +    } + +    gsl_ran_discrete_free(g); +} + diff --git a/src/random.h b/src/random.h index dac48f8..747cfeb 100644 --- a/src/random.h +++ b/src/random.h @@ -18,8 +18,10 @@  #define RANDOM_H  #include "mt19937ar.h" +#include <stdlib.h> /* for size_t */  double randn(void);  void rand_sphere(double *dir); +void ran_choose_weighted(int *dest, double *w, size_t k, int *src, size_t n);  #endif diff --git a/src/sno_charge.c b/src/sno_charge.c index f0d38ec..130df9f 100644 --- a/src/sno_charge.c +++ b/src/sno_charge.c @@ -116,6 +116,26 @@ double get_qhi(void)      return qhi;  } +double get_qmean(void) +{ +    if (!initialized) { +        fprintf(stderr, "charge interpolation hasn't been initialized!\n"); +        exit(1); +    } + +    return qmean; +} + +double get_qstd(void) +{ +    if (!initialized) { +        fprintf(stderr, "charge interpolation hasn't been initialized!\n"); +        exit(1); +    } + +    return qstd; +} +  static double nlopt_log_pq(unsigned int n, const double *x, double *grad, void *params)  {      int i; diff --git a/src/sno_charge.h b/src/sno_charge.h index 7c6513d..b54f844 100644 --- a/src/sno_charge.h +++ b/src/sno_charge.h @@ -19,6 +19,8 @@  double get_qlo(void);  double get_qhi(void); +double get_qmean(void); +double get_qstd(void);  double get_most_likely_mean_pe(double q);  void init_charge(void);  double get_log_pq(double q, int n); diff --git a/src/test-find-peaks.c b/src/test-find-peaks.c index 11fa4b4..2ccc90d 100644 --- a/src/test-find-peaks.c +++ b/src/test-find-peaks.c @@ -31,12 +31,16 @@  #include "quad.h"  #include "likelihood.h"  #include "optics.h" +#include "sno_charge.h" +#include "db.h" +#include "pmt_response.h" +#include "dqxx.h"  #define EV_RECORD      0x45562020  #define MCTK_RECORD    0x4d43544b  #define MCVX_RECORD    0x4d435658 -#define NUM_ANGLES 1000 +#define TEST_FIND_PEAKS_NUM_ANGLES 1000  void plot_hough_transform(double *x, double *y, double *z, size_t n, size_t m)  { @@ -101,6 +105,8 @@ void plot_find_peaks(event *ev, double *pos, double *peak_theta, double *peak_ph      fprintf(pipe, "plot '-' u 1:2:3:4 with circles palette fillstyle solid, '-' u 1:2 with lines lc rgb \"red\" lw 2\n");      for (i = 0; i < MAX_PMTS; i++) { +        if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL) continue; +          if (ev->pmt_hits[i].hit) {              r = NORM(pmts[i].pos);              theta = acos(pmts[i].pos[2]/r); @@ -131,9 +137,9 @@ void plot_find_peaks(event *ev, double *pos, double *peak_theta, double *peak_ph          /* Rotate the direction by the Cerenkov angle. */          rotate(tmp2,dir,tmp,acos(1/n_d2o)); -        for (j = 0; j < NUM_ANGLES; j++) { +        for (j = 0; j < TEST_FIND_PEAKS_NUM_ANGLES; j++) {              /* Rotate the new direction around the peak by 2 pi. */ -            rotate(tmp,tmp2,dir,j*2*M_PI/(NUM_ANGLES-1)); +            rotate(tmp,tmp2,dir,j*2*M_PI/(TEST_FIND_PEAKS_NUM_ANGLES-1));              /* Calculate the intersection of the ray with the sphere. */              if (!intersect_sphere(pos,tmp,PSUP_RADIUS,&l)) continue; @@ -197,6 +203,8 @@ int main(int argc, char **argv)      int plot = 0;      int status;      double t0; +    int last_run; +    char dqxx_file[256];      int32_t gtid = -1;      for (i = 1; i < argc; i++) { @@ -233,6 +241,8 @@ int main(int argc, char **argv)      load_pmt_info(); +    init_charge(); +      f = zebra_open(filename);      if (!f) { @@ -240,6 +250,30 @@ int main(int argc, char **argv)          return 1;      } +    dict *db = db_init(); + +    last_run = -1; + +    if (load_file(db, "pmt_response_qoca_d2o_20060216.dat", 0)) { +        fprintf(stderr, "failed to load pmt_response_qoca_d2o_20060216.dat: %s\n", db_err); +        goto err; +    } + +    if (load_file(db, "rsp_rayleigh.dat", 0)) { +        fprintf(stderr, "failed to load rsp_rayleigh.dat: %s\n", db_err); +        goto err; +    } + +    if (pmt_response_init(db)) { +        fprintf(stderr, "failed to initialize PMTR bank: %s\n", pmtr_err); +        goto err; +    } + +    if (optics_init()) { +        fprintf(stderr, "failed to initialize optics: %s\n", optics_err); +        goto err; +    } +      while (1) {          rv = zebra_read_next_logical_record(f); @@ -251,7 +285,12 @@ int main(int argc, char **argv)              break;          } -        rv = zebra_get_bank(f, &bmast, f->first_bank); +        if (f->mast_bank == -1) { +            fprintf(stderr, "no MAST bank in logical record! Skipping...\n"); +            continue; +        } + +        rv = zebra_get_bank(f, &bmast, f->mast_bank);          if (rv) {              fprintf(stderr, "error getting MAST bank: %s\n", zebra_err); @@ -288,6 +327,23 @@ int main(int argc, char **argv)          if (gtid > 0 && ev.gtid != gtid) continue; +        if (bev.run != last_run) { +            printf("loading DQXX file for run %010i\n", bev.run); + +            sprintf(dqxx_file, "DQXX_%010i.dat", bev.run); +            if (load_file(db, dqxx_file, 1)) { +                fprintf(stderr, "failed to load %s: %s\n", dqxx_file, db_err); +                goto err; +            } + +            if (dqxx_init(db, &ev)) { +                fprintf(stderr, "failed to initialize DQXX bank: %s\n", dqxx_err); +                goto err; +            } + +            last_run = bev.run; +        } +          rv = get_event(f,&ev,&b);          size_t n = 100; @@ -306,7 +362,7 @@ int main(int argc, char **argv)          }          /* Guess the position and t0 of the event using the QUAD fitter. */ -        status = quad(&ev,pos,&t0,10000); +        status = quad(&ev,pos,&t0,10000,0.1);          if (status || t0 < 0 || t0 > GTVALID || NORM(pos) > PSUP_RADIUS) {              /* If the QUAD fitter fails or returns something outside the PSUP or @@ -1421,7 +1421,6 @@ int test_quad(char *err)      double fit_pos[3];      double pmt_dir[3];      double fit_t0; -    double wavelength0;      double n_d2o;      init_genrand(0); @@ -1438,8 +1437,7 @@ int test_quad(char *err)          index[valid_pmts++] = i;      } -    wavelength0 = 400.0; -    n_d2o = get_index_snoman_d2o(wavelength0); +    n_d2o = get_avg_index_d2o();      for (i = 0; i < 100; i++) {          /* Generate a random position within a cube the size of the AV. @@ -1449,6 +1447,13 @@ int test_quad(char *err)          x0[0] = (genrand_real2()*2 - 1)*AV_RADIUS;          x0[1] = (genrand_real2()*2 - 1)*AV_RADIUS;          x0[2] = (genrand_real2()*2 - 1)*AV_RADIUS; + +        while (NORM(x0) >= PSUP_RADIUS) { +            x0[0] = (genrand_real2()*2 - 1)*AV_RADIUS; +            x0[1] = (genrand_real2()*2 - 1)*AV_RADIUS; +            x0[2] = (genrand_real2()*2 - 1)*AV_RADIUS; +        } +          t0 = genrand_real2()*GTVALID;          /* Zero out all PMTs. */ @@ -1468,7 +1473,7 @@ int test_quad(char *err)              ev.pmt_hits[hits[j]].t = t0 + NORM(pmt_dir)*n_d2o/SPEED_OF_LIGHT;          } -        if (quad(&ev, fit_pos, &fit_t0, 10000)) { +        if (quad(&ev, fit_pos, &fit_t0, 10000, 1.0)) {              sprintf(err, "%s", quad_err);              goto err;          } @@ -1526,7 +1531,6 @@ int test_quad_noise(char *err)      double fit_pos[3];      double pmt_dir[3];      double fit_t0; -    double wavelength0;      double n_d2o;      init_genrand(0); @@ -1543,8 +1547,7 @@ int test_quad_noise(char *err)          index[valid_pmts++] = i;      } -    wavelength0 = 400.0; -    n_d2o = get_index_snoman_d2o(wavelength0); +    n_d2o = get_avg_index_d2o();      for (i = 0; i < 100; i++) {          /* Generate a random position within a cube the size of the AV. @@ -1556,6 +1559,12 @@ int test_quad_noise(char *err)          x0[2] = (genrand_real2()*2 - 1)*AV_RADIUS;          t0 = genrand_real2()*GTVALID; +        while (NORM(x0) >= PSUP_RADIUS) { +            x0[0] = (genrand_real2()*2 - 1)*AV_RADIUS; +            x0[1] = (genrand_real2()*2 - 1)*AV_RADIUS; +            x0[2] = (genrand_real2()*2 - 1)*AV_RADIUS; +        } +          /* Zero out all PMTs. */          for (j = 0; j < LEN(ev.pmt_hits); j++) {              ev.pmt_hits[j].hit = 0; @@ -1573,7 +1582,7 @@ int test_quad_noise(char *err)              ev.pmt_hits[hits[j]].t = t0 + NORM(pmt_dir)*n_d2o/SPEED_OF_LIGHT + randn()*PMT_TTS;          } -        if (quad(&ev, fit_pos, &fit_t0, 10000)) { +        if (quad(&ev, fit_pos, &fit_t0, 10000, 1.0)) {              sprintf(err, "%s", quad_err);              goto err;          } @@ -2129,6 +2138,62 @@ err:      return 1;  } +/* Tests that the ran_choose_weighted() function returns elements proportional + * to the weights. */ +int test_ran_choose_weighted(char *err) +{ +    size_t i, j, k; + +    int choices[10] = {0,1,2,3,4,5,6,7,8,9}; +    int results[1000000]; +    double w[10] = {0.0,0.0,0.0,0.0,0.0,0.2,0.2,0.2,0.2,0.2}; +    double fraction[10]; +    double sum; + +    for (j = 0; j < 100; j++) { +        sum = 0.0; +        for (i = 0; i < LEN(choices); i++) { +            w[i] = genrand_real2(); +            sum += w[i]; +        } + +        for (i = 0; i < LEN(choices); i++) { +            w[i] /= sum; +        } + +        for (i = 0; i < LEN(results); i++) { +            ran_choose_weighted(results+i,w,1,choices,LEN(choices)); +        } + +        sum = 0.0; +        for (i = 0; i < LEN(choices); i++) { +            fraction[i] = 0.0; +            for (k = 0; k < LEN(results); k++) { +                if (results[k] == choices[i]) { +                    fraction[i] += 1.0; +                } +            } +            sum += fraction[i]; +        } + +        for (i = 0; i < LEN(choices); i++) { +            fraction[i] /= sum; +        } + +        for (i = 0; i < LEN(choices); i++) { +            if (!isclose(fraction[i],w[i],0,1e-2)) { +                sprintf(err, "ran_choose_weighted() returned %zu %.5g%% of the time but expected %.5g", i, fraction[i], w[i]); +                goto err; +            } +        } +    } + +    return 0; + +err: +    return 1; +} +  struct tests {      testFunction *test;      char *name; @@ -2182,6 +2247,7 @@ struct tests {      {test_electron_get_angular_pdf_norm, "test_electron_get_angular_pdf_norm"},      {test_fast_acos, "test_fast_acos"},      {test_get_most_likely_mean_pe, "test_get_most_likely_mean_pe"}, +    {test_ran_choose_weighted, "test_ran_choose_weighted"},  };  int main(int argc, char **argv) | 
