Skip to content

Commit 5fa0305

Browse files
authored
Merge pull request #61 from sjgardiner/phaseI-beam-cuts
Phase I beam cuts
2 parents 445cc92 + 33ffbef commit 5fa0305

23 files changed

+889
-269
lines changed

DataModel/ANNIEalgorithms.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ template<typename ElementType> void ComputeMeanAndVariance(
2525
}
2626

2727
size_t num_samples = 0;
28-
double mean_x2 = 0.;
28+
double m2 = 0.;
2929
mean = 0.;
3030

3131
for (const ElementType& x : data) {
3232
++num_samples;
3333
double delta = x - mean;
3434
mean += delta / num_samples;
3535
double delta2 = x - mean;
36-
mean_x2 = delta * delta2;
36+
m2 += delta * delta2;
3737
if (num_samples == sample_cutoff) break;
3838
}
3939

40-
var = mean_x2 / (num_samples - 1);
40+
var = m2 / (num_samples - 1);
4141
return;
4242
}
4343

DataModel/BeamStatus.cpp

+14
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,18 @@ void BeamStatus::clear()
1717
time_ = TimeClass(0ull);
1818
pot_ = 0.;
1919
condition_ = BeamCondition::Missing;
20+
data_.clear();
21+
cuts_.clear();
22+
}
23+
24+
void BeamStatus::add_measurement(const std::string& device_name,
25+
uint64_t ms_since_epoch, const BeamDataPoint& bdp)
26+
{
27+
data_[device_name] = std::pair<uint64_t, BeamDataPoint>(ms_since_epoch, bdp);
28+
}
29+
30+
void BeamStatus::add_measurement(const std::string& device_name,
31+
uint64_t ms_since_epoch, double value, const std::string& unit)
32+
{
33+
add_measurement(device_name, ms_since_epoch, BeamDataPoint(value, unit));
2034
}

DataModel/BeamStatus.h

+82-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
// standard library includes
99
#include <iostream>
1010
#include <fstream>
11+
#include <map>
1112
#include <sstream>
1213

1314
// ToolAnalysis includes
15+
#include "BeamDataPoint.h"
1416
#include "SerialisableObject.h"
1517
#include "TimeClass.h"
1618

@@ -59,6 +61,9 @@ class BeamStatus : public SerialisableObject {
5961
inline TimeClass time() const { return time_; }
6062
inline double pot() const { return pot_; }
6163
inline BeamCondition condition() const { return condition_; }
64+
inline const std::map<std::string, std::pair<uint64_t, BeamDataPoint> >&
65+
data() const { return data_; }
66+
inline const std::map<std::string, bool>& cuts() const { return cuts_; }
6267

6368
// Good beam minibuffers for the analysis should use is_beam() == true
6469
// and ok() == true. Good non-beam minibuffers for the analysis
@@ -73,14 +78,52 @@ class BeamStatus : public SerialisableObject {
7378
{ return condition_ != BeamCondition::Bad
7479
&& condition_ != BeamCondition::Missing; }
7580

81+
// Beam quality cut checks
82+
inline bool passed_cut(const std::string& cut_name) const {
83+
return cuts_.at(cut_name);
84+
}
85+
inline bool passed_all_cuts() const {
86+
// Returns true if all of the entries in the cuts_ map have true
87+
// boolean values, or false otherwise.
88+
return std::all_of(cuts_.cbegin(), cuts_.cend(),
89+
[](const std::pair<std::string, bool>& p) -> bool { return p.second; });
90+
}
91+
7692
inline void set_time(TimeClass time) { time_ = time; }
7793
inline void set_pot(double POT) { pot_ = POT; }
7894
inline void set_condition(BeamCondition bc) { condition_ = bc; }
7995

96+
// Adds a new monitoring device measurement to the data map
97+
void add_measurement(const std::string& device_name,
98+
uint64_t ms_since_epoch, const BeamDataPoint& bdp);
99+
100+
void add_measurement(const std::string& device_name,
101+
uint64_t ms_since_epoch, double value, const std::string& unit);
102+
103+
// Adds a new cut result to the cuts map (passed == true means that the
104+
// beam spill survived the quality cut)
105+
// TODO: Note that this will overwrite an existing cut with the same
106+
// name. Think more carefully about whether this is the optimal behavior.
107+
inline void add_cut(const std::string& cut_name, bool passed) {
108+
cuts_[cut_name] = passed;
109+
}
110+
80111
inline bool Print() {
81112
std::cout << "Timestamp : " << time_ << '\n';
82113
std::cout << "Beam Intensity [ETOR875] : " << pot_ << " POT\n";
83114
std::cout << "Condition : " << condition_ << '\n';
115+
for (auto& outer_pair : data_) {
116+
std::cout << "Monitoring Data for device : " << outer_pair.first
117+
<< '\n';
118+
std::cout << "Measurement timestamp : " << outer_pair.second.first
119+
<< " ms since epoch\n";
120+
outer_pair.second.second.Print();
121+
}
122+
for (auto& pair : cuts_) {
123+
if (pair.second) std::cout << "PASSED ";
124+
else std::cout << "FAILED ";
125+
std::cout << " the " << pair.first << " beam quality cut\n";
126+
}
84127
return true;
85128
}
86129

@@ -106,18 +149,54 @@ class BeamStatus : public SerialisableObject {
106149
/// successfully, but the current beam spill should be ignored in the
107150
/// analysis). Reasons for marking a beam minibuffer as "Bad" include
108151
/// a very low POT value (suggesting that the beam monitor E:TOR875
109-
/// was off at the time, Hefty mode information suggesting that one or
110-
/// more self-trigger minibuffers might be missing after the current beam
111-
/// spill, etc.
152+
/// was off at the time), a low peak horn current, etc.
112153
BeamCondition condition_;
113154

155+
// Map storing data from the beam monitoring instruments for the spill
156+
// of interest. Keys are device names, values are pairs where the
157+
// first element is a timestamp for the measurement (in ms since the Unix
158+
// epoch) and the second element is a BeamDataPoint object.
159+
//
160+
// ** Known device names (from the Intensity Frontier beam database) **
161+
// E:1DCNT = Accumulating count of the number of "$1D" triggers (a "$1D"
162+
// signal indicates that the Booster is preparing to send a beam
163+
// pulse to ANNIE).
164+
//
165+
// E:HI860, E:VI860, E:HP860, E:VP860 = beam intensity (I) or position (P)
166+
// monitor reading at position 860.
167+
// Horizontal (H) or vertical (V)
168+
// position monitors are read
169+
// separately.
170+
//
171+
// E:HP875, E:VP875 = similar monitors at position 875
172+
//
173+
// E:HITG1, E:VITG1, E:VPTG1, E:HPTG1 = similar monitors at position TG1
174+
//
175+
// E:HPTG2, E:HITG2, E:VITG2, E:VPTG2 = similar monitors at position TG2
176+
//
177+
// E:THCURR = Horn current
178+
// E:TOR860 = POT as measured by toroid at position 860
179+
// (further away from target than 875)
180+
//
181+
// E:TOR875 = POT as measured by toroid at position 875
182+
std::map<std::string, std::pair<uint64_t, BeamDataPoint> > data_;
183+
184+
// Map storing the results of each of the beam quality cut checks.
185+
// Keys are cut names, values are boolean variables telling whether
186+
// the spill of interest passed (true) or failed (false) the beam
187+
// quality cut. For good beam spills to be used for analysis, all of
188+
// the entries in this map should have a true boolean value.
189+
std::map<std::string, bool> cuts_;
190+
114191
template<class Archive> void serialize(Archive & ar,
115192
const unsigned int version)
116193
{
117194
if (!serialise) return;
118195
ar & time_;
119196
ar & pot_;
120197
ar & condition_;
198+
ar & data_;
199+
ar & cuts_;
121200
}
122201
};
123202
#endif

UserTools/ADCCalibrator/ADCCalibrator.cpp

+70-20
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <string>
2+
13
// ToolAnalysis includes
24
#include "ADCCalibrator.h"
35
#include "ANNIEalgorithms.h"
@@ -56,6 +58,9 @@ bool ADCCalibrator::Execute() {
5658
const auto& channel_key = temp_pair.first;
5759
const auto& raw_waveforms = temp_pair.second;
5860

61+
Log("Making calibrated waveforms for ADC channel " +
62+
std::to_string(channel_key.GetDetectorElementIndex()), 3, verbosity);
63+
5964
calibrated_waveform_map[channel_key] = make_calibrated_waveforms(
6065
raw_waveforms);
6166
}
@@ -74,17 +79,22 @@ void ADCCalibrator::ze3ra_baseline(
7479
const std::vector< Waveform<unsigned short> >& raw_data,
7580
double& baseline, double& sigma_baseline, size_t num_baseline_samples)
7681
{
77-
// All F-distribution probabilities below this value will pass the
78-
// variance consistency test in ze3ra_baseline()
79-
double q_critical;
80-
m_variables.Get("QCritical", q_critical);
82+
int verbosity;
83+
m_variables.Get("verbose", verbosity);
84+
85+
// All F-distribution probabilities above this value will pass the
86+
// variance consistency test in ze3ra_baseline(). That is, p_critical
87+
// is the maximum p-value for which we will reject the null hypothesis
88+
// of equal variances.
89+
double p_critical;
90+
m_variables.Get("PCritical", p_critical);
8191

8292
// Signal ADC means, variances, and F-distribution probability values
83-
// ("Q") for the first num_baseline_samples from each minibuffer
93+
// ("P") for the first num_baseline_samples from each minibuffer
8494
// (in Hefty mode) or from each sub-minibuffer (in non-Hefty mode)
8595
std::vector<double> means;
8696
std::vector<double> variances;
87-
std::vector<double> Qs;
97+
std::vector<double> Ps;
8898

8999
// Hefty mode uses multiple minibuffers, non-Hefty mode uses a single
90100
// minibuffer
@@ -136,11 +146,20 @@ void ADCCalibrator::ze3ra_baseline(
136146
else F = sigma2_jp1 / sigma2_j;
137147

138148
double nu = (num_baseline_samples - 1) / 2.;
139-
double Q = std::tgamma(2*nu)
140-
* annie_math::Incomplete_Beta_Function(1. / (1. + F), nu, nu)
141-
/ (2. * std::tgamma(nu));
149+
double P = annie_math::Regularized_Beta_Function(1. / (1. + F), nu, nu);
150+
151+
// Two-tailed hypothesis test (we need to exclude unusually small values
152+
// as well as unusually large ones). The tails have equal sizes, so we
153+
// may use symmetry and simply multiply our earlier result by 2.
154+
P *= 2.;
155+
156+
// I've never seen this problem (the numerical values for the regularized
157+
// beta function that I've checked all fall within [0,1]), but the book
158+
// Numerical Recipes includes this check in a similar block of code,
159+
// so I'll add it just in case.
160+
if (P > 1.) P = 2. - P;
142161

143-
Qs.push_back(Q);
162+
Ps.push_back(P);
144163
}
145164

146165
// Compute the mean and standard deviation of the baseline signal
@@ -149,30 +168,61 @@ void ADCCalibrator::ze3ra_baseline(
149168
// the critical value.
150169
baseline = 0.;
151170
sigma_baseline = 0.;
171+
double variance_baseline = 0.;
152172
size_t num_passing = 0;
153-
for (size_t k = 0; k < Qs.size(); ++k) {
154-
if (Qs.at(k) < q_critical) {
173+
for (size_t k = 0; k < Ps.size(); ++k) {
174+
if (Ps.at(k) > p_critical) {
155175
++num_passing;
156176
baseline += means.at(k);
157-
sigma_baseline += std::sqrt( variances.at(k) );
177+
variance_baseline += variances.at(k);
158178
}
159179
}
160180

161-
if (num_passing > 0) {
181+
if (num_passing > 1) {
162182
baseline /= num_passing;
163-
sigma_baseline /= num_passing;
183+
184+
variance_baseline *= static_cast<double>(num_baseline_samples - 1)
185+
/ (num_passing*num_baseline_samples - 1);
186+
// Now that we've combined the sample variances correctly, take the
187+
// square root to get the standard deviation
188+
sigma_baseline = std::sqrt( variance_baseline );
189+
}
190+
else if (num_passing == 1) {
191+
// We only have one set of sample statistics, so all we need to
192+
// do is take the square root of the variance to get the standard
193+
// deviation.
194+
sigma_baseline = std::sqrt( variance_baseline );
164195
}
165196
else {
166197
// If none of the minibuffers passed the F-distribution test,
167-
// choose the one closest to passing and adopt its baseline statistics
198+
// choose the one closest to passing (i.e., the one with the largest
199+
// P-value) and adopt its baseline statistics. For a sufficiently large
200+
// number of minibuffers (e.g., 40), such a situation should be very rare.
168201
// TODO: consider changing this approach
169-
auto min_iter = std::min_element(Qs.cbegin(), Qs.cend());
170-
int min_index = std::distance(Qs.cbegin(), min_iter);
202+
auto max_iter = std::max_element(Ps.cbegin(), Ps.cend());
203+
int max_index = std::distance(Ps.cbegin(), max_iter);
171204

172-
baseline = means.at(min_index);
173-
sigma_baseline = std::sqrt( variances.at(min_index) );
205+
baseline = means.at(max_index);
206+
sigma_baseline = std::sqrt( variances.at(max_index) );
174207
}
175208

209+
std::string mb_temp_string = "minibuffer";
210+
if ( !hefty_mode ) mb_temp_string = "sub-minibuffer";
211+
212+
if (verbosity >= 4) {
213+
for ( size_t x = 0; x < Ps.size(); ++x ) {
214+
Log(" " + mb_temp_string + " " + std::to_string(x) + ", mean = "
215+
+ std::to_string(means.at(x)) + ", var = "
216+
+ std::to_string(variances.at(x)) + ", p-value = "
217+
+ std::to_string(Ps.at(x)), 4, verbosity);
218+
}
219+
}
220+
221+
Log(std::to_string(num_passing) + " " + mb_temp_string + " pairs passed the"
222+
" F-test", 3, verbosity);
223+
Log("Baseline estimate: " + std::to_string(baseline) + " ± "
224+
+ std::to_string(sigma_baseline) + " ADC counts", 3, verbosity);
225+
176226
}
177227

178228
std::vector< CalibratedADCWaveform<double> >

UserTools/ADCCalibrator/ADCCalibrator.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ADCCalibrator : public Tool {
2525
/// @brief Compute the baseline for a particular RawChannel
2626
/// object using a technique taken from the ZE3RA code.
2727
/// @details See section 2.2 of https://arxiv.org/pdf/1106.0808.pdf for a
28-
/// full description of the algorithm.
28+
/// description of the algorithm.
2929
void ze3ra_baseline(const std::vector< Waveform<unsigned short> >& raw_data,
3030
double& baseline, double& sigma_baseline, size_t num_baseline_samples);
3131

0 commit comments

Comments
 (0)