aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/dc.c371
-rw-r--r--src/dc.h12
-rw-r--r--src/fit.c7
-rw-r--r--src/zdab_utils.c8
4 files changed, 314 insertions, 84 deletions
diff --git a/src/dc.c b/src/dc.c
index 5056fbc..9f427a7 100644
--- a/src/dc.c
+++ b/src/dc.c
@@ -24,6 +24,98 @@
#include <gsl/gsl_sort.h>
#include "zdab_utils.h"
#include "zebra.h"
+#include <gsl/gsl_statistics_double.h>
+
+/* Returns 1 if the event is tagged as an incoming muon. The goal of this cut
+ * is to tag external muons *without* tagging muons which start within the PSUP
+ * and then exit. In order to achieve that we use the ECA time of the OWL
+ * tubes.
+ *
+ * Ideally we would have the full calibrated time of the OWL tubes, but there
+ * was never any PCA for the OWL tubes. I also discussed this with Josh and he
+ * said that the cables were never changed for the OWL tubes so we should at
+ * least be able to rely on them being consistent.
+ *
+ * Since the average PCA correction is of order +/- 5 ns and the round trip
+ * time for light from the edge of the PSUP to the cavity and back should be
+ * longer than 15 ns (4 meters), the ECA time should be enough to distinguish
+ * incoming vs outgoing muons.
+ *
+ * This function tags an event as an incoming muon if the following criteria
+ * are met:
+ *
+ * - calibrated nhit >= 150
+ * - at least 5 calibrated OWL hits
+ * - at least 1 OWL hit with a QHS >= 20.0
+ * - at least 2 OWL hits with ECA times earlier than the 10th percentile of the
+ * PSUP PMT times
+ *
+ * Idea: Should we also require at least one of the early tubes to have a high
+ * charge? */
+int is_muon(event *ev)
+{
+ size_t i;
+ double ept[MAX_PMTS];
+ size_t nhit, nhit_owl, nhit_owl_early;
+ double early_time, max_owl_qhs;
+
+ nhit = 0;
+ nhit_owl = 0;
+ for (i = 0; i < MAX_PMTS; i++) {
+ /* Require good calibrations.
+ *
+ * Note: Typically I would do this with the following check:
+ *
+ * ev->pmt_hits[i].pf & (KPF_NO_CAL | KPF_BAD_CAL)
+ *
+ * However, Javi mentioned in an email that for very early hits which
+ * are in the TAC curl region, the calibration processor will flag
+ * them, set the time to -9999.0, and set the KPF_BAD_CAL bit. However,
+ * in this case for some reason the ECA time still looks reasonable.
+ * Therefore, since we only use the ECA time here we instead just make
+ * sure that the ECA time is something reasonable. */
+ if (!ev->pmt_hits[i].hit || ev->pmt_hits[i].ept <= -100) continue;
+
+ switch (pmts[i].pmt_type) {
+ case PMT_OWL:
+ if (nhit_owl == 0) {
+ max_owl_qhs = ev->pmt_hits[i].qhs;
+ } else if (ev->pmt_hits[i].qhs > max_owl_qhs) {
+ max_owl_qhs = ev->pmt_hits[i].qhs;
+ }
+ nhit_owl += 1;
+ break;
+ case PMT_NORMAL:
+ if (!ev->pmt_hits[i].flags) ept[nhit++] = ev->pmt_hits[i].ept;
+ break;
+ }
+ }
+
+ /* Require at least 150 normal hits. */
+ if (nhit < MUON_MIN_NHIT) return 0;
+
+ /* Require at least 5 OWL hits. */
+ if (nhit_owl < MUON_MIN_OWL_NHIT) return 0;
+
+ /* Require at least 1 OWL tube to have a QHS >= 20.0. */
+ if (max_owl_qhs < MUON_OWL_QHS) return 0;
+
+ /* Sort the times. */
+ gsl_sort(ept,1,nhit);
+
+ /* Calculate the time to define early OWL hits. */
+ early_time = gsl_stats_quantile_from_sorted_data(ept,1,nhit,MUON_OWL_TIME_PERCENTILE);
+
+ nhit_owl_early = 0;
+ for (i = 0; i < MAX_PMTS; i++) {
+ /* Require good calibrations. */
+ if (!ev->pmt_hits[i].hit || ev->pmt_hits[i].ept <= -100) continue;
+
+ if (pmts[i].pmt_type == PMT_OWL && ev->pmt_hits[i].ept < early_time) nhit_owl_early += 1;
+ }
+
+ return nhit_owl_early >= 2;
+}
/* Returns 1 if the event fails the junk cut. The definition for the junk cut
* comes from the SNOMAN companion:
@@ -31,11 +123,14 @@
* This will remove orphans, ECA data and events containing the same PMT
* more than once.
*
+ * I also added a check that will flag the event if it contains more than
+ * MAX_PMTS PMT hits (which shouldn't be possible).
+ *
* Note: This function may return -1 if there is an error traversing the PMT
* banks. */
int junk_cut(zebraFile *f, zebraBank *bmast, zebraBank *bev)
{
- size_t i;
+ int i;
int ids[MAX_PMTS];
int nhit;
static int pmt_links[] = {KEV_PMT,KEV_OWL,KEV_LG,KEV_FECD,KEV_BUTT,KEV_NECK};
@@ -68,7 +163,14 @@ int junk_cut(zebraFile *f, zebraBank *bmast, zebraBank *bev)
while (1) {
unpack_pmt(b.data, &bpmt);
+
ids[nhit++] = bpmt.pin;
+
+ if (nhit >= MAX_PMTS) {
+ fprintf(stderr, "event with more than %i PMT hits\n", MAX_PMTS);
+ return 1;
+ }
+
if (!b.next) break;
rv = zebra_get_bank(f,&b,b.next);
@@ -240,14 +342,17 @@ int is_neck_event(event *ev)
double avg_ept_psup;
int nhit;
- /* First, we compute the average ECA time for all normal PMTs. */
+ /* First, we compute the average ECA time for all the PSUP PMTs with z <= 0. */
nhit = 0;
avg_ept_psup = 0.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 || ev->pmt_hits[i].ept <= -100) continue;
- if (ev->pmt_hits[i].hit) {
+ /* Require good calibrations. */
+ if (ev->pmt_hits[i].pf & (KPF_NO_CAL | KPF_BAD_CAL)) continue;
+
+ if (ev->pmt_hits[i].hit && pmts[i].pos[2] < 0) {
avg_ept_psup += ev->pmt_hits[i].ept;
nhit += 1;
}
@@ -263,7 +368,10 @@ int is_neck_event(event *ev)
n = 0;
high_charge = 0;
for (i = 0; i < MAX_PMTS; i++) {
- if (pmts[i].pmt_type == PMT_NECK && ev->pmt_hits[i].hit) {
+ /* Require good calibrations. */
+ if (!ev->pmt_hits[i].hit || ev->pmt_hits[i].ept <= -100) continue;
+
+ if (pmts[i].pmt_type == PMT_NECK) {
n += 1;
/* FIXME: The SNOMAN companion says "Early if defined by the neck
@@ -280,6 +388,81 @@ int is_neck_event(event *ev)
return 0;
}
+/* Returns 1 if the average time for the hits in the same card as
+ * `flasher_pmt_id` is at least 10 ns earlier than all the hits within 4
+ * meters of the potential flasher.
+ *
+ * This cut is designed to help flag flashers in the is_flasher() function for
+ * events in which the flashing channel may be missing from the event, so there
+ * is no high charge channel to flag it. */
+int is_slot_early(event *ev, int flasher_pmt_id)
+{
+ int i;
+ double t_slot, t_nearby;
+ int n_slot, n_nearby;
+ double flasher_pos[3];
+ double pmt_dir[3];
+ double distance;
+
+ COPY(flasher_pos,pmts[flasher_pmt_id].pos);
+
+ n_slot = 0;
+ n_nearby = 0;
+ t_slot = 0.0;
+ t_nearby = 0.0;
+ for (i = 0; i < MAX_PMTS; i++) {
+ if (!ev->pmt_hits[i].hit) continue;
+
+ /* Require good calibrations.
+ *
+ * Note: For some reason PMT hits in the flasher slot sometimes are
+ * marked as having bad calibrations and get a time of -9999. I still
+ * don't understand why this is, so for now we don't check the
+ * calibration bits and instead just check to see that the ECA time is
+ * reasonable.
+ *
+ * Here is what Javi said via email:
+ *
+ * If SNO calibrations are the same than SNO+ (and I'm pretty sure they
+ * are, at last for this matter) there is a hit-level time validation,
+ * i.e., if the TAC value is determined to be in the curl region of the
+ * time slope (high TAC = very early hits), they get flagged and a
+ * calibration is not provided (which is compatible with a flashing
+ * channel). If this is the case, you can fall back to raw TACs and QHS
+ * when calibration is not available. */
+ if (ev->pmt_hits[i].ept < -100) continue;
+
+ if (i/32 == flasher_pmt_id/32) {
+ /* This hit is in the same slot as the potential flasher. */
+ t_slot += ev->pmt_hits[i].ept;
+ n_slot += 1;
+ continue;
+ }
+
+ /* Calculate the distance from the current channel to the high charge
+ * channel. */
+ SUB(pmt_dir,pmts[i].pos,flasher_pos);
+ distance = NORM(pmt_dir);
+
+ if (distance < 400.0) {
+ t_nearby += ev->pmt_hits[i].ept;
+ n_nearby += 1;
+ continue;
+ }
+ }
+
+ /* If there are no PMTs within 4 meters of the slot, it's almost certainly
+ * a flasher. */
+ if (n_slot > 4 && n_nearby == 0) return 1;
+
+ if (n_slot == 0 || n_nearby == 0) return 0;
+
+ t_slot /= n_slot;
+ t_nearby /= n_nearby;
+
+ return t_slot < t_nearby + 10.0;
+}
+
/* Returns 1 if the event is a flasher and 0 if it isn't. The definition of
* a flasher event comes from
* http://detector.sno.laurentian.ca/~detector/private/snoop/doc/eventIDs.html,
@@ -317,18 +500,19 @@ int is_neck_event(event *ev)
int is_flasher(event *ev)
{
int hits_pc[1280] = {0};
- int qhs, qhl;
+ int qhs, qhl, qlx;
int qhl_pc[8] = {0};
int qhs_pc[8] = {0};
+ int qlx_pc[8] = {0};
double t_pc[8] = {0};
+ int channel_pc[8] = {0};
size_t crate, card, channel, id;
int i, j;
- size_t index[1280], index_qhs[8], index_qhl[8];
+ size_t index[1280], index_qhs[8], index_qhl[8], index_qlx[8];
double t;
int flasher_pc;
double flasher_pos[3];
double pmt_dir[3];
- int flasher = 0;
double distance;
int nhit, nhit_late, nhit_far;
@@ -358,12 +542,12 @@ int is_flasher(event *ev)
for (j = 1279; j >= 0; j--) {
id = index[j];
- /* If this paddle card doesn't have as many hits as the most hit paddle
- * card and we haven't found a flasher yet, then it's not a flasher. */
- if (hits_pc[id] < hits_pc[index[1279]]) return 0;
+ /* Make sure this paddle card has at least 4 hits. */
+ if (hits_pc[id] < 4) return 0;
+ nhit = 0;
for (i = 0; i < MAX_PMTS; i++) {
- if (!ev->pmt_hits[i].hit) continue;
+ if (!ev->pmt_hits[i].hit || pmts[i].pmt_type != PMT_NORMAL) continue;
crate = i/512;
card = (i % 512)/32;
@@ -376,101 +560,122 @@ int is_flasher(event *ev)
/* Get the uncalibrated QHS and QHL charges. */
qhs = ev->pmt_hits[i].qihs;
qhl = ev->pmt_hits[i].qihl;
+ qlx = ev->pmt_hits[i].qilx;
if (qhs < 300) {
/* QHS values below 300 are set to 4095. */
- qhs_pc[channel % 8] = 4095;
+ qhs_pc[nhit] = 4095;
} else {
- qhs_pc[channel % 8] = qhs;
+ qhs_pc[nhit] = qhs;
}
if (qhl < 300) {
/* QHL values below 300 are set to 4095. */
- qhl_pc[channel % 8] = 4095;
+ qhl_pc[nhit] = 4095;
} else {
- qhl_pc[channel % 8] = qhl;
+ qhl_pc[nhit] = qhl;
}
- t_pc[channel % 8] = ev->pmt_hits[i].t;
- }
+ if (qlx < 300) {
+ /* QLX values below 300 are set to 4095. */
+ qlx_pc[nhit] = 4095;
+ } else {
+ qlx_pc[nhit] = qlx;
+ }
- /* Sort the QHS and QHL values by size. */
- argsort(qhs_pc, LEN(qhs_pc), index_qhs);
- argsort(qhl_pc, LEN(qhl_pc), index_qhl);
-
- /* Check if this paddle card has QHS or QHL values > 4000, and the QHS
- * and QHL values for the other channels in the card are less than 700. */
- if (qhs_pc[index_qhs[7]] > 4000 || qhl_pc[index_qhl[7]] > 4000) {
- if (qhs_pc[index_qhs[6]] < 800 && qhl_pc[index_qhl[6]] < 800) {
- /* Flasher! */
- flasher = 1;
- break;
- }
+ t_pc[nhit] = ev->pmt_hits[i].t;
+
+ channel_pc[nhit] = channel;
+
+ nhit += 1;
}
- }
- /* If we didn't find a flasher, return 0. */
- if (!flasher) return 0;
-
- flasher_pc = index[j];
- crate = index[j]/64;
- card = (index[j] % 64)/4;
- if (qhs_pc[index_qhs[7]] > 4000) {
- channel = (index[j] % 4)*8 + index_qhs[7];
- t = t_pc[index_qhs[7]];
- } else {
- channel = (index[j] % 4)*8 + index_qhl[7];
- t = t_pc[index_qhl[7]];
- }
- COPY(flasher_pos,pmts[crate*512 + card*32 + channel].pos);
+ if (nhit < 2) {
+ fprintf(stderr, "only 2 hits on %zu/%zu/PC%zu but expected at least 4!\n",
+ id/64, (id % 64)/4, id % 4);
+ continue;
+ }
- /* Check that 70% of the regular PMTs in the event which are not in the
- * flasher slot fired more than 50 ns after the high charge channel and are
- * more than 12 meters away from the high charge channel. */
- nhit = 0;
- /* Number of PMTs which fired 50 ns after the high charge channel. */
- nhit_late = 0;
- /* Number of PMTs which are 12 meters or more away from the high charge
- * channel. */
- nhit_far = 0;
- for (i = 0; i < MAX_PMTS; i++) {
- /* Skip PMTs which weren't hit. */
- if (!ev->pmt_hits[i].hit) continue;
+ /* Sort the QHS and QHL values by size. */
+ argsort(qhs_pc, nhit, index_qhs);
+ argsort(qhl_pc, nhit, index_qhl);
+ argsort(qlx_pc, nhit, index_qlx);
- /* Only count regular PMTs without any flags. */
- if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL) continue;
+ flasher_pc = index[j];
+ crate = index[j]/64;
+ card = (index[j] % 64)/4;
+ channel = channel_pc[index_qhs[nhit-1]];
- crate = i/512;
- card = (i % 512)/32;
- channel = i % 32;
+ /* Check if this paddle card has QHS, QHL, or QLX values > 4000, and the QHS,
+ * QHL, and QLX values for the other channels in the card are less than 700.
+ *
+ * Alternatively, if there isn't an obvious charge outlier, it's still
+ * possible that the flasher PMT hit is missing from the event or that
+ * the QHS/QHL/QLX values were just outside of the threshold. In this
+ * case, we look to see if the TAC for the proposed slot is early with
+ * respect to neighboring PMTs. */
+ if ((qhs_pc[index_qhs[nhit-1]] < 4000 || qhs_pc[index_qhs[nhit-2]] > 800) &&
+ (qhl_pc[index_qhl[nhit-1]] < 4000 || qhl_pc[index_qhl[nhit-2]] > 800) &&
+ (qlx_pc[index_qlx[nhit-1]] < 4000 || qlx_pc[index_qlx[nhit-2]] > 800) &&
+ !is_slot_early(ev, crate*512 + card*32 + channel)) continue;
+
+ if (qhs_pc[index_qhs[nhit-1]] > 4000) {
+ channel = channel_pc[index_qhs[nhit-1]];
+ t = t_pc[index_qhs[nhit-1]];
+ } else if (qhl_pc[index_qhl[nhit-1]] > 4000) {
+ channel = channel_pc[index_qhl[nhit-1]];
+ t = t_pc[index_qhl[nhit-1]];
+ } else {
+ channel = channel_pc[index_qlx[nhit-1]];
+ t = t_pc[index_qlx[nhit-1]];
+ }
+ COPY(flasher_pos,pmts[crate*512 + card*32 + channel].pos);
+
+ /* Check that 70% of the regular PMTs in the event which are not in the
+ * flasher slot fired more than 50 ns after the high charge channel and are
+ * more than 12 meters away from the high charge channel. */
+ nhit = 0;
+ /* Number of PMTs which fired 50 ns after the high charge channel. */
+ nhit_late = 0;
+ /* Number of PMTs which are 12 meters or more away from the high charge
+ * channel. */
+ nhit_far = 0;
+ for (i = 0; i < MAX_PMTS; i++) {
+ /* Skip PMTs which weren't hit. */
+ if (!ev->pmt_hits[i].hit) continue;
- id = crate*64 + card*4 + channel/8;
+ /* Only count regular PMTs without any flags. */
+ if (ev->pmt_hits[i].flags || pmts[i].pmt_type != PMT_NORMAL) continue;
- /* Skip PMTs in the same paddle card as the high charge channel. */
- if (id == flasher_pc) continue;
+ crate = i/512;
+ card = (i % 512)/32;
+ channel = i % 32;
- nhit += 1;
+ id = crate*64 + card*4 + channel/8;
- /* Calculate the distance from the current channel to the high charge
- * channel. */
- SUB(pmt_dir,pmts[i].pos,flasher_pos);
- distance = NORM(pmt_dir);
+ /* Skip PMTs in the same card as the high charge channel. */
+ if (id/4 == flasher_pc/4) continue;
- /* If this channel is 12 meters or more away, increment nhit_far. */
- if (distance >= 1200.0) nhit_far += 1;
+ nhit += 1;
- /* If this channel fired more than 50 ns after the high charge channel,
- * increment nhit_late. */
- if (ev->pmt_hits[i].t > t + 50.0) nhit_late += 1;
- }
+ /* Calculate the distance from the current channel to the high charge
+ * channel. */
+ SUB(pmt_dir,pmts[i].pos,flasher_pos);
+ distance = NORM(pmt_dir);
+
+ /* If this channel is 12 meters or more away, increment nhit_far. */
+ if (distance >= 1200.0) nhit_far += 1;
- /* If less than 70% of the regular PMTs fired within 50 ns of the high
- * charge channel, then it's not a flasher. */
- if (nhit_late < nhit*0.7) return 0;
+ /* If this channel fired more than 50 ns after the high charge channel,
+ * increment nhit_late. */
+ if (ev->pmt_hits[i].t > t + 50.0) nhit_late += 1;
+ }
- /* If less than 70% of the regular PMTs were closer than 12 meters away
- * from the high charge channel, then it's not a flasher. */
- if (nhit_far < nhit*0.7) return 0;
+ /* If at least 70% of the regular PMTs fired within 50 ns and were at
+ * least 12 meters away from the high charge channel, then it's a
+ * flasher! */
+ if (nhit_late >= 7*nhit/10 && nhit_far >= 7*nhit/10) return 1;
+ }
- return 1;
+ return 0;
}
diff --git a/src/dc.h b/src/dc.h
index 9df82c8..89a7670 100644
--- a/src/dc.h
+++ b/src/dc.h
@@ -20,9 +20,21 @@
#include "event.h"
#include "zebra.h"
+/* Minimum number of normal PMTs which must be hit to be tagged as an incoming
+ * muon. */
+#define MUON_MIN_NHIT 150
+/* Minimum number of OWL PMTs which must be hit to be tagged as an incoming
+ * muon. */
+#define MUON_MIN_OWL_NHIT 5
+/* Minimum value for the maximum OWL QHS value. */
+#define MUON_OWL_QHS 20.0
+/* Percentile of PSUP PMT times to define early OWL hits. */
+#define MUON_OWL_TIME_PERCENTILE 0.1
+
/* QvNHIT ratio threshold. */
#define QRATIO_THRESHOLD 0.25
+int is_muon(event *ev);
int junk_cut(zebraFile *f, zebraBank *bmast, zebraBank *bev);
int crate_isotropy(event *ev);
int qvnhit(event *ev);
diff --git a/src/fit.c b/src/fit.c
index 40f67f5..e4e054f 100644
--- a/src/fit.c
+++ b/src/fit.c
@@ -6172,7 +6172,7 @@ skip_mc:
last_run = ev.run;
}
- rv = get_event(f,&ev,&b);
+ if (get_event(f,&ev,&b)) goto skip_event;
nhit = get_nhit(&ev);
@@ -6181,6 +6181,11 @@ skip_mc:
fprintf(fout, " gtid: %i\n", ev.gtid);
fprintf(fout, " nhit: %zu\n", nhit);
fprintf(fout, " is_flasher: %i\n", is_flasher(&ev));
+ fprintf(fout, " is_muon: %i\n", is_muon(&ev));
+ fprintf(fout, " is_junk: %i\n", junk_cut(f, &bmast, &b));
+ fprintf(fout, " is_crate_isotropy: %i\n", crate_isotropy(&ev));
+ fprintf(fout, " is_qvnhit: %i\n", qvnhit(&ev));
+ fprintf(fout, " is_neck_event: %i\n", is_neck_event(&ev));
}
if (nhit < min_nhit) goto skip_event;
diff --git a/src/zdab_utils.c b/src/zdab_utils.c
index ba76b63..6dffe0f 100644
--- a/src/zdab_utils.c
+++ b/src/zdab_utils.c
@@ -78,6 +78,11 @@ int get_event(zebraFile *f, event *ev, zebraBank *bev)
channel = bpmt.pin % 32;
id = crate*512 + card*32 + channel;
+ if (id >= MAX_PMTS) {
+ fprintf(stderr, "PMT hit from %i/%i/%i\n", crate, card, channel);
+ return -1;
+ }
+
if (ev->pmt_hits[id].hit) {
fprintf(stderr, "%i/%i/%i is in the PMT bank twice!\n", crate, card, channel);
}
@@ -105,6 +110,9 @@ int get_event(zebraFile *f, event *ev, zebraBank *bev)
fprintf(stderr, "%i/%i/%i has PMT type %s but expected %s based on bank\n", crate, card, channel, pmt_type_string, pmt_names[i]);
}
+ /* For now we assume SNOMAN has it correct. */
+ pmts[id].pmt_type = pmt_types[i];
+
if (!b.next) break;
rv = zebra_get_bank(f,&b,b.next);