ATLAS Offline Software
Loading...
Searching...
No Matches
ZDCPulseAnalyzer.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7#include "TFitResult.h"
8#include "TFitResultPtr.h"
9#include "TVirtualFitter.h"
10#include "TList.h"
11#include "TMinuit.h"
12#include "ZdcAnalysis/ZDCMsg.h"
14
15#include <algorithm>
16#include <sstream>
17#include <cmath>
18#include <numeric>
19#include <iomanip>
20#include <stdexcept>
21
23
24template<typename T> T Sqr(T in) {return in*in;}
25
26//
27// List of allowed JSON configuration parameters
28//
29// For each parameter we have name, JSON value type, whether it can be set per channel, and whether it is required
30//
31// if the type is -1, then there's no value, the presence of the parameter itself is a boolean -- i.e. enabling
32//
34 {"tag", {JSON::value_t::string, 1, true, true}},
35 {"enabled", {JSON::value_t::boolean, 1, true, true}},
36 {"LGMode", {JSON::value_t::number_unsigned, 1, false, true}},
37 {"Nsample", {JSON::value_t::number_integer, 1, false, true}},
38 {"FADCFreqMHz", {JSON::value_t::number_integer, 1, false, true}},
39 {"preSampleIdx", {JSON::value_t::number_integer, 1, true, true}},
40 {"nominalPedestal", {JSON::value_t::number_integer, 1, false, true}},
41 {"fitFunction", {JSON::value_t::string, 1, true, true}},
42 {"peakSample", {JSON::value_t::number_integer, 1, true, true}},
43 {"peakTolerance", {JSON::value_t::number_integer, 1, true, false}},
44 {"quietFits", {JSON::value_t::boolean, 1, false, false}},
45 {"2ndDerivThreshHG", {JSON::value_t::number_integer, 1, true, true}},
46 {"2ndDerivThreshLG", {JSON::value_t::number_integer, 1, true, true}},
47 {"2ndDerivStep", {JSON::value_t::number_integer, 1, false, false}},
48 {"HGOverflowADC", {JSON::value_t::number_integer, 1, true, true}},
49 {"HGUnderflowADC", {JSON::value_t::number_integer, 1, true, true}},
50 {"LGOverflowADC", {JSON::value_t::number_integer, 1, true, true}},
51 {"nominalT0HG", {JSON::value_t::number_float, 1, true, true}},
52 {"nominalT0LG", {JSON::value_t::number_float, 1, true, true}},
53 {"nominalTau1", {JSON::value_t::number_float, 1, true, false}},
54 {"nominalTau2", {JSON::value_t::number_float, 1, true, false}},
55 {"fixTau1", {JSON::value_t::boolean, 1, true, false}},
56 {"fixTau2", {JSON::value_t::boolean, 1, true, false}},
57 {"T0CutsHG", {JSON::value_t::array, 2, true, true}},
58 {"T0CutsLG", {JSON::value_t::array, 2, true, true}},
59 {"chisqDivAmpCutHG", {JSON::value_t::number_float, 1, true, true}},
60 {"chisqDivAmpCutLG", {JSON::value_t::number_float, 1, true, true}},
61 {"chisqDivAmpScaleHG", {JSON::value_t::number_float, 1, true, true}},
62 {"chisqDivAmpScaleLG", {JSON::value_t::number_float, 1, true, true}},
63 {"chisqDivAmpOffsetHG", {JSON::value_t::number_float, 1, true, true}},
64 {"chisqDivAmpOffsetLG", {JSON::value_t::number_float, 1, true, true}},
65 {"chisqDivAmpPowerHG", {JSON::value_t::number_float, 1, true, true}},
66 {"chisqDivAmpPowerLG", {JSON::value_t::number_float, 1, true, true}},
67 {"gainFactorHG", {JSON::value_t::number_float, 1, true, true}},
68 {"gainFactorLG", {JSON::value_t::number_float, 1, true, true}},
69 {"noiseSigmaHG", {JSON::value_t::number_float, 1, true, true}},
70 {"noiseSigmaLG", {JSON::value_t::number_float, 1, true, true}},
71 {"perSampleNoiseSigmaHG", {JSON::value_t::array, -1, true, true}},
72 {"perSampleNoiseSigmaLG", {JSON::value_t::array, -1, true, true}},
73 {"enableRepass", {JSON::value_t::boolean, 1, false, false}},
74 {"Repass2ndDerivThreshHG", {JSON::value_t::number_integer, 1, true, true}},
75 {"Repass2ndDerivThreshLG", {JSON::value_t::number_integer, 1, true, true}},
76 {"fitAmpMinMaxHG", {JSON::value_t::array, 2, true, false}},
77 {"fitAmpMinMaxLG", {JSON::value_t::array, 2, true, false}},
78 {"ampMinSignifHGLG", {JSON::value_t::array, 2, true, false}},
79 {"enablePreExclusion", {JSON::value_t::array, 3, false, false}},
80 {"enablePostExclusion", {JSON::value_t::array, 3, false, false}},
81 {"enableUnderflowExclusionHG", {JSON::value_t::array, 2, true, false}},
82 {"enableUnderflowExclusionLG", {JSON::value_t::array, 2, true, false}},
83 {"enablePrePulseDetection", {JSON::value_t::boolean, 1, false, false}},
84 {"enablePostPulseDetection", {JSON::value_t::boolean, 1, false, false}},
85 {"enableTimingCorrection", {JSON::value_t::array, 3, false, false}},
86 {"timeCorrCoeffHG", {JSON::value_t::array, 6, true, false}},
87 {"timeCorrCoeffLG", {JSON::value_t::array, 6, true, false}},
88 {"enableADCNLCorrection", {JSON::value_t::array, 3, false, false}},
89 {"ADCNLCorrCoeffs", {JSON::value_t::array, 0, true, false}},
90 {"enableNLCorrection", {JSON::value_t::array, 2, false, false}},
91 {"HGNLCorrCoeffs", {JSON::value_t::array, 5, true, false}},
92 {"LGNLCorrCoeffs", {JSON::value_t::array, 5, true, false}},
93 {"useDelayed", {JSON::value_t::boolean, 1, false, false}},
94 {"delayDeltaT", {JSON::value_t::number_float, 1, true, false}},
95 {"delayDefaultPedestalShift", {JSON::value_t::number_float, 1, true, false}},
96 {"fitTimeMax", {JSON::value_t::number_float, 1, true, false}}
97 };
98
103float ZDCPulseAnalyzer::s_combinedFitTMin = -0.5; // add to allow switch to high gain by skipping early samples
104std::vector<float> ZDCPulseAnalyzer::s_pullValues;
105
106void ZDCPulseAnalyzer::CombinedPulsesFCN(int& /*numParam*/, double*, double& f, double* par, int flag)
107{
108 // The first parameter is a correction factor to account for decrease in beam intensity between x
109 // and y scan. It is applied here and not passed to the actual fit function
110 //
111 int nSamples = s_undelayedFitHist->GetNbinsX();
112
113 if (flag == 3) {
114 s_pullValues.assign(nSamples * 2, 0);
115 }
116
117 double chiSquare = 0;
118
119 float delayBaselineAdjust = par[0];
120
121 // undelayed
122 //
123 for (int isample = 0; isample < nSamples; isample++) {
124 double histValue = s_undelayedFitHist->GetBinContent(isample + 1);
125 double histError = std::max(s_undelayedFitHist->GetBinError(isample + 1), 1.0);
126 double t = s_undelayedFitHist->GetBinCenter(isample + 1);
127
128 if (t > s_combinedFitTMax) break;
129 if (t < s_combinedFitTMin) continue;
130
131 double funcVal = s_combinedFitFunc->EvalPar(&t, &par[1]);
132
133 double pull = (histValue - funcVal) / histError;
134
135 if (flag == 3) s_pullValues[2 * isample] = pull;
136 chiSquare += pull * pull;
137 }
138
139 // delayed
140 //
141 for (int isample = 0; isample < nSamples; isample++) {
142 double histValue = s_delayedFitHist->GetBinContent(isample + 1);
143 double histError = std::max(s_delayedFitHist->GetBinError(isample + 1), 1.0);
144 double t = s_delayedFitHist->GetBinCenter(isample + 1);
145
146 if (t > s_combinedFitTMax) break;
147 if (t < s_combinedFitTMin) continue;
148
149 double funcVal = s_combinedFitFunc->EvalPar(&t, &par[1]) + delayBaselineAdjust;
150 double pull = (histValue - funcVal) / histError;
151
152 if (flag == 3) s_pullValues[2 * isample + 1] = pull;
153 chiSquare += pull * pull;
154 }
155
156 f = chiSquare;
157}
158
159
160ZDCPulseAnalyzer::ZDCPulseAnalyzer(ZDCMsg::MessageFunctionPtr msgFunc_p, const std::string& tag, int Nsample, float deltaTSample, size_t preSampleIdx, int pedestal,
161 const std::string& fitFunction, int peak2ndDerivMinSample,
162 float peak2ndDerivMinThreshHG, float peak2ndDerivMinThreshLG) :
163 m_msgFunc_p(std::move(msgFunc_p)),
164 m_tag(tag), m_Nsample(Nsample),
165 m_preSampleIdx(preSampleIdx),
166 m_deltaTSample(deltaTSample),
167 m_pedestal(pedestal), m_fitFunction(fitFunction),
168 m_peak2ndDerivMinSample(peak2ndDerivMinSample),
169 m_peak2ndDerivMinThreshLG(std::abs(peak2ndDerivMinThreshLG)),
170 m_peak2ndDerivMinThreshHG(std::abs(peak2ndDerivMinThreshHG))
171{
172 // Create the histogram used for fitting
173 //
174 m_tmin = -deltaTSample / 2;
175 m_tmax = m_tmin + ((float) Nsample) * deltaTSample;
178
179 std::string histName = "ZDCFitHist" + tag;
180 std::string histNameLGRefit = "ZDCFitHist" + tag + "_LGRefit";
181
182 m_fitHist = std::make_unique<TH1F>(histName.c_str(), "", m_Nsample, m_tmin, m_tmax);
183 m_fitHistLGRefit = std::make_unique<TH1F>(histNameLGRefit.c_str(), "", m_Nsample, m_tmin, m_tmax);
184
185 m_fitHist->SetDirectory(0);
186 m_fitHistLGRefit->SetDirectory(0);
187
188 setDefaults();
189 reset();
190}
191
193 m_msgFunc_p(std::move(msgFunc_p))
194{
195 setDefaults();
196
197 // auto [result, resultString] = ValidateJSONConfig(configJSON);
198 // (*m_msgFunc_p)(ZDCMsg::Debug, "ValidateJSON produced result: " + resultString);
199
200 auto [result2, resultString2] = ConfigFromJSON(configJSON);
201 (*m_msgFunc_p)(ZDCMsg::Debug, "ConfigFromJSON produced result: "+ resultString2);
202
204
205 // Create the histogram used for fitting
206 //
207 if (m_freqMHz >1.e-6) m_deltaTSample = 1000./m_freqMHz;
208 m_tmin = -m_deltaTSample / 2;
209 m_tmax = m_tmin + ((float)m_Nsample) * m_deltaTSample;
211
212 std::string histName = "ZDCFitHist" + m_tag;
213 std::string histNameLGRefit = "ZDCFitHist" + m_tag + "_LGRefit";
214
215 m_fitHist = std::make_unique<TH1F>(histName.c_str(), "", m_Nsample, m_tmin, m_tmax);
216 m_fitHistLGRefit = std::make_unique<TH1F>(histNameLGRefit.c_str(), "", m_Nsample, m_tmin, m_tmax);
217
218 m_fitHist->SetDirectory(0);
219 m_fitHistLGRefit->SetDirectory(0);
220
221 if(m_useDelayed) {
223 }
224
225 reset();
226}
227
228void ZDCPulseAnalyzer::enableDelayed(float deltaT, float pedestalShift, bool fixedBaseline)
229{
230 m_useDelayed = true;
231 m_useFixedBaseline = fixedBaseline;
232
233 m_delayedDeltaT = deltaT;
234 m_delayedPedestalDiff = pedestalShift;
235
236 m_deltaTSample /= 2.;
237
238 std::string delayedHGName = std::string(m_fitHist->GetName()) + "delayed";
239 std::string delayedLGName = std::string(m_fitHistLGRefit->GetName()) + "delayed";
240
241 m_delayedHist = std::make_unique<TH1F>(delayedHGName.c_str(), "", m_Nsample, m_tmin + m_delayedDeltaT, m_tmax + m_delayedDeltaT);
242 m_delayedHist->SetDirectory(0);
243
244 m_delayedHistLGRefit = std::make_unique<TH1F>(delayedLGName.c_str(), "", m_Nsample, m_tmin + m_delayedDeltaT, m_tmax + m_delayedDeltaT);
245 m_delayedHistLGRefit->SetDirectory(0);
246}
247
248void ZDCPulseAnalyzer::enableRepass(float peak2ndDerivMinRepassHG, float peak2ndDerivMinRepassLG)
249{
250 m_enableRepass = true;
251 m_peak2ndDerivMinRepassHG = peak2ndDerivMinRepassHG;
252 m_peak2ndDerivMinRepassLG = peak2ndDerivMinRepassLG;
253}
254
255void ZDCPulseAnalyzer::enablePostPulseCheck(unsigned int postPulseSampleDelta, float postPulseDerivMinSig, float postPulseAbsDer2ndMinSig, float minMainDer2ndRatio)
256{
257 m_doPostPulseCheck = true;
258 m_postPulseDelta = postPulseSampleDelta;
259 m_postPulseDerivMinSig = postPulseDerivMinSig;
260 m_postPulseAbsDer2ndMinSig = postPulseAbsDer2ndMinSig;
261 m_postPulseMainMinDer2ndRatio = minMainDer2ndRatio;
262}
263
265{
267
268 m_nominalTau1 = 1.5;
269 m_nominalTau2 = 5;
270
271 m_fixTau1 = false;
272 m_fixTau2 = false;
273
274 m_HGOverflowADC = 3500;
276 m_LGOverflowADC = 3900;
277
278 // Default values for the gain factors uswed to match low and high gain
279 //
280 m_gainFactorLG = 10;
281 m_gainFactorHG = 1;
282
283 m_2ndDerivStep = 1;
284
285 m_noiseSigHG = 1;
286 m_noiseSigLG = 1;
287
288 m_sigMinLG = 0;
289 m_sigMinHG = 0;
290
291 m_timeCutMode = 0;
292 m_chisqDivAmpCutLG = 100;
293 m_chisqDivAmpCutHG = 100;
294
297
300
303
304 m_LGT0CorrParams.assign(6, 0);
305 m_HGT0CorrParams.assign(6, 0);
306
307 // m_defaultFitTMax = m_tmax;
308 // m_defaultFitTMin = m_tmin;
309
310 m_fitAmpMinHG = 1;
311 m_fitAmpMinLG = 1;
312
313 m_fitAmpMaxHG = 1500;
314 m_fitAmpMaxLG = 1500;
315
316 m_haveSignifCuts = false;
317
319
320 m_doPostPulseCheck = true;
325
326 m_doPrePulseCheck = true;
327 m_prePulseDelta = 2;
328
331
333
334 m_initialExpAmp = 0;
335 m_fitPostT0lo = 0;
336
337 m_useDelayed = false;
338 m_enablePreExcl = false;
339 m_enablePostExcl = false;
340
342 m_haveNonlinCorr = false;
343 m_quietFits = true;
344 m_saveFitFunc = false;
345 m_fitOptions = "s";
346}
347
361
362void ZDCPulseAnalyzer::reset(bool repass)
363{
364 if (!repass) {
365 m_haveData = false;
366
367 m_useLowGain = false;
368 m_fail = false;
369 m_HGOverflow = false;
370
371 m_HGUnderflow = false;
372 m_PSHGOverUnderflow = false;
373 m_LGOverflow = false;
374 m_LGUnderflow = false;
375
376 m_ExcludeEarly = false;
377 m_ExcludeLate = false;
378
379 m_adjTimeRangeEvent = false;
380 m_backToHG_pre = false;
381 m_fixPrePulse = false;
382 m_underflowExclusion = false;
383
384 m_minADCLG = -1;
385 m_maxADCLG = -1;
386 m_minADCHG = -1;
387 m_maxADCHG = -1;
388
389 m_minADCSampleHG = -1;
390 m_maxADCSampleHG = -1;
391 m_minADCSampleLG = -1;
392 m_maxADCSampleLG = -1;
393
394 m_ADCPeakHG = -1;
395 m_ADCPeakLG = -1;
396
399
400 m_useSampleHG.assign(m_NSamplesAna, true);
401 m_useSampleLG.assign(m_NSamplesAna, true);
402
405 }
406 else {
408 }
409
412 }
413 else {
415 }
416
417 m_minSampleEvt = 0;
419
421
424
427
428 m_fitPulls.assign(m_NSamplesAna, 0);
429 }
430
431
434
435 if (m_initializedFits) {
439 }
440
441 // -----------------------
442 // Statuses
443 //
444 m_havePulse = false;
445
446 m_prePulse = false;
447 m_postPulse = false;
448 m_fitFailed = false;
449 m_badChisq = false;
450
451 m_badT0 = false;
452 m_preExpTail = false;
453 m_repassPulse = false;
454
455 m_fitMinAmp = false;
456 m_evtLGRefit = false;
457 m_failSigCut = false;
458
459 // -----------------------
460
462
463 m_fitAmplitude = 0;
464 m_ampNoNonLin = 0;
465 m_fitTime = -100;
466 m_fitTimeSub = -100;
467 m_fitTimeCorr = -100;
468 m_fitTCorr2nd = -100;
469
470 m_fitPreT0 = -100;
471 m_fitPreAmp = -100;
472 m_fitPostT0 = -100;
473 m_fitPostAmp = -100;
474 m_fitExpAmp = -100;
475
476 m_minDeriv2ndSig = -10;
477 m_preExpSig = -10;
478 m_prePulseSig = -10;
479
480 m_fitChisq = 0;
481 m_chisqRatio = 0;
482
483 m_amplitude = 0;
484 m_ampError = 0;
485 m_preSampleAmp = 0;
486 m_preAmplitude = 0;
487 m_postAmplitude = 0;
488 m_expAmplitude = 0;
490
491 m_refitLGAmpl = 0;
493 m_refitLGChisq = 0;
494 m_refitLGTime = -100;
495 m_refitLGTimeSub = -100;
496
499
501
502 m_initialExpAmp = 0;
503 m_fitPostT0lo = 0;
504
505 m_fitPulls.clear();
506
507 m_samplesSub.clear();
508 m_samplesDeriv2nd.clear();
509}
510
511
512void ZDCPulseAnalyzer::setMinimumSignificance(float sigMinHG, float sigMinLG)
513{
514 m_haveSignifCuts = true;
515 m_sigMinHG = sigMinHG;
516 m_sigMinLG = sigMinLG;
517}
518
519void ZDCPulseAnalyzer::SetGainFactorsHGLG(float gainFactorHG, float gainFactorLG)
520{
521 m_gainFactorHG = gainFactorHG;
522 m_gainFactorLG = gainFactorLG;
523}
524
525void ZDCPulseAnalyzer::SetFitMinMaxAmp(float minAmpHG, float minAmpLG, float maxAmpHG, float maxAmpLG)
526{
527 m_fitAmpMinHG = minAmpHG;
528 m_fitAmpMinLG = minAmpLG;
529
530 m_fitAmpMaxHG = maxAmpHG;
531 m_fitAmpMaxLG = maxAmpLG;
532}
533
534void ZDCPulseAnalyzer::SetTauT0Values(bool fixTau1, bool fixTau2, float tau1, float tau2, float t0HG, float t0LG)
535{
536 m_fixTau1 = fixTau1;
537 m_fixTau2 = fixTau2;
538 m_nominalTau1 = tau1;
539 m_nominalTau2 = tau2;
540
541 m_nominalT0HG = t0HG;
542 m_nominalT0LG = t0LG;
543
544 std::ostringstream ostrm;
545 ostrm << "ZDCPulseAnalyzer::SetTauT0Values:: m_fixTau1=" << m_fixTau1 << " m_fixTau2=" << m_fixTau2 << " m_nominalTau1=" << m_nominalTau1 << " m_nominalTau2=" << m_nominalTau2 << " m_nominalT0HG=" << m_nominalT0HG << " m_nominalT0LG=" << m_nominalT0LG;
546
547 (*m_msgFunc_p)(ZDCMsg::Info, ostrm.str());
548
549 m_initializedFits = false;
550}
551
553{
554 if (tmax < m_tmin) {
555 (*m_msgFunc_p)(ZDCMsg::Error, ("ZDCPulseAnalyzer::SetFitTimeMax:: invalid FitTimeMax: " + std::to_string(tmax)));
556 return;
557 }
558
559 m_defaultFitTMax = std::min(tmax, m_defaultFitTMax);
560
562}
563
564void ZDCPulseAnalyzer::SetADCOverUnderflowValues(int HGOverflowADC, int HGUnderflowADC, int LGOverflowADC)
565{
566 m_HGOverflowADC = HGOverflowADC;
567 m_LGOverflowADC = LGOverflowADC;
568 m_HGUnderflowADC = HGUnderflowADC;
569}
570
571void ZDCPulseAnalyzer::SetCutValues(float chisqDivAmpCutHG, float chisqDivAmpCutLG,
572 float deltaT0MinHG, float deltaT0MaxHG,
573 float deltaT0MinLG, float deltaT0MaxLG)
574{
575 m_chisqDivAmpCutHG = chisqDivAmpCutHG;
576 m_chisqDivAmpCutLG = chisqDivAmpCutLG;
577
580
583
586
587 m_T0CutLowHG = deltaT0MinHG;
588 m_T0CutLowLG = deltaT0MinLG;
589
590 m_T0CutHighHG = deltaT0MaxHG;
591 m_T0CutHighLG = deltaT0MaxLG;
592}
593
594void ZDCPulseAnalyzer::SetTimeCuts(float deltaT0MinHG, float deltaT0MaxHG,
595 float deltaT0MinLG, float deltaT0MaxLG)
596{
597 m_T0CutLowHG = deltaT0MinHG;
598 m_T0CutLowLG = deltaT0MinLG;
599
600 m_T0CutHighHG = deltaT0MaxHG;
601 m_T0CutHighLG = deltaT0MaxLG;
602}
603
604void ZDCPulseAnalyzer::SetChisqCuts(float chisqDivAmpCutHG, float chisqDivAmpScaleHG, float chisqDivAmpOffsetHG, float chisqDivAmpPowerHG,
605 float chisqDivAmpCutLG, float chisqDivAmpScaleLG, float chisqDivAmpOffsetLG, float chisqDivAmpPowerLG)
606{
607 m_chisqDivAmpCutHG = chisqDivAmpCutHG;
608 m_chisqDivAmpScaleHG = chisqDivAmpScaleHG;
609 m_chisqDivAmpOffsetHG = chisqDivAmpOffsetHG;
610 m_chisqDivAmpPowerHG = chisqDivAmpPowerHG;
611
612 m_chisqDivAmpCutLG = chisqDivAmpCutLG;
613 m_chisqDivAmpScaleLG = chisqDivAmpScaleLG;
614 m_chisqDivAmpOffsetLG = chisqDivAmpOffsetLG;
615 m_chisqDivAmpPowerLG = chisqDivAmpPowerLG;
616}
617
618void ZDCPulseAnalyzer::enableTimeSigCut(bool AND, float sigCut, const std::string& TF1String,
619 const std::vector<double>& parsHG,
620 const std::vector<double>& parsLG)
621{
622 m_timeCutMode = AND ? 2 : 1;
623 m_t0CutSig = sigCut;
624
625 // Make the TF1's that provide the resolution
626 //
627 std::string timeResHGName = "TimeResFuncHG_" + m_tag;
628 std::string timeResLGName = "TimeResFuncLG_" + m_tag;
629
630 TF1* funcHG_p = new TF1(timeResHGName.c_str(), TF1String.c_str(), 0, m_HGOverflowADC);
631 TF1* funcLG_p = new TF1(timeResLGName.c_str(), TF1String.c_str(), 0, m_LGOverflowADC);
632
633 if (parsHG.size() != static_cast<unsigned int>(funcHG_p->GetNpar()) ||
634 parsLG.size() != static_cast<unsigned int>(funcLG_p->GetNpar())) {
635 //
636 // generate an error message
637 }
638
639 funcHG_p->SetParameters(&parsHG[0]);
640 funcLG_p->SetParameters(&parsLG[0]);
641
642 m_timeResFuncHG_p.reset(funcHG_p);
643 m_timeResFuncLG_p.reset(funcLG_p);
644
645}
646
647void ZDCPulseAnalyzer::enableFADCCorrections(bool correctPerSample, std::unique_ptr<const TH1>& correHistHG, std::unique_ptr<const TH1>& correHistLG)
648{
650 m_FADCCorrPerSample = correctPerSample;
651
652 // check for appropriate limits
653 //
654 auto getXmin=[](const TH1 * pH){
655 return pH->GetXaxis()->GetXmin();
656 };
657 auto getXmax=[](const TH1 * pH){
658 return pH->GetXaxis()->GetXmax();
659 };
660 auto xmin= getXmin(correHistHG.get());
661 auto xmax= getXmax(correHistHG.get());
662 if (std::abs(xmin+0.5) > 1e-3 || std::abs(xmax - 4095.5) > 1e-3) {
663 (*m_msgFunc_p)(ZDCMsg::Error, "ZDCPulseAnalyzer::enableFADCCorrections:: invalid high gain correction histogram range: xmin, xmax = " +
664 std::to_string(xmin ) + ", " + std::to_string(xmax) );
665 }
666 else {
667 m_FADCCorrHG = std::move(correHistHG);
668 }
669 xmin= getXmin(correHistLG.get());
670 xmax= getXmax(correHistLG.get());
671 if (std::abs(xmin+0.5) > 1e-3 ||
672 std::abs(xmax - 4095.5) > 1e-3) {
673 (*m_msgFunc_p)(ZDCMsg::Error, "ZDCPulseAnalyzer::enableFADCCorrections:: invalid low gain correction histogram range: xmin, xmax = " +
674 std::to_string(xmin) + ", " + std::to_string(xmax) );
675 }
676 else {
677 m_FADCCorrLG = std::move(correHistLG);
678 }
679}
680
681std::vector<float> ZDCPulseAnalyzer::GetFitPulls(bool refitLG) const
682{
683 //
684 // If there was no pulse for this event, return an empty vector (see reset() method)
685 //
686 if (!m_havePulse) {
687 return m_fitPulls;
688 }
689
690 //
691 // The combined (delayed+undelayed) pulse fitting fills out m_fitPulls directly
692 //
693 if (m_useDelayed) {
694 return m_fitPulls;
695 }
696 else {
697 //
698 // When not using combined fitting, We don't have the pre-calculated pulls. Calculate them on the fly.
699 //
700 std::vector<float> pulls(m_Nsample, -100);
701
702 const TH1* dataHist_p = refitLG ? m_fitHistLGRefit.get() : m_fitHist.get();
703 const TF1* fit_p = static_cast<const TF1*>(dataHist_p->GetListOfFunctions()->Last());
704
705 for (size_t ibin = 0; ibin < m_Nsample ; ibin++) {
706 float t = dataHist_p->GetBinCenter(ibin + 1);
707 float fitVal = fit_p->Eval(t);
708 float histVal = dataHist_p->GetBinContent(ibin + 1);
709 float histErr = dataHist_p->GetBinError(ibin + 1);
710 float pull = (histVal - fitVal)/histErr;
711 pulls[ibin] = pull;
712 }
713
714 return pulls;
715 }
716}
717
719{
720 double amplCorrFactor = 1;
721
722 // If we have FADC correction and we aren't applying it per-sample, do so here
723 //
725 double fadcCorr = highGain ? m_FADCCorrHG->Interpolate(m_fitAmplitude) : m_FADCCorrLG->Interpolate(m_fitAmplitude);
726 amplCorrFactor *= fadcCorr;
727 }
728
729 double amplCorr = m_fitAmplitude * amplCorrFactor;
730
731 // If we have a non-linear correction, apply it here
732 // We apply it as an inverse correction - i.e. we divide by a correction
733 // term tha is a sum of coefficients times the ADC minus a reference
734 // to a power. The lowest power is 1, the highest is deteremined by
735 // the number of provided coefficients
736
737 if (m_haveNonlinCorr) {
738 float invNLCorr = 1.0;
739 float nlPolyArg = (amplCorr - m_nonLinCorrRefADC) / m_nonLinCorrRefScale;
740
741 if (highGain) {
742 for (size_t power = 1; power <= m_nonLinCorrParamsHG.size(); power++) {
743 invNLCorr += m_nonLinCorrParamsHG[power - 1]*pow(nlPolyArg, power);
744 }
745 }
746 else {
747 for (size_t power = 1; power <= m_nonLinCorrParamsLG.size(); power++) {
748 invNLCorr += m_nonLinCorrParamsLG[power - 1]*pow(nlPolyArg, power);
749 }
750 }
751
752 amplCorrFactor /= invNLCorr;
753 }
754
755 return amplCorrFactor;
756}
757
759{
760 float prePulseTMin = 0;
761 float prePulseTMax = prePulseTMin + m_deltaTSample * (m_peak2ndDerivMinSample - m_peak2ndDerivMinTolerance);
762
763 if (m_fitFunction == "FermiExp") {
764 if (!m_fixTau1 || !m_fixTau2) {
765 //
766 // Use the variable tau version of the expFermiFit
767 //
769 }
770 else {
772 }
773
774 m_preExpFitWrapper = std::unique_ptr<ZDCFitExpFermiPreExp>(new ZDCFitExpFermiPreExp(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2, m_nominalTau2, false));
775 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitExpFermiPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
776 }
777 else if (m_fitFunction == "FermiExpRun3") {
778 if (!m_fixTau1 || !m_fixTau2) {
779 //
780 // Use the variable tau version of the expFermiFit
781 //
783 }
784 else {
786 }
787
788 m_preExpFitWrapper = std::unique_ptr<ZDCFitExpFermiPreExp>(new ZDCFitExpFermiPreExp(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2, 6, false));
789 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitExpFermiPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
790 }
791 else if (m_fitFunction == "FermiExpLHCf") {
792 //
793 // Use the variable tau version of the expFermiFit
794 //
797
798 m_preExpFitWrapper = std::unique_ptr<ZDCFitExpFermiLHCfPreExp>(new ZDCFitExpFermiLHCfPreExp(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2, 6, false));
799
800 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitExpFermiLHCfPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
801 }
802 else if (m_fitFunction == "FermiExpInduct") {
803 //
804 // Use the variable tau version of the expFermiFit
805 //
808 m_preExpFitWrapper = std::unique_ptr<ZDCFitExpFermiInductPreExp>(new ZDCFitExpFermiInductPreExp(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2, 6, false));
809
810 // We still have to implement the pre-pulse version of the new induct function For now, using old ("LHCf") version
811 //
812 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitExpFermiLHCfPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
813 }
814 else if (m_fitFunction == "FermiExpLinear") {
815 if (!m_fixTau1 || !m_fixTau2) {
816 //
817 // Use the variable tau version of the expFermiFit
818 //
820 }
821 else {
823 }
824
825 m_preExpFitWrapper = std::unique_ptr<ZDCFitExpFermiPreExp>(new ZDCFitExpFermiPreExp(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2, 6, false));
826 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitExpFermiLinearPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
827 }
828 else if (m_fitFunction == "ComplexPrePulse") {
829 if (!m_fixTau1 || !m_fixTau2) {
830 //
831 // Use the variable tau version of the expFermiFit
832 //
834 }
835 else {
837 }
838
839 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitComplexPrePulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
840 }
841 else if (m_fitFunction == "GeneralPulse") {
842 if (!m_fixTau1 || !m_fixTau2) {
843 //
844 // Use the variable tau version of the expFermiFit
845 //
847 }
848 else {
850 }
851
852 m_prePulseFitWrapper = std::unique_ptr<ZDCPrePulseFitWrapper>(new ZDCFitGeneralPulse(m_tag, m_tmin, m_tmax, m_nominalTau1, m_nominalTau2));
853 }
854 else {
855 (*m_msgFunc_p)(ZDCMsg::Fatal, "Wrong fit function type: " + m_fitFunction);
856 }
857
858 m_prePulseFitWrapper->SetPrePulseT0Range(prePulseTMin, prePulseTMax);
859
860 m_initializedFits = true;
861}
862
863bool ZDCPulseAnalyzer::LoadAndAnalyzeData(const std::vector<float>& ADCSamplesHG, const std::vector<float>& ADCSamplesLG)
864{
865 if (m_useDelayed) {
866 (*m_msgFunc_p)(ZDCMsg::Fatal, "ZDCPulseAnalyzer::LoadAndAnalyzeData:: Wrong LoadAndAnalyzeData called -- expecting both delayed and undelayed samples");
867 }
868
871
872 // Clear any transient data
873 //
874 reset(false);
875
876 // Make sure we have the right number of samples. Should never fail. necessry?
877 //
878 if (ADCSamplesHG.size() != m_Nsample || ADCSamplesLG.size() != m_Nsample) {
879 m_fail = true;
880 return false;
881 }
882
883 m_ADCSamplesHG = ADCSamplesHG;
884 m_ADCSamplesLG = ADCSamplesLG;
885
886 return DoAnalysis(false);
887}
888
889bool ZDCPulseAnalyzer::LoadAndAnalyzeData(const std::vector<float>& ADCSamplesHG, const std::vector<float>& ADCSamplesLG,
890 const std::vector<float>& ADCSamplesHGDelayed, const std::vector<float>& ADCSamplesLGDelayed)
891{
892 if (!m_useDelayed) {
893 (*m_msgFunc_p)(ZDCMsg::Fatal, "ZDCPulseAnalyzer::LoadAndAnalyzeData:: Wrong LoadAndAnalyzeData called -- expecting only undelayed samples");
894 }
895
898
899 // Clear any transient data
900 //
901 reset();
902
903 // Make sure we have the right number of samples. Should never fail. necessry?
904 //
905 if (ADCSamplesHG.size() != m_Nsample || ADCSamplesLG.size() != m_Nsample ||
906 ADCSamplesHGDelayed.size() != m_Nsample || ADCSamplesLGDelayed.size() != m_Nsample) {
907 m_fail = true;
908 return false;
909 }
910
911 // +++ BAC Mar 22, 2026
912 // Trying to rationalize the initialization/handling of transient data. These line should not be necessary. Thus commented
913 // ---
914 //
915 // m_ADCSamplesHG.reserve(m_Nsample*2);
916 // m_ADCSamplesLG.reserve(m_Nsample*2);
917
918 // Now do pedestal subtraction and check for overflows
919 //
920 for (size_t isample = 0; isample < m_Nsample; isample++) {
921 float ADCHG = ADCSamplesHG[isample];
922 float ADCLG = ADCSamplesLG[isample];
923
924 float ADCHGDelay = ADCSamplesHGDelayed[isample] - m_delayedPedestalDiff;
925 float ADCLGDelay = ADCSamplesLGDelayed[isample] - m_delayedPedestalDiff;
926
927 if (m_delayedDeltaT > 0) {
928 m_ADCSamplesHG.push_back(ADCHG);
929 m_ADCSamplesHG.push_back(ADCHGDelay);
930
931 m_ADCSamplesLG.push_back(ADCLG);
932 m_ADCSamplesLG.push_back(ADCLGDelay);
933 }
934 else {
935 m_ADCSamplesHG.push_back(ADCHGDelay);
936 m_ADCSamplesHG.push_back(ADCHG);
937
938 m_ADCSamplesLG.push_back(ADCLGDelay);
939 m_ADCSamplesLG.push_back(ADCLG);
940 }
941 }
942
943 return DoAnalysis(false);
944}
945
947{
948 reset(true);
949
950 bool result = DoAnalysis(true);
951 if (result && havePulse()) {
952 m_repassPulse = true;
953 }
954
955 return result;
956}
957
959{
960 //
961 // Dump samples to verbose output
962 //
963 bool doDump = (*m_msgFunc_p)(ZDCMsg::Verbose, "Dumping all samples before subtraction: ");
964 if (doDump) {
965 std::ostringstream dumpStringHG;
966 dumpStringHG << "HG: ";
967 for (auto val : m_ADCSamplesHG) {
968 dumpStringHG << std::setw(4) << val << " ";
969 }
970
971 (*m_msgFunc_p)(ZDCMsg::Verbose, dumpStringHG.str().c_str());
972
973
974 // Now low gain
975 //
976 std::ostringstream dumpStringLG;
977 dumpStringLG << "LG: " << std::setw(4) << std::setfill(' ');
978 for (auto val : m_ADCSamplesLG) {
979 dumpStringLG << std::setw(4) << val << " ";
980 }
981
982 (*m_msgFunc_p)(ZDCMsg::Verbose, dumpStringLG.str().c_str());
983 }
984
985 // Now do pedestal subtraction and check for overflows
986 //
989
990 m_maxADCHG = 0;
991 m_maxADCLG = 0;
992
993 for (size_t isample = 0; isample < m_NSamplesAna; isample++) {
994 float ADCHG = m_ADCSamplesHG[isample];
995 float ADCLG = m_ADCSamplesLG[isample];
996
997 //
998 // We always pedestal subtract even if the sample isn't going to be used in analysis
999 // basically for diagnostics
1000 //
1001 m_ADCSamplesHGSub[isample] = ADCHG - m_pedestal;
1002 m_ADCSamplesLGSub[isample] = ADCLG - m_pedestal;
1003
1004
1005 // If we have the per-sample FADC correction, we apply it after pedestal correction
1006 // since we analyze the FADC response after baseline (~ same as pedestal) subtraction
1007 //
1009 double fadcCorrHG = m_FADCCorrHG->Interpolate(m_ADCSamplesHGSub[isample]);
1010 double fadcCorrLG = m_FADCCorrLG->Interpolate(m_ADCSamplesLGSub[isample]);
1011
1012 m_ADCSamplesHGSub[isample] *= fadcCorrHG;
1013 m_ADCSamplesLGSub[isample] *= fadcCorrLG;
1014
1015 // if (doDump) {
1016 // std::ostringstream dumpString;
1017 // dumpString << "After FADC correction, sample " << isample << ", HG ADC = " << m_ADCSamplesHGSub[isample]
1018 // << ", LG ADC = " << m_ADCSamplesLGSub[isample] << std::endl;
1019 // (*m_msgFunc_p)(ZDCMsg::Verbose, dumpString.str().c_str());
1020 // }
1021 }
1022
1023 if (ADCHG > m_maxADCHG) {
1024 m_maxADCHG = ADCHG;
1025 m_maxADCSampleHG = isample;
1026 }
1027 else if (ADCHG < m_minADCHG) {
1028 m_minADCHG = ADCHG;
1029 m_minADCSampleHG = isample;
1030 }
1031
1032 if (ADCLG > m_maxADCLG) {
1033 m_maxADCLG = ADCLG;
1034 m_maxADCSampleLG = isample;
1035 }
1036 else if (ADCLG < m_minADCLG) {
1037 m_minADCLG = ADCLG;
1038 m_minADCSampleLG = isample;
1039 }
1040
1041 if (m_useSampleHG[isample]) {
1042 if (m_enablePreExcl) {
1043 if (ADCHG > m_preExclHGADCThresh && isample < m_maxSamplesPreExcl) {
1044 m_useSampleHG[isample] = false;
1045 continue;
1046 }
1047 }
1048
1049 if (m_enablePostExcl) {
1050 if (ADCHG > m_postExclHGADCThresh && isample >= m_NSamplesAna - m_maxSamplesPostExcl) {
1051 m_useSampleHG[isample] = false;
1052 continue;
1053 }
1054 }
1055
1056 // If we get here, we've passed the above filters on the sample
1057 //
1058 if (ADCHG > m_HGOverflowADC) {
1059 m_HGOverflow = true;
1060
1061 if (isample == m_preSampleIdx) m_PSHGOverUnderflow = true;
1062
1063 // Note: the implementation of the explicit pre- and post- sample exclusion should make this
1064 // code obselete but before removing it we first need to validate the pre- and post-exclusion
1065 //
1066 if ((int) isample > m_lastHGOverFlowSample) m_lastHGOverFlowSample = isample;
1067 if ((int) isample < m_firstHGOverFlowSample) m_firstHGOverFlowSample = isample;
1068 }
1069 else if (ADCHG < m_HGUnderflowADC) {
1072 m_useSampleHG[isample] = false;
1073 m_underflowExclusion = true;
1074 }
1075 else {
1076 m_HGUnderflow = true;
1077
1078 if (isample == m_preSampleIdx) m_PSHGOverUnderflow = true;
1079 }
1080 }
1081 }
1082
1083 // Now the low gain sample
1084 //
1085 if (m_useSampleLG[isample]) {
1086 if (m_enablePreExcl) {
1087 if (ADCLG > m_preExclLGADCThresh && isample <= m_maxSamplesPreExcl) {
1088 m_useSampleLG[isample] = false;
1089 continue;
1090 }
1091 }
1092
1093 if (m_enablePostExcl) {
1094 if (ADCLG > m_postExclLGADCThresh && isample >= m_NSamplesAna - m_maxSamplesPostExcl) {
1095 m_useSampleLG[isample] = false;
1096 m_ExcludeLate = true;
1097 continue;
1098 }
1099 }
1100
1101 if (ADCLG > m_LGOverflowADC) {
1102 m_LGOverflow = true;
1103 m_fail = true;
1104 m_amplitude = m_LGOverflowADC * m_gainFactorLG; // Give a vale here even though we know it's wrong because
1105 // the user may not check the return value and we know that
1106 // amplitude is bigger than this
1107 }
1108
1109 if (ADCLG == 0) {
1112 m_underflowExclusion = true;
1113 m_useSampleLG[isample] = false;
1114 }
1115 else {
1116 m_LGUnderflow = true;
1117 m_fail = true;
1118 }
1119 }
1120 }
1121 }
1122
1123 // if (doDump) {
1124 // (*m_msgFunc_p)(ZDCMsg::Verbose, "Dump of useSamples: ");
1125
1126 // std::ostringstream dumpStringUseHG;
1127 // dumpStringUseHG << "HG: ";
1128 // for (auto val : m_useSampleHG) {
1129 // dumpStringUseHG << val << " ";
1130 // }
1131 // (*m_msgFunc_p)(ZDCMsg::Verbose, dumpStringUseHG.str().c_str());
1132
1133 // std::ostringstream dumpStringUseLG;
1134 // dumpStringUseLG << "LG: ";
1135 // for (auto val : m_useSampleLG) {
1136 // dumpStringUseLG << val << " ";
1137 // }
1138 // (*m_msgFunc_p)(ZDCMsg::Verbose, dumpStringUseLG.str().c_str());
1139 // }
1140
1141 // This ugly code should be obseleted by the introduction of the better, pre- and post-sample exclusion but
1142 // that code still has to be fully validated.
1143 //
1144 // See if we can still use high gain even in the case of HG overflow if the overflow results from
1145 // the first few samples or the last few samples
1146 //
1147 if (m_HGOverflow && !m_HGUnderflow) {
1149 m_HGOverflow = false;
1151 m_adjTimeRangeEvent = true;
1152 m_backToHG_pre = true;
1153 m_ExcludeEarly = true;
1154 }
1155 else if (m_firstHGOverFlowSample < static_cast<int>(m_NSamplesAna) && m_firstHGOverFlowSample >= static_cast<int>(m_NSamplesAna - 2) ) {
1157 m_HGOverflow = false;
1158 m_adjTimeRangeEvent = true;
1159 m_ExcludeLate = true;
1160 }
1161 }
1162
1163 return true;
1164}
1165
1167{
1168 float deriv2ndThreshHG = 0;
1169 float deriv2ndThreshLG = 0;
1170
1171 if (!repass) {
1173
1174 deriv2ndThreshHG = m_peak2ndDerivMinThreshHG;
1175 deriv2ndThreshLG = m_peak2ndDerivMinThreshLG;
1176 }
1177 else {
1178 deriv2ndThreshHG = m_peak2ndDerivMinRepassHG;
1179 deriv2ndThreshLG = m_peak2ndDerivMinRepassLG;
1180 }
1181
1183 if (m_useLowGain) {
1184 // (*m_msgFunc_p)(ZDCMsg::Verbose, "ZDCPulseAnalyzer:: " + m_tag + " using low gain data ");
1185
1186 auto chisqCutLambda = [cut = m_chisqDivAmpCutLG,
1187 scale = m_chisqDivAmpScaleLG,
1188 offset = m_chisqDivAmpOffsetLG,
1189 power = m_chisqDivAmpPowerLG]
1190 (float chisq, float amp, unsigned int fitNDoF, float& ratio)->bool
1191 {
1192 // Avoid a spurious FPE from clang.
1194 if (amp < 1e-6) return true;
1195 ratio = chisq /(offset + scale* (std::pow(amp/1000, power)));
1196 if (chisq/fitNDoF > 2 && ratio > cut) return false;
1197 else return true;
1198 };
1199
1201 deriv2ndThreshLG, m_LGT0CorrParams, chisqCutLambda, m_T0CutLowLG, m_T0CutHighLG);
1202 if (result) {
1203 //
1204 // +++BAC
1205 //
1206 // I have removed some code that attempted a refit in case of failure of the chi-square cut
1207 // Instead, we should implement an analysis of the failure with possible re-fit afterwards
1208 // with possible change of the pre- or post-pulse condition
1209 //
1210 // --BAC
1211 //
1212
1213 double amplCorrFactor = getAmplitudeCorrection(false);
1214
1215 //
1216 // Multiply amplitude by gain factor
1217 //
1219 m_amplitude = m_fitAmplitude * amplCorrFactor * m_gainFactorLG;
1220 m_ampError = m_fitAmpError * amplCorrFactor * m_gainFactorLG;
1225
1226 // BAC: also scale up the 2nd derivative by the gain factor so low and high gain can be treated on the same footing
1227 //
1229 }
1230
1231 return result;
1232 }
1233 else {
1234 // (*m_msgFunc_p)(ZDCMsg::Verbose, "ZDCPulseAnalyzer:: " + m_tag + " using high gain data ");
1235 auto chisqCutLambda = [cut = m_chisqDivAmpCutHG,
1236 scale = m_chisqDivAmpScaleHG,
1237 offset = m_chisqDivAmpOffsetHG,
1238 power = m_chisqDivAmpPowerHG, tag = m_tag]
1239 (float chisq, float amp, unsigned int fitNDoF, float& ratio)->bool
1240 {
1241 // Avoid a spurious FPE from clang.
1243 if (amp < 1e-6) return true;
1244 ratio = chisq /(offset + scale*(std::pow(amp/1000, power)));
1245 if (chisq/float(fitNDoF) > 2 && ratio > cut) return false;
1246 else return true;
1247 };
1248
1250 deriv2ndThreshHG, m_HGT0CorrParams, chisqCutLambda, m_T0CutLowHG, m_T0CutHighHG);
1251 if (result) {
1252 // +++BAC
1253 //
1254 // I have removed some code that attempted a refit in case of failure of the chi-square cut
1255 // Instead, we should implement an analysis of the failure with possible re-fit afterwards
1256 // with possible change of the pre- or post-pulse condition
1257 //
1258 // --BAC
1259
1267
1269
1270 // If we have a non-linear correction, apply it here
1271 //
1272 // We apply it as an inverse correction - i.e. we divide by a correction
1273 // term tha is a sum of coefficients times the ADC minus a reference
1274 // to a power. The lowest power is 1, the highest is deteremined by
1275 // the number of provided coefficients
1276 //
1277 double amplCorrFactor = getAmplitudeCorrection(true);
1278
1279 m_amplitude *= amplCorrFactor;
1280 m_ampError *= amplCorrFactor;
1281 }
1282
1283 // If LG refit has been requested, do it now
1284 //
1287 DoFit(true);
1288
1289 double amplCorrFactor = getAmplitudeCorrection(false);
1290 m_refitLGAmplCorr = m_refitLGAmpl*amplCorrFactor;
1291 }
1292
1293 return result;
1294 }
1295}
1296
1297bool ZDCPulseAnalyzer::AnalyzeData(size_t nSamples, size_t preSampleIdx,
1298 const std::vector<float>& samples, // The samples used for this event
1299 const std::vector<float>& samplesNoise, // The per-sample noise used for this event
1300 const std::vector<bool>& useSample, // Whether each sample is to be used for this event
1301 float peak2ndDerivMinThresh,
1302 const std::vector<float>& t0CorrParams, // The parameters used to correct the t0
1303 ChisqCutLambdatype chisqCutLambda, // Lambda to perform the selection
1304 float minT0Corr, float maxT0Corr // The minimum and maximum corrected T0 values
1305 )
1306{
1307
1308 // We keep track of which sample we used to do the subtraction sepaate from m_minSampleEvt
1309 // because the corresponding time is the reference time we provide to the fit function when
1310 // we have pre-pulses and in case we change the m_minSampleEvt after doing the subtraction
1311 //
1312 // e.g. our refit when the chisquare cuts fails
1313 //
1314
1315 // Find the first used sample in the event
1316 //
1317 bool haveFirst = false;
1318 unsigned int lastUsed = 0;
1319
1320 for (unsigned int sample = preSampleIdx; sample < nSamples; sample++) {
1321 if (useSample[sample]) {
1322 //
1323 // We're going to use this sample in the analysis, update bookeeping
1324 //
1325 if (!haveFirst) {
1326 if (sample > m_minSampleEvt) m_minSampleEvt = sample;
1327 haveFirst = true;
1328 }
1329 else {
1330 lastUsed = sample;
1331 }
1332 }
1333 }
1334
1335 if (lastUsed < m_maxSampleEvt) m_maxSampleEvt = lastUsed;
1336
1337 // Check to see whether we've changed the range of samples used in the analysis
1338 //
1339 // Should be obseleted with reworking of the fitting using Root::Fit package
1340 //
1341 if (m_minSampleEvt > preSampleIdx) {
1342 m_ExcludeEarly = true;
1343 m_adjTimeRangeEvent = true;
1344 }
1345
1346 if (m_maxSampleEvt < nSamples - 1) {
1347 m_adjTimeRangeEvent = true;
1348 m_ExcludeLate = true;
1349 }
1350
1351 // Prepare for subtraction
1352 //
1354 m_preSample = samples[m_usedPresampIdx];
1355
1356 // std::ostringstream pedMessage;
1357 // pedMessage << "Pedestal index = " << m_usedPresampIdx << ", value = " << m_preSample;
1358 // (*m_msgFunc_p)(ZDCMsg::Verbose, pedMessage.str().c_str());
1359
1360 m_samplesSub = samples;
1361 m_samplesNoise = samplesNoise;
1362
1363 //
1364 // When we are combinig delayed and undelayed samples we have to deal with the fact that
1365 // the two readouts can have different noise and thus different baselines. Which is a huge
1366 // headache.
1367 //
1368 if (m_useDelayed) {
1369 if (m_useFixedBaseline) {
1371 }
1372 else {
1373 //
1374 // Use much-improved method to match delayed and undelayed baselines
1375 //
1377 std::ostringstream baselineMsg;
1378 baselineMsg << "Delayed samples baseline correction = " << m_baselineCorr << std::endl;
1379 (*m_msgFunc_p)(ZDCMsg::Debug, baselineMsg.str().c_str());
1380 }
1381
1382 // Now apply the baseline correction to align ADC values for delayed and undelayed samples
1383 //
1384 for (size_t isample = 0; isample < nSamples; isample++) {
1385 if (isample % 2) m_samplesSub[isample] -= m_baselineCorr;
1386 }
1387
1388 // If we use one of the delayed samples for presample, we have to adjust the presample value as well
1389 //
1391 }
1392
1393 // Do the presample subtraction
1394 //
1395 std::for_each(m_samplesSub.begin(), m_samplesSub.end(), [psval = m_preSample] (float & adcUnsub) {return adcUnsub -= psval;} );
1396
1397 // Calculate the second derivatives using step size m_2ndDerivStep
1398 //
1400 m_samplesDeriv2nd = deriv2ndResult.first;
1401 m_samplesDeriv2ndErr = deriv2ndResult.second;
1402
1403 // Find the sample which has the lowest 2nd derivative. We loop over the range defined by the
1404 // tolerance on the position of the minimum second derivative. Note: the +1 in the upper iterator is because
1405 // that's where the loop terminates, not the last element.
1406 //
1407 SampleCIter minDeriv2ndIter;
1408
1409 int upperDelta = std::min(m_peak2ndDerivMinSample + m_peak2ndDerivMinTolerance + 1, nSamples);
1410
1411 minDeriv2ndIter = std::min_element(m_samplesDeriv2nd.begin() + m_peak2ndDerivMinSample - m_peak2ndDerivMinTolerance, m_samplesDeriv2nd.begin() + upperDelta);
1412
1413 m_minDeriv2nd = *minDeriv2ndIter;
1414 m_minDeriv2ndIndex = std::distance(m_samplesDeriv2nd.cbegin(), minDeriv2ndIter);
1416
1417 m_havePulse = false;
1418
1419 // If the second derivative is greater than the threshold, we may have a pulse
1420 //
1421 if (std::abs(m_minDeriv2nd) >= peak2ndDerivMinThresh) {
1422 //
1423 // Check that we have found a real maximum
1424 //
1429
1430 if ((deltaLeft2 > 0 || deltaLeft > 0) && (deltaRight2 > 0 || deltaRight > 0)) {
1431 m_havePulse = true;
1432 }
1433 else {
1434 // In the case that a fluctuation made the most negative 2nd derivative not at the real peak
1435 // check the 2nd derivative at the nominal peak position.
1436 //
1437 if (std::abs(m_samplesDeriv2nd[m_peak2ndDerivMinSample]) > peak2ndDerivMinThresh) {
1439
1440 // Now we re-do the check
1441 //
1446
1447 if ((deltaLeft2 > 0 || deltaLeft > 0) && (deltaRight2 > 0 || deltaRight > 0)) {
1448 m_havePulse = true;
1449 }
1450 }
1451 else {
1452 (*m_msgFunc_p)(ZDCMsg::Info, ("ZDCPulseAnalyzer for " + m_tag + " found a fake maximum at sample " + std::to_string(m_minDeriv2ndIndex) ));
1453 }
1454 }
1455 }
1456
1457 // save the low and high gain ADC values at the peak -- if we have a pulse, at m_minDeriv2ndIndex
1458 // otherwise at m_peak2ndDerivMinSample
1459 //
1460 if (m_havePulse) {
1463 }
1464 else {
1467 }
1468
1469 // Now decide whether we have a preceeding pulse or not. There are two possible kinds of preceeding pulses:
1470 // 1) exponential tail from a preceeding pulse
1471 // 2) peaked pulse before the main pulse
1472 //
1473 // we can, of course, have both
1474 //
1475
1476 // To check for exponential tail, test the slope determined by the minimum ADC value (and pre-sample)
1477 // **beware** this can cause trouble in 2015 data where the pulses had overshoot due to the transformers
1478 //
1479
1480 // Implement a much simpler test for presence of an exponential tail from an OOT pulse
1481 // namely if the slope evaluated at the presample is significantly negative, then
1482 // we have a preceeding pulse.
1483 //
1484 // Note that we don't have to subtract the FADC value corresponding to the presample because
1485 // that subtraction has already been done.
1486 //
1487 if (m_havePulse) {
1488 if (m_doPrePulseCheck) {
1489 // If we've alreday excluded early samples, we have almost by construction have negative exponential tail
1490 //
1491 if (m_ExcludeEarly) m_preExpTail = true;
1492
1493 //
1494 // The subtracted ADC value at m_usedPresampIdx is, by construction, zero
1495 // The next sample has had the pre-sample subtracted, so it represents the initial derivative
1496 //
1497 float derivPresampleErr = std::sqrt(Sqr(m_samplesNoise[m_usedPresampIdx]) + Sqr(m_samplesNoise[m_usedPresampIdx+1]));
1498 float derivPresampleSig = m_samplesSub[m_usedPresampIdx+1]/derivPresampleErr;
1499 if (derivPresampleSig < -5) {
1500 m_preExpTail = true;
1501 m_preExpSig = derivPresampleSig;
1502 }
1503
1504 for (unsigned int isample = m_usedPresampIdx; isample <= m_minDeriv2ndIndex - m_prePulseDelta; isample++) {
1505 if (!useSample[isample]) continue;
1506
1507 float sampleSig = -m_samplesSub[isample]/m_samplesNoise[isample];
1508
1509 // Compare the derivative significant to the 2nd derivative significance,
1510 // so we don't waste time dealing with small perturbations on large signals
1511 //
1512 if ((sampleSig > 5 && sampleSig > 0.02*m_minDeriv2ndSig) || sampleSig > 0.5*m_minDeriv2ndSig) {
1513 m_preExpTail = true;
1514
1515 if (sampleSig > m_preExpSig) m_preExpSig = sampleSig;
1516 }
1517 }
1518
1519 // Now we search for maxima before the main pulse
1520 //
1521 int loopLimit = (m_havePulse ? m_minDeriv2ndIndex - 2 : m_peak2ndDerivMinSample - 2);
1522 int loopStart = m_minSampleEvt == 0 ? 1 : m_minSampleEvt;
1523
1524 float maxPrepulseSig = 0;
1525 unsigned int maxPrepulseSample = 0;
1526
1527 for (int isample = loopStart; isample <= loopLimit; isample++) {
1528 if (!useSample[isample]) continue;
1529
1530 //
1531 // If any of the second derivatives prior to the peak are significantly negative, we have a an extra pulse
1532 // prior to the main one -- as opposed to just an expnential tail
1533 //
1534 double prePulseSig = -m_samplesDeriv2nd[isample]/(1e-6 + m_samplesDeriv2ndErr[isample]);
1535 //
1536 // apply a cut on the significance (as above) but without using division
1537 //
1538 if ((prePulseSig > 6 && m_samplesDeriv2nd[isample] < 0.05 * m_minDeriv2nd) ||
1539 m_samplesDeriv2nd[isample] < 0.5*m_minDeriv2nd)
1540 {
1541 m_prePulse = true;
1542 if (prePulseSig > maxPrepulseSig) {
1543 maxPrepulseSig = prePulseSig;
1544 maxPrepulseSample = isample;
1545 }
1546 }
1547 }
1548
1549 if (m_prePulse) {
1550 m_prePulseSig = maxPrepulseSig;
1551
1552 if (m_preExpTail) {
1553 //
1554 // We have a prepulse. If we already indicated an negative exponential,
1555 // if the prepulse has greater significance, we override the negative exponential
1556 //
1557 if (m_prePulseSig > m_preExpSig) {
1558 m_preExpTail = false;
1559 }
1560 else {
1561 m_prePulse = false;
1562 }
1563 }
1564
1565 m_initialPrePulseAmp = m_samplesSub[maxPrepulseSample];
1566 m_initialPrePulseT0 = m_deltaTSample * (maxPrepulseSample);
1567 }
1568 }
1569
1570 // -----------------------------------------------------
1571 // Post pulse detection
1572 //
1573 if (m_doPostPulseCheck) {
1574 for (int isample = m_minDeriv2ndIndex + m_postPulseDelta; isample < (int) nSamples - 1; isample++) {
1575 if (!useSample[isample] || !useSample[isample +1]) continue;
1576
1577 float deriv = m_samplesSub[isample + 1] - m_samplesSub[isample];
1578 float derivErr = std::sqrt(Sqr(m_samplesNoise[isample + 1]) + Sqr(m_samplesNoise[isample]));
1579
1580 float deriv2nd = m_samplesDeriv2nd[isample];
1581 float deriv2ndErr = m_samplesDeriv2ndErr[isample];
1582
1583 if (deriv > m_postPulseDerivMinSig*derivErr && std::abs(deriv2nd) > m_postPulseAbsDer2ndMinSig*deriv2ndErr) {
1584 m_postPulse = true;
1585
1586 // The place to apply the cut on samples depends on whether we have found a "minimum" or a "maximum"
1587 // The -m_2ndDerivStep for the minimum accounts for the shift between 2nd derivative and the samples
1588 // if we find a maximum we cut one sample lower
1589 //
1590 unsigned int delta = deriv2nd < 0 ? m_2ndDerivStep : m_2ndDerivStep - 1;
1591 m_maxSampleEvt = std::min<int>(isample - delta, m_maxSampleEvt);
1592
1593 std::ostringstream msg;
1594 msg << "ZDCPulseAnalyzer for " << m_tag << " Found a post pulse at sample " << isample
1595 << " deriv = " << deriv << ", derivErr = " << derivErr
1596 << " deriv2nd = " << deriv2nd << ", deriv2ndErr = " << deriv2nd;
1597 (*m_msgFunc_p)(ZDCMsg::Debug, msg.str());
1598 m_adjTimeRangeEvent = true;
1599
1600 break;
1601 }
1602 }
1603 }
1604 }
1605
1606
1607 // -----------------------------------------------------
1608
1609 // Stop now if we have no pulse or we've detected a failure
1610 //
1611 if (m_fail || !m_havePulse) return false;
1612
1613 if (!m_useDelayed) DoFit();
1614 else DoFitCombined();
1615
1616 if (fitFailed()) {
1617 m_fail = true;
1618 }
1619 else {
1620 std::ostringstream ostrm;
1621 // ostrm << "Pulse fit successful with chisquare = " << m_fitChisq;
1622 // (*m_msgFunc_p)(ZDCMsg::Debug, ostrm.str());
1623
1625
1627 //
1628 // We correct relative to the m_timingCorrRefADC using m_timingCorrScale to scale
1629 //
1630 double t0CorrFact = (m_fitAmplitude - m_timingCorrRefADC) / m_timingCorrScale;
1631 if (m_timingCorrMode == TimingCorrLog) t0CorrFact = std::log(t0CorrFact);
1632
1633 // Calculate the correction using a polynomial of power determined by the size of the vector.
1634 // For historical reasons we include here a constant term, though it is degenerate with
1635 // the nominal t0 and/or timing calibrations.
1636 //
1637 float correction = 0;
1638
1639 for (unsigned int ipow = 0; ipow < t0CorrParams.size(); ipow++) {
1640 correction += t0CorrParams[ipow]*std::pow(t0CorrFact, double(ipow));
1641 }
1642
1643 // The correction represents the offset of the timing value from zero so we subtract the result
1644 //
1645 m_fitTimeCorr -= correction;
1646 }
1647
1648 bool failFixedCut = m_fitTimeCorr < minT0Corr || m_fitTimeCorr > maxT0Corr;
1649
1650 // Calculate the timing significance. Note this implementation breaks the model for
1651 // how DoAnalysis is currently used by explicitly testing m_useLowGain, but that model
1652 // needs adjustment anyway ... (one step at a time)
1653 //
1654 // Note that to avoid coupling non-linear corrections to the amplitude and the
1655 // timing significance we evaluate the time resolution using the fit amplitude
1656 //
1657 if (m_timeCutMode != 0) {
1658 double timeResolution = 0;
1659 if (m_useLowGain) timeResolution = m_timeResFuncLG_p->Eval(m_fitAmplitude);
1660 else timeResolution = m_timeResFuncHG_p->Eval(m_fitAmplitude);
1661
1662 m_timeSig = m_fitTimeCorr/timeResolution;
1663 if (std::abs(m_timeSig) > m_t0CutSig) {
1664 //
1665 // We've failed the significance cut. In OR mode (1) we mark the time
1666 // as bad if we've lso failed the fixed cut. In AND mode, we mark it
1667 // as bad regardless of the fixed cut.
1668 //
1669 if (m_timeCutMode == 1) {
1670 if (failFixedCut) m_badT0 = true;
1671 }
1672 else m_badT0 = true;
1673 }
1674 else if (m_timeCutMode == 2 && failFixedCut) m_badT0 = failFixedCut;
1675 }
1676 else {
1677 m_timeSig = -1;
1678 m_badT0 = failFixedCut;
1679 }
1680
1681 // Now check for valid chisq using lambda function
1682 //
1683 // if (m_fitChisq/m_fitNDoF > 2 && m_fitChisq / (m_fitAmplitude + 1.0e-6) > maxChisqDivAmp) m_badChisq = true;
1684 if (!chisqCutLambda(m_fitChisq, m_fitAmplitude, m_fitNDoF, m_chisqRatio)) m_badChisq = true; }
1685
1686 return !m_fitFailed;
1687}
1688
1689void ZDCPulseAnalyzer::prepareLGRefit(const std::vector<float>& samplesLG, const std::vector<float>& samplesNoise,
1690 const std::vector<bool>& useSamples)
1691{
1692 m_samplesLGRefit.clear();
1693 m_samplesNoiseLGRefit.clear();
1694
1695 float presampleLG = m_ADCSamplesLGSub[m_usedPresampIdx];
1696
1697 // Do the presample subtraction
1698 //
1699 for (unsigned int idx = 0; idx < samplesLG.size(); idx++) {
1700 m_samplesLGRefit.push_back(samplesLG[idx] - presampleLG);
1701
1702 if (useSamples[idx]) {
1703 m_samplesNoiseLGRefit.push_back(samplesNoise[idx]);
1704 }
1705 else {
1706 m_samplesNoiseLGRefit.push_back(0);
1707 }
1708 }
1709}
1710
1711
1712void ZDCPulseAnalyzer::DoFit(bool refitLG)
1713{
1714 bool fitLG = m_useLowGain || refitLG;
1715
1716 TH1* hist_p = nullptr;
1717 FillHistogram(refitLG);
1718
1719 // Set the initial values
1720 //
1721 float ampInitial, fitAmpMin, fitAmpMax, t0Initial;
1722
1723 if (fitLG) {
1724 fitAmpMin = m_fitAmpMinLG;
1725 fitAmpMax = m_fitAmpMaxLG;
1726 t0Initial = m_nominalT0LG;
1727 ampInitial = m_ADCPeakLG;
1728 }
1729 else {
1730 ampInitial = m_ADCPeakHG;
1731 fitAmpMin = m_fitAmpMinHG;
1732 fitAmpMax = m_fitAmpMaxHG;
1733 t0Initial = m_nominalT0HG;
1734 }
1735
1736 if (refitLG) {
1737 hist_p = m_fitHistLGRefit.get();
1738 }
1739 else {
1740 hist_p = m_fitHist.get();
1741 }
1742 if (ampInitial < fitAmpMin) ampInitial = fitAmpMin * 1.5;
1743
1744
1745 ZDCFitWrapper* fitWrapper = m_defaultFitWrapper.get();
1746 if (preExpTail()) {
1747 fitWrapper = m_preExpFitWrapper.get();
1748 (static_cast<ZDCPreExpFitWrapper*>(m_preExpFitWrapper.get()))->SetInitialExpPulse(m_initialExpAmp);
1749 }
1750 else if (prePulse()) {
1751 fitWrapper = m_prePulseFitWrapper.get();
1752 (static_cast<ZDCPrePulseFitWrapper*>(fitWrapper))->SetInitialPrePulse(m_initialPrePulseAmp, m_initialPrePulseT0,0,25);
1753 }
1754
1755 if (m_adjTimeRangeEvent) {
1758
1759 float fitTReference = m_deltaTSample * m_usedPresampIdx;
1760
1761 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax, m_fitTMin, m_fitTMax, fitTReference);
1762 }
1763 else {
1764 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax);
1765 }
1766
1767 // Now perform the fit
1768 //
1769 std::string options = m_fitOptions + "Ns";
1770 if (m_quietFits) {
1771 options += "Q";
1772 }
1773
1774 bool fitFailed = false;
1775
1776 // dumpTF1(fitWrapper->GetWrapperTF1RawPtr());
1777 checkTF1Limits(fitWrapper->GetWrapperTF1RawPtr());
1778
1779 //
1780 // Fit the data with the function provided by the fit wrapper
1781 //
1782 TFitResultPtr result_ptr = hist_p->Fit(fitWrapper->GetWrapperTF1RawPtr(), options.c_str(), "", m_fitTMin, m_fitTMax);
1783 int fitStatus = result_ptr;
1784
1785 //
1786 // If the first fit failed, also check the EDM. If sufficiently small, the failure is almost surely due
1787 // to parameter limits and we just accept the result
1788 //
1789 if (fitStatus != 0 && result_ptr->Edm() > 0.001)
1790 {
1791 //
1792 // We contstrain the fit and try again
1793 //
1794 fitWrapper->ConstrainFit();
1795
1796 TFitResultPtr constrFitResult_ptr = hist_p->Fit(fitWrapper->GetWrapperTF1RawPtr(), options.c_str(), "", m_fitTMin, m_fitTMax);
1797 fitWrapper->UnconstrainFit();
1798
1799 if ((int) constrFitResult_ptr != 0) {
1800 //
1801 // Even the constrained fit failed, so we quit.
1802 //
1803 fitFailed = true;
1804 }
1805 else {
1806 // Now we try the fit again with the constraint removed
1807 //
1808 TFitResultPtr unconstrFitResult_ptr = hist_p->Fit(fitWrapper->GetWrapperTF1RawPtr(), options.c_str(), "", m_fitTMin, m_fitTMax);
1809 if ((int) unconstrFitResult_ptr != 0) {
1810 //
1811 // The unconstrained fit failed again, so we redo the constrained fit
1812 //
1813 fitWrapper->ConstrainFit();
1814
1815 TFitResultPtr constrFit2Result_ptr = hist_p->Fit(fitWrapper->GetWrapperTF1RawPtr(), options.c_str(), "", m_fitTMin, m_fitTMax);
1816 if ((int) constrFit2Result_ptr != 0) {
1817 //
1818 // Even the constrained fit failed the second time, so we quit.
1819 //
1820 fitFailed = true;
1821 }
1822
1823 result_ptr = constrFit2Result_ptr;
1824 fitWrapper->UnconstrainFit();
1825 }
1826 else {
1827 result_ptr = unconstrFitResult_ptr;
1828 }
1829 }
1830 }
1831
1832 // At this point we're don varying function parameters
1833 //
1834 fitWrapper->Finalize();
1835
1836 if (!m_fitFailed && m_saveFitFunc) {
1837 hist_p->GetListOfFunctions()->Clear();
1838
1839 TF1* func = fitWrapper->GetWrapperTF1RawPtr();
1840 std::string name = func->GetName();
1841
1842 TF1* copyFunc = static_cast<TF1*>(func->Clone((name + "_copy").c_str()));
1843 hist_p->GetListOfFunctions()->Add(copyFunc);
1844 }
1845
1846 if (!refitLG) {
1848 m_bkgdMaxFraction = fitWrapper->GetBkgdMaxFraction();
1849 m_fitAmplitude = fitWrapper->GetAmplitude();
1850 m_fitAmpError = fitWrapper->GetAmpError();
1851
1852 if (preExpTail()) {
1853 m_fitExpAmp = (static_cast<ZDCPreExpFitWrapper*>(m_preExpFitWrapper.get()))->GetExpAmp();
1854 }
1855 else {
1856 m_fitExpAmp = 0;
1857 }
1858
1859 m_fitTime = fitWrapper->GetTime();
1860 m_fitTimeSub = m_fitTime - t0Initial;
1861
1862 m_fitChisq = result_ptr->Chi2();
1863 m_fitNDoF = result_ptr->Ndf();
1864
1865 m_fitTau1 = fitWrapper->GetTau1();
1866 m_fitTau2 = fitWrapper->GetTau2();
1867
1868 // Here we need to check if the fit amplitude is small (close) enough to fitAmpMin.
1869 // with "< 1+epsilon" where epsilon ~ 1%
1870 if (m_fitAmplitude < fitAmpMin * 1.01) {
1871 m_fitMinAmp = true;
1872 }
1873
1874 if (m_haveSignifCuts) {
1875 float sigMinCut = fitLG ? m_sigMinLG : m_sigMinHG;
1876
1877 if (m_fitAmpError > 1e-6) {
1878 if (m_fitAmplitude/m_fitAmpError < sigMinCut) m_failSigCut = true;
1879 }
1880 }
1881
1882 if (!m_fitFailed) {
1883 unsigned int numPars = fitWrapper->GetNumShapeParameters();
1884 m_shapeParameters.assign(numPars, 0);
1885
1886 for (size_t ipar = 0; ipar < numPars; ipar++) {
1887 m_shapeParameters[ipar] = fitWrapper->GetShapeParameter(ipar);
1888 }
1889 }
1890 }
1891 else {
1892 m_evtLGRefit = true;
1893 m_refitLGFitAmpl = fitWrapper->GetAmplitude();
1894 m_refitLGAmpl = fitWrapper->GetAmplitude() * m_gainFactorLG;
1896 m_refitLGChisq = result_ptr->Chi2();
1897 m_refitLGTime = fitWrapper->GetTime();
1898 m_refitLGTimeSub = m_refitLGTime - t0Initial;
1899 }
1900}
1901
1903{
1904 bool fitLG = refitLG || m_useLowGain;
1905 TH1* hist_p = nullptr, *delayedHist_p = nullptr;
1906
1907 FillHistogram(refitLG);
1908 if (refitLG) {
1909 hist_p = m_fitHistLGRefit.get();
1910 delayedHist_p = m_delayedHistLGRefit.get();
1911 }
1912 else {
1913 hist_p = m_fitHist.get();
1914 delayedHist_p = m_delayedHist.get();
1915 }
1916
1917 float fitAmpMin, fitAmpMax, t0Initial, ampInitial;
1918 if (fitLG) {
1919 ampInitial = m_ADCPeakLG;
1920 fitAmpMin = m_fitAmpMinLG;
1921 fitAmpMax = m_fitAmpMaxLG;
1922 t0Initial = m_nominalT0LG;
1923 }
1924 else {
1925 ampInitial = m_ADCPeakHG;
1926 fitAmpMin = m_fitAmpMinHG;
1927 fitAmpMax = m_fitAmpMaxHG;
1928 t0Initial = m_nominalT0HG;
1929 }
1930
1931 // Set the initial values
1932 //
1933 if (ampInitial < fitAmpMin) ampInitial = fitAmpMin * 1.5;
1934
1935 ZDCFitWrapper* fitWrapper = m_defaultFitWrapper.get();
1936 //if (prePulse()) fitWrapper = fitWrapper = m_preExpFitWrapper.get();
1937
1938 if (preExpTail()) {
1939 fitWrapper = m_preExpFitWrapper.get();
1940 (static_cast<ZDCPreExpFitWrapper*>(m_preExpFitWrapper.get()))->SetInitialExpPulse(m_initialExpAmp);
1941 }
1942 else if (prePulse()) {
1943 fitWrapper = m_prePulseFitWrapper.get();
1944 (static_cast<ZDCPrePulseFitWrapper*>(fitWrapper))->SetInitialPrePulse(m_initialPrePulseAmp, m_initialPrePulseT0,0,25);
1945 }
1946
1947 // Initialize the fit wrapper for this event, specifying a
1948 // per-event fit range if necessary
1949 //
1950 if (m_adjTimeRangeEvent) {
1953
1954 float fitTReference = m_deltaTSample * m_usedPresampIdx;
1955
1956 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax, m_fitTMin, m_fitTMax, fitTReference);
1957 }
1958 else {
1959 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax);
1960 }
1961
1962 // Set up the virtual fitter
1963 //
1964 TFitter* theFitter = nullptr;
1965
1966 if (prePulse()) {
1968
1969 theFitter = m_prePulseCombinedFitter.get();
1970 }
1971 else {
1973
1974 theFitter = m_defaultCombinedFitter.get();
1975 }
1976
1977 // dumpTF1(fitWrapper->GetWrapperTF1RawPtr());
1978
1979 // Set the static pointers to histograms and function for use in FCN
1980 //
1981 s_undelayedFitHist = hist_p;
1982 s_delayedFitHist = delayedHist_p;
1983 s_combinedFitFunc = fitWrapper->GetWrapperTF1RawPtr();
1986
1987
1988 size_t numFitPar = theFitter->GetNumberTotalParameters();
1989
1990 theFitter->GetMinuit()->fISW[4] = -1;
1991
1992 // Now perform the fit
1993 //
1994 if (m_quietFits) {
1995 theFitter->GetMinuit()->fISW[4] = -1;
1996
1997 int ierr= 0;
1998 theFitter->GetMinuit()->mnexcm("SET NOWarnings",nullptr,0,ierr);
1999 }
2000 else theFitter->GetMinuit()->fISW[4] = 0;
2001
2002 // Only include baseline shift in fit for pre-pulses. Otherwise baseline matching should work
2003 //
2004 if (prePulse()) {
2005 theFitter->SetParameter(0, "delayBaselineAdjust", 0, 0.01, -100, 100);
2006 theFitter->ReleaseParameter(0);
2007 }
2008 else {
2009 theFitter->SetParameter(0, "delayBaselineAdjust", 0, 0.01, -100, 100);
2010 theFitter->FixParameter(0);
2011 }
2012
2013
2014 double arglist[100];
2015 arglist[0] = 5000; // number of function calls
2016 arglist[1] = 0.01; // tolerance
2017 int status = theFitter->ExecuteCommand("MIGRAD", arglist, 2);
2018
2019 double fitAmp = theFitter->GetParameter(1);
2020
2021 // Capture the chi-square etc.
2022 //
2023 double chi2, edm, errdef;
2024 int nvpar, nparx;
2025
2026 theFitter->GetStats(chi2, edm, errdef, nvpar, nparx);
2027
2028 // Here we need to check if fitAmp is small (close) enough to fitAmpMin.
2029 // with "< 1+epsilon" where epsilon ~ 1%
2030 //
2031 if (status || fitAmp < fitAmpMin * 1.01 || edm > 0.01){
2032
2033 //
2034 // We first retry the fit with no baseline adjust
2035 //
2036 theFitter->SetParameter(0, "delayBaselineAdjust", 0, 0.01, -100, 100);
2037 theFitter->FixParameter(0);
2038
2039 // Here we need to check if fitAmp is small (close) enough to fitAmpMin.
2040 // with "< 1+epsilon" where epsilon ~ 1%
2041 if (fitAmp < fitAmpMin * 1.01) {
2042 if (m_adjTimeRangeEvent) {
2043 float fitTReference = m_deltaTSample * m_usedPresampIdx;
2044 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax, m_fitTMin, m_fitTMax, fitTReference);
2045 }
2046 else {
2047 fitWrapper->Initialize(ampInitial, t0Initial, fitAmpMin, fitAmpMax);
2048 }
2049 }
2050
2051 status = theFitter->ExecuteCommand("MIGRAD", arglist, 2);
2052 if (status != 0) {
2053 //
2054 // Fit failed event with no baseline adust, so be it
2055 //
2056 theFitter->ReleaseParameter(0);
2057 m_fitFailed = true;
2058 }
2059 else {
2060 //
2061 // The fit succeeded with no baseline adjust, re-fit allowing for baseline adjust using
2062 // the parameters from previous fit as the starting point
2063 //
2064 theFitter->ReleaseParameter(0);
2065 status = theFitter->ExecuteCommand("MIGRAD", arglist, 2);
2066
2067 if (status) {
2068 // Since we know the fit can succeed without the baseline adjust, go back to it
2069 //
2070 theFitter->SetParameter(0, "delayBaselineAdjust", 0, 0.01, -100, 100);
2071 theFitter->FixParameter(0);
2072 status = theFitter->ExecuteCommand("MIGRAD", arglist, 2);
2073 }
2074 }
2075 }
2076 else m_fitFailed = false;
2077
2078 // Check to see if the fit forced the amplitude to the minimum, if so set the corresponding status bit
2079 //
2080 fitAmp = theFitter->GetParameter(1);
2081
2082 // Here we need to check if fitAmp is small (close) enough to fitAmpMin.
2083 // with "< 1+epsilon" where epsilon ~ 1%
2084 if (fitAmp < fitAmpMin * 1.01) {
2085 m_fitMinAmp = true;
2086 }
2087
2088 // Check that the amplitude passes minimum significance requirement
2089 //
2090 float sigMinCut = fitLG ? m_sigMinLG : m_sigMinHG;
2091 if (m_fitAmpError > 1e-6) {
2092 if (m_fitAmplitude/m_fitAmpError < sigMinCut) m_failSigCut = true;
2093 }
2094
2095 if (!m_quietFits) theFitter->GetMinuit()->fISW[4] = -1;
2096
2097 std::vector<double> funcParams(numFitPar - 1);
2098 std::vector<double> funcParamErrs(numFitPar - 1);
2099
2100 // Save the baseline shift between delayed and undelayed samples
2101 //
2102 m_delayedBaselineShift = theFitter->GetParameter(0);
2103
2104 // Capture and store the fit function parameteds and errors
2105 //
2106 for (size_t ipar = 1; ipar < numFitPar; ipar++) {
2107 funcParams[ipar - 1] = theFitter->GetParameter(ipar);
2108 funcParamErrs[ipar - 1] = theFitter->GetParError(ipar);
2109 }
2110
2111 s_combinedFitFunc->SetParameters(&funcParams[0]);
2112 s_combinedFitFunc->SetParErrors(&funcParamErrs[0]);
2113
2114 // Capture the chi-square etc.
2115 //
2116 theFitter->GetStats(chi2, edm, errdef, nvpar, nparx);
2117
2118 int ndf = 2 * m_Nsample - nvpar;
2119
2120 s_combinedFitFunc->SetChisquare(chi2);
2121 s_combinedFitFunc->SetNDF(ndf);
2122
2123 // add to list of functions
2124 if (m_saveFitFunc) {
2125 s_undelayedFitHist->GetListOfFunctions()->Clear();
2126 s_undelayedFitHist->GetListOfFunctions()->Add(s_combinedFitFunc);
2127
2128 s_delayedFitHist->GetListOfFunctions()->Clear();
2129 s_delayedFitHist->GetListOfFunctions()->Add(s_combinedFitFunc);
2130 }
2131
2132 if (!refitLG) {
2133 // Save the pull values from the last call to FCN
2134 //
2135 arglist[0] = 3; // number of function calls
2136 theFitter->ExecuteCommand("Cal1fcn", arglist, 1);
2138
2139 m_fitAmplitude = fitWrapper->GetAmplitude();
2140 m_fitTime = fitWrapper->GetTime();
2141 if (prePulse()) {
2142 m_fitPreT0 = (static_cast<ZDCPrePulseFitWrapper*>(m_prePulseFitWrapper.get()))->GetPreT0();
2143 m_fitPreAmp = (static_cast<ZDCPrePulseFitWrapper*>(m_prePulseFitWrapper.get()))->GetPreAmp();
2144 m_fitPostT0 = (static_cast<ZDCPrePulseFitWrapper*>(m_prePulseFitWrapper.get()))->GetPostT0();
2145 m_fitPostAmp = (static_cast<ZDCPrePulseFitWrapper*>(m_prePulseFitWrapper.get()))->GetPostAmp();
2146 }
2147
2148 if (preExpTail()) {
2149 m_fitExpAmp = (static_cast<ZDCPreExpFitWrapper*>(m_preExpFitWrapper.get()))->GetExpAmp();
2150 }
2151 else {
2152 m_fitExpAmp = 0;
2153 }
2154
2155 m_fitTimeSub = m_fitTime - t0Initial;
2156 m_fitChisq = chi2;
2157 m_fitNDoF = ndf;
2158
2159 m_fitTau1 = fitWrapper->GetTau1();
2160 m_fitTau2 = fitWrapper->GetTau2();
2161
2162 m_fitAmpError = fitWrapper->GetAmpError();
2163 m_bkgdMaxFraction = fitWrapper->GetBkgdMaxFraction();
2164
2165 if (!m_fitFailed) {
2166 unsigned int numPars = fitWrapper->GetNumShapeParameters();
2167 m_shapeParameters.assign(numPars, 0);
2168
2169 for (size_t ipar = 0; ipar < numPars; ipar++) {
2170 m_shapeParameters[ipar] = fitWrapper->GetShapeParameter(ipar);
2171 }
2172 }
2173 }
2174 else {
2175 m_evtLGRefit = true;
2176 m_refitLGFitAmpl = fitWrapper->GetAmplitude();
2177 m_refitLGAmpl = fitWrapper->GetAmplitude() * m_gainFactorLG;
2180 m_refitLGTime = fitWrapper->GetTime();
2181 m_refitLGTimeSub = m_refitLGTime - t0Initial;
2182 }
2183}
2184
2186{
2187 for (int ipar = 0; ipar < func->GetNpar(); ipar++) {
2188 double parLimitLow, parLimitHigh;
2189
2190 func->GetParLimits(ipar, parLimitLow, parLimitHigh);
2191
2192 (*m_msgFunc_p)(ZDCMsg::Debug, (
2193 "ZDCPulseAnalyzer name=" + std::string(func->GetName())
2194 + " ipar=" + std::to_string(ipar)
2195 + " parLimitLow=" + std::to_string(parLimitLow)
2196 + " parLimitHigh="+ std::to_string(parLimitHigh)
2197 )
2198 );
2199
2200 if (std::abs(parLimitHigh - parLimitLow) > (1e-6)*std::abs(parLimitLow)) {
2201 double value = func->GetParameter(ipar);
2202 if (value >= parLimitHigh) {
2203 value = parLimitHigh * 0.9;
2204 }
2205 else if (value <= parLimitLow) {
2206 value = parLimitLow + 0.1*std::abs(parLimitLow);
2207 }
2208 func->SetParameter(ipar, value);
2209 }
2210 }
2211}
2212
2213std::unique_ptr<TFitter> ZDCPulseAnalyzer::MakeCombinedFitter(TF1* func)
2214{
2215 TVirtualFitter::SetDefaultFitter("Minuit");
2216
2217 size_t nFitParams = func->GetNpar() + 1;
2218 std::unique_ptr<TFitter> fitter = std::make_unique<TFitter>(nFitParams);
2219
2220 fitter->GetMinuit()->fISW[4] = -1;
2221 fitter->SetParameter(0, "delayBaselineAdjust", 0, 0.01, -100, 100);
2222
2223 for (size_t ipar = 0; ipar < nFitParams - 1; ipar++) {
2224 double parLimitLow, parLimitHigh;
2225
2226 func->GetParLimits(ipar, parLimitLow, parLimitHigh);
2227 if (std::abs(parLimitHigh - parLimitLow) < (1e-6)*std::abs(parLimitLow)) {
2228 double value = func->GetParameter(ipar);
2229 double lowLim = std::min(value * 0.99, value * 1.01);
2230 double highLim = std::max(value * 0.99, value * 1.01);
2231
2232 fitter->SetParameter(ipar + 1, func->GetParName(ipar), func->GetParameter(ipar), 0.01, lowLim, highLim);
2233 fitter->FixParameter(ipar + 1);
2234 }
2235 else {
2236 double value = func->GetParameter(ipar);
2237 if (value >= parLimitHigh) value = parLimitHigh * 0.99;
2238 else if (value <= parLimitLow) value = parLimitLow * 1.01;
2239
2240 double step = std::min((parLimitHigh - parLimitLow)/100., (value - parLimitLow)/100.);
2241
2242 fitter->SetParameter(ipar + 1, func->GetParName(ipar), value, step, parLimitLow, parLimitHigh);
2243 }
2244 }
2245
2247
2248 return fitter;
2249}
2250
2252{
2253 double parLimitLow, parLimitHigh;
2254
2255 auto func_p = wrapper->GetWrapperTF1();
2256 func_p->GetParLimits(1, parLimitLow, parLimitHigh);
2257
2258 fitter->SetParameter(2, func_p->GetParName(1), func_p->GetParameter(1), 0.01, parLimitLow, parLimitHigh);
2259
2260 if (prePulse) {
2261 unsigned int parIndex = static_cast<ZDCPrePulseFitWrapper*>(wrapper)->GetPreT0ParIndex();
2262
2263 func_p->GetParLimits(parIndex, parLimitLow, parLimitHigh);
2264 fitter->SetParameter(parIndex + 1, func_p->GetParName(parIndex), func_p->GetParameter(parIndex), 0.01, parLimitLow, parLimitHigh);
2265 }
2266}
2267
2269{
2270 (*m_msgFunc_p)(ZDCMsg::Info, ("ZDCPulseAnalyzer dump for tag = " + m_tag));
2271
2272 (*m_msgFunc_p)(ZDCMsg::Info, ("Presample index, value = " + std::to_string(m_preSampleIdx) + ", " + std::to_string(m_preSample)));
2273
2274 if (m_useDelayed) {
2275 (*m_msgFunc_p)(ZDCMsg::Info, ("using delayed samples with delta T = " + std::to_string(m_delayedDeltaT) + ", and pedestalDiff == " +
2276 std::to_string(m_delayedPedestalDiff)));
2277 }
2278
2279 std::ostringstream message1;
2280 message1 << "samplesSub ";
2281 for (size_t sample = 0; sample < m_samplesSub.size(); sample++) {
2282 message1 << ", [" << sample << "] = " << m_samplesSub[sample];
2283 }
2284 (*m_msgFunc_p)(ZDCMsg::Info, message1.str());
2285
2286 std::ostringstream message3;
2287 message3 << "samplesDeriv2nd ";
2288 for (size_t sample = 0; sample < m_samplesDeriv2nd.size(); sample++) {
2289 message3 << ", [" << sample << "] = " << m_samplesDeriv2nd[sample];
2290 }
2291 (*m_msgFunc_p)(ZDCMsg::Info, message3.str());
2292
2293 (*m_msgFunc_p)(ZDCMsg::Info, ("minimum 2nd deriv sample, value = " + std::to_string(m_minDeriv2ndIndex) + ", " + std::to_string(m_minDeriv2nd)));
2294}
2295
2296void ZDCPulseAnalyzer::dumpTF1(const TF1* func) const
2297{
2298 std::string message = "Dump of TF1: " + std::string(func->GetName());
2299 bool continueDump = (*m_msgFunc_p)(ZDCMsg::Verbose, std::move(message));
2300 if (!continueDump) return;
2301
2302 unsigned int npar = func->GetNpar();
2303 for (unsigned int ipar = 0; ipar < npar; ipar++) {
2304 std::ostringstream msgstr;
2305
2306 double parMin = 0, parMax = 0;
2307 func->GetParLimits(ipar, parMin, parMax);
2308
2309 msgstr << "Parameter " << ipar << ", value = " << func->GetParameter(ipar) << ", error = "
2310 << func->GetParError(ipar) << ", min = " << parMin << ", max = " << parMax;
2311 (*m_msgFunc_p)(ZDCMsg::Verbose, msgstr.str());
2312 }
2313}
2314
2316{
2317 std::ostringstream ostrStream;
2318 (*m_msgFunc_p)(ZDCMsg::Info, ("\n ZDCPulserAnalyzer:: ======================================================================="));
2319 (*m_msgFunc_p)(ZDCMsg::Info, ("ZDCPulserAnalyzer:: settings for instance: " + m_tag));
2320
2321 ostrStream << "Nsample = " << m_Nsample << " at frequency " << m_freqMHz << " MHz, preSample index = "
2322 << m_preSampleIdx << ", nominal pedestal = " << m_pedestal;
2323
2324 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2325
2326 ostrStream << "LG mode = " << m_LGMode << ", gainFactor HG = " << m_gainFactorHG << ", gainFactor LG = " << m_gainFactorLG << ", noise sigma HG = " << m_noiseSigHG << ", noiseSigLG = " << m_noiseSigLG;
2327 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2328
2330 ostrStream << "Using per-sample noise sigmas for high gain, values = ";
2331 for (auto sig : m_setPerSampleNoiseHG) {ostrStream << sig << ", ";}
2332 ostrStream << "end";
2333 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2334 }
2335
2337 ostrStream << "Using per-sample noise sigmas for low gain, values = ";
2338 for (auto sig : m_setPerSampleNoiseLG) {ostrStream << sig << ", ";}
2339 ostrStream << "end";
2340 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2341 }
2342
2343 ostrStream << "peak sample = " << m_peak2ndDerivMinSample << ", tolerance = " << m_peak2ndDerivMinTolerance
2344 << ", 2ndDerivThresh HG, LG = " << m_peak2ndDerivMinThreshHG << ", " << m_peak2ndDerivMinThreshLG
2345 << ", 2nd deriv step = " << m_2ndDerivStep;
2346 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2347
2348 if (m_useDelayed) {
2349 ostrStream << "using delayed samples with delta T = " << m_delayedDeltaT << ", and default pedestalDiff == "
2351 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2352 }
2353
2354 ostrStream <<"Fit function = " << m_fitFunction << "fixTau1 = " << m_fixTau1 << ", fixTau2 = " << m_fixTau2
2355 << ", nominalTau1 = " << m_nominalTau1 << ", nominalTau2 = " << m_nominalTau2 << "\n"
2356 << ", nominalT0HG = " << m_nominalT0HG << ", nominalT0LG = " << m_nominalT0LG
2357 << ", t0Cuts HG = [" << m_T0CutLowHG << ", " << m_T0CutHighHG << "], t0Cuts LG = ["
2358 << m_T0CutLowLG << ", " << m_T0CutHighLG << "]";
2359 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2360
2361 ostrStream << "HGOverflowADC = " << m_HGOverflowADC << ", HGUnderflowADC = " << m_HGUnderflowADC
2362 << ", LGOverflowADC = "<< m_LGOverflowADC;
2363 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2364
2365 ostrStream << "chisqDivAmpCutHG = " << m_chisqDivAmpCutHG << ", chisqDivAmpCutLG=" << m_chisqDivAmpCutLG
2366 << ", chisqDivAmpOffsetHG = " << m_chisqDivAmpOffsetHG << ", chisqDivAmpOffsetLG = " << m_chisqDivAmpOffsetLG
2367 << ", chisqDivAmpPowerHG = " << m_chisqDivAmpPowerHG << ", chisqDivAmpPowerLG = " << m_chisqDivAmpPowerLG;
2368 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2369
2370 if ( m_enableRepass) {
2371 ostrStream << "Repass enabled with peak2ndDerivMinRepassHG = " << m_peak2ndDerivMinRepassHG << ", peak2ndDerivMinRepassLG = " << m_peak2ndDerivMinRepassLG;
2372 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2373 }
2374
2375 if (!m_doPrePulseCheck) {
2376 (*m_msgFunc_p)(ZDCMsg::Info, "Pre-pulse and negative exponential pulse checking disabled");
2377 }
2378 if (!m_doPostPulseCheck) {
2379 (*m_msgFunc_p)(ZDCMsg::Info, "Post-pulse checking disabled");
2380 }
2381
2382 if (m_enablePreExcl) {
2383 ostrStream << "Pre-exclusion enabled for up to " << m_maxSamplesPreExcl << ", samples with ADC threshold HG = "
2384 << m_preExclHGADCThresh << ", LG = " << m_preExclLGADCThresh;
2385 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2386 }
2387 if (m_enablePostExcl) {
2388 ostrStream << "Post-exclusion enabled for up to " << m_maxSamplesPostExcl << ", samples with ADC threshold HG = "
2390 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2391 }
2392
2394 ostrStream << "High gain Underflow pre- and post-exclusion enabled for up to " << m_underFlowExclSamplesPreHG
2395 << ", and " << m_underFlowExclSamplesPostHG << " samples, respectively";
2396 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2397 }
2399 ostrStream << "Low gain Underflow pre- and post-exclusion enabled for up to " << m_underFlowExclSamplesPreLG
2400 << ", and " << m_underFlowExclSamplesPostLG << " samples, respectively";
2401 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2402 }
2403 if (m_haveSignifCuts) {
2404 ostrStream << "Minimum significance cuts applied: HG min. sig. = " << m_sigMinHG << ", LG min. sig. " << m_sigMinLG;
2405 (*m_msgFunc_p)(ZDCMsg::Info, ostrStream.str()); ostrStream.str(""); ostrStream.clear();
2406 }
2407
2408}
2409
2411{
2412 unsigned int statusMask = 0;
2413
2414 if (havePulse()) statusMask |= 1 << PulseBit;
2415 if (useLowGain()) statusMask |= 1 << LowGainBit;
2416 if (failed()) statusMask |= 1 << FailBit;
2417 if (HGOverflow()) statusMask |= 1 << HGOverflowBit;
2418
2419 if (HGUnderflow()) statusMask |= 1 << HGUnderflowBit;
2420 if (PSHGOverUnderflow()) statusMask |= 1 << PSHGOverUnderflowBit;
2421 if (LGOverflow()) statusMask |= 1 << LGOverflowBit;
2422 if (LGUnderflow()) statusMask |= 1 << LGUnderflowBit;
2423
2424 if (prePulse()) statusMask |= 1 << PrePulseBit;
2425 if (postPulse()) statusMask |= 1 << PostPulseBit;
2426 if (fitFailed()) statusMask |= 1 << FitFailedBit;
2427 if (badChisq()) statusMask |= 1 << BadChisqBit;
2428
2429 if (badT0()) statusMask |= 1 << BadT0Bit;
2430 if (excludeEarlyLG()) statusMask |= 1 << ExcludeEarlyLGBit;
2431 if (excludeLateLG()) statusMask |= 1 << ExcludeLateLGBit;
2432 if (preExpTail()) statusMask |= 1 << preExpTailBit;
2433 if (fitMinimumAmplitude()) statusMask |= 1 << FitMinAmpBit;
2434 if (repassPulse()) statusMask |= 1 << RepassPulseBit;
2435 if (armSumInclude()) statusMask |= 1 << ArmSumIncludeBit;
2436 if (failSigCut()) statusMask |= 1 << FailSigCutBit;
2437 if (underflowExclusion()) statusMask |= 1<<UnderFlowExclusionBit;
2438
2439 return statusMask;
2440}
2441
2442std::shared_ptr<TGraphErrors> ZDCPulseAnalyzer::GetCombinedGraph(bool LGRefit)
2443{
2444 //
2445 // We defer filling the histogram if we don't have a pulse until the histogram is requested
2446 //
2447 GetHistogramPtr(LGRefit);
2448
2449 TH1* hist_p = nullptr, *delayedHist_p = nullptr;
2450 if (LGRefit) {
2451 hist_p = m_fitHistLGRefit.get();
2452 delayedHist_p = m_delayedHistLGRefit.get();
2453 }
2454 else {
2455 hist_p = m_fitHist.get();
2456 delayedHist_p = m_delayedHist.get();
2457 }
2458
2459 std::shared_ptr<TGraphErrors> theGraph = std::make_shared<TGraphErrors>(TGraphErrors(2 * m_Nsample));
2460 size_t npts = 0;
2461
2462 for (int ipt = 0; ipt < hist_p->GetNbinsX(); ipt++) {
2463 theGraph->SetPoint(npts, hist_p->GetBinCenter(ipt + 1), hist_p->GetBinContent(ipt + 1));
2464 theGraph->SetPointError(npts++, 0, hist_p->GetBinError(ipt + 1));
2465 }
2466
2467 for (int iDelayPt = 0; iDelayPt < delayedHist_p->GetNbinsX(); iDelayPt++) {
2468 theGraph->SetPoint(npts, delayedHist_p->GetBinCenter(iDelayPt + 1), delayedHist_p->GetBinContent(iDelayPt + 1) - m_delayedBaselineShift);
2469 theGraph->SetPointError(npts++, 0, delayedHist_p->GetBinError(iDelayPt + 1));
2470 }
2471 if (m_havePulse) {
2472 TF1* func_p = static_cast<TF1*>(hist_p->GetListOfFunctions()->Last());
2473 if (func_p) {
2474 theGraph->GetListOfFunctions()->Add(new TF1(*func_p));
2475 hist_p->GetListOfFunctions()->SetOwner (false);
2476 }
2477 }
2478 theGraph->SetName(( std::string(hist_p->GetName()) + "combinaed").c_str());
2479
2480 theGraph->SetMarkerStyle(20);
2481 theGraph->SetMarkerColor(1);
2482
2483 return theGraph;
2484}
2485
2486
2487std::shared_ptr<TGraphErrors> ZDCPulseAnalyzer::GetGraph(bool forceLG)
2488{
2489 //
2490 // We defer filling the histogram if we don't have a pulse until the histogram is requested
2491 //
2492 const TH1* hist_p = GetHistogramPtr(forceLG);
2493
2494 std::shared_ptr<TGraphErrors> theGraph = std::make_shared<TGraphErrors>(TGraphErrors(m_Nsample));
2495 size_t npts = 0;
2496
2497 for (int ipt = 0; ipt < hist_p->GetNbinsX(); ipt++) {
2498 theGraph->SetPoint(npts, hist_p->GetBinCenter(ipt + 1), hist_p->GetBinContent(ipt + 1));
2499 theGraph->SetPointError(npts++, 0, hist_p->GetBinError(ipt + 1));
2500 }
2501
2502 TF1* func_p = static_cast<TF1*>(hist_p->GetListOfFunctions()->Last());
2503 theGraph->GetListOfFunctions()->Add(func_p);
2504 theGraph->SetName(( std::string(hist_p->GetName()) + "not_combinaed").c_str());
2505
2506 theGraph->SetMarkerStyle(20);
2507 theGraph->SetMarkerColor(1);
2508
2509 return theGraph;
2510}
2511
2512
2513std::vector<float> ZDCPulseAnalyzer::calculateDerivative(const std::vector <float>& inputData, unsigned int step)
2514{
2515 unsigned int nSamples = inputData.size();
2516
2517 // So we pad at the beginning and end based on step (i.e. with step - 1 zeros). Fill out the fill vector with zeros initially
2518 //
2519 unsigned int vecSize = 2*(step - 1) + nSamples - step - 1;
2520 std::vector<float> results(vecSize, 0);
2521
2522 // Now fill out the values
2523 //
2524 unsigned int fillIdx = step - 1;
2525
2526 for (unsigned int sample = 0; sample < nSamples - step; sample++) {
2527 int deriv = inputData[sample + step] - inputData[sample];
2528 results.at(fillIdx++) = deriv;
2529 }
2530
2531 return results;
2532}
2533
2534std::vector<float> ZDCPulseAnalyzer::calculate2ndDerivative(const std::vector <float>& inputData, unsigned int step)
2535{
2536 unsigned int nSamples = inputData.size();
2537
2538 // We start with two zero entries for which we can't calculate the double-step derivative
2539 // and would pad with two zero entries at the end. Start by initializing
2540 //
2541 unsigned int vecSize = 2*step + nSamples - step - 1;
2542 std::vector<float> results(vecSize, 0);
2543
2544 unsigned int fillIndex = step;
2545 for (unsigned int sample = step; sample < nSamples - step; sample++) {
2546 auto deriv2nd = inputData[sample + step] + inputData[sample - step] - 2*inputData[sample];
2547 results.at(fillIndex++) = deriv2nd;
2548 }
2549
2550 return results;
2551}
2552
2553std::pair<std::vector<float>, std::vector<float>>
2554ZDCPulseAnalyzer::calculate2ndDerivative(const std::vector <float>& inputData, const std::vector <float>& inputNoise, unsigned int step)
2555{
2556 unsigned int nSamples = inputData.size();
2557
2558 // We start with two zero entries for which we can't calculate the double-step derivative
2559 // and would pad with two zero entries at the end. Start by initializing
2560 //
2561 unsigned int vecSize = 2*step + nSamples - step - 1;
2562 std::vector<float> results(vecSize, 0);
2563 std::vector<float> resultsErr(vecSize, 0);
2564
2565 unsigned int fillIndex = step;
2566 for (unsigned int sample = step; sample < nSamples - step; sample++) {
2567 float deriv2nd = inputData[sample + step] + inputData[sample - step] - 2*inputData[sample];
2568 float deriv2ndErr = std::sqrt(Sqr(inputNoise[sample + step]) + Sqr(inputNoise[sample - step]) + 2*Sqr(inputNoise[sample]));
2569
2570 results[fillIndex] = deriv2nd;
2571 resultsErr[fillIndex++] = deriv2ndErr;
2572 }
2573
2574 return {results, resultsErr};
2575}
2576
2577// Implement a more general method for the nasty problem (Runs 1 & 2 only) of matching
2578// the baselines on the delayed and undelayed data. Instead of specifically looking
2579// for early samples or late samples, find the region of the waveform with the smallest
2580// combination of slope and second derivative in both sets of samples and match there.
2581//
2582// Depending on the second derivative
2583// we match using linear or (negative) exponential interpolation
2584//
2585// Note: the samples have already been combined so we distinguish by even or odd index
2586// we use step = 2 for the derivative and 2nd derivative calculation to handle
2587// the delayed and undelayed separately.
2588//
2589//
2590float ZDCPulseAnalyzer::obtainDelayedBaselineCorr(const std::vector<float>& samples)
2591{
2592 const unsigned int nsamples = samples.size();
2593
2594 std::vector<float> derivVec = calculateDerivative(samples, 2);
2595 std::vector<float> deriv2ndVec = calculate2ndDerivative(samples, 2);
2596
2597 // Now step through and check even and odd samples values for 2nd derivative and derivative
2598 // we start with index 2 since the 2nd derivative calculation has 2 initial zeros with nstep = 2
2599 //
2600 float minScore = 1.0e9;
2601 unsigned int minIndex = 0;
2602
2603 for (unsigned int idx = 2; idx < nsamples - 1; idx++) {
2604 float deriv = derivVec[idx];
2605 float prevDeriv = derivVec[idx - 1];
2606
2607 float derivDiff = deriv - prevDeriv;
2608
2609 float deriv2nd = deriv2ndVec[idx];
2610 if (idx > nsamples - 2) deriv2nd = deriv2ndVec[idx - 1];
2611
2612 // Calculate a score based on the actual derivatives (squared) and 2nd derivatives (squared)
2613 // and the slope differences (squared). The relative weights are not adjustable for now
2614 //
2615 float score = (deriv*deriv + 2*derivDiff*derivDiff +
2616 0.5*deriv2nd*deriv2nd);
2617
2618 if (score < minScore) {
2619 minScore = score;
2620 minIndex = idx;
2621 }
2622 }
2623
2624 // We use four samples, two each of "even" and "odd".
2625 // Because of the way the above analysis is done, we can always
2626 // Go back one even and one odd sample and forward one odd sample.
2627 //
2628 //if minIndex is < 2 or >samples.size() the result is undefined; prevent this:
2629 if (minIndex<2 or (minIndex+1) >=nsamples){
2630 throw std::out_of_range("minIndex out of range in ZDCPulseAnalyzer::obtainDelayedBaselineCorr");
2631 }
2632 float sample0 = samples[minIndex - 2];
2633 float sample1 = samples[minIndex - 1];
2634 float sample2 = samples[minIndex];
2635 float sample3 = samples[minIndex + 1];
2636
2637 // Possibility -- implement logarithmic interpolation for large 2nd derivative?
2638 //
2639 float baselineCorr = (0.5 * (sample1 - sample0 + sample3 - sample2) -
2640 0.25 * (sample3 - sample1 + sample2 - sample0));
2641
2642 if (minIndex % 2 != 0) baselineCorr =-baselineCorr;
2643
2644 return baselineCorr;
2645}
2646
2647std::pair<bool, std::string> ZDCPulseAnalyzer::ValidateJSONConfig(const JSON& config)
2648{
2649 bool result = true;
2650 std::string resultString = "success";
2651
2652 for (auto [key, descr] : JSONConfigParams) {
2653 auto iter = config.find(key);
2654 if (iter != config.end()) {
2655 //
2656 // Check type consistency
2657 //
2658 auto jsonType = iter.value().type();
2659 auto paramType = std::get<0>(descr);
2660 if (jsonType != paramType) {
2661 result = false;
2662 resultString = "Bad type for parameter " + key + ", type in JSON = " + std::to_string((unsigned int) jsonType) ;
2663 break;
2664 }
2665
2666 size_t paramSize = std::get<1>(descr);
2667 size_t jsonSize = iter.value().size();
2668 if (jsonSize != paramSize) {
2669 result = false;
2670 resultString = "Bad length for parameter " + key + ", length in JSON = " + std::to_string(jsonSize) ;
2671 break;
2672 }
2673 }
2674 else {
2675 bool required = std::get<2>(descr);
2676 if (required) {
2677 result = false;
2678 resultString = "Missing required parameter " + key;
2679 break;
2680 }
2681 }
2682 }
2683
2684 if (result) {
2685 //
2686 // Now check that the parameters in the JSON object are in the master list of parameters
2687 //
2688 for (auto [key, value] : config.items()) {
2689 //
2690 // Look for this key in the list of allowed parameters. Yes, it's a slow
2691 // search but we only do it once at configuration time.
2692 //
2693 // bool found = false;
2694 auto iter = JSONConfigParams.find(key);
2695 if (iter == JSONConfigParams.end()) {
2696 result = false;
2697 resultString = "Unknown parameter, key = " + key;
2698 break;
2699 }
2700 }
2701 }
2702
2703 return {result, resultString};
2704}
2705
2706std::pair<bool, std::string> ZDCPulseAnalyzer::ConfigFromJSON(const JSON& config)
2707{
2708 bool result = true;
2709 std::string resultString = "success";
2710
2711 for (auto [key, value] : config.items()) {
2712 //
2713 // Big if statement to process configuration parameters
2714 //
2715 if (key == "Nsample") m_Nsample = value;
2716 else if (key == "tag") m_tag = value;
2717 else if (key == "LGMode") m_LGMode = value;
2718 else if (key == "Nsample") m_Nsample = value;
2719 else if (key == "useDelayed") m_useDelayed = value;
2720 else if (key == "preSampleIdx") m_preSampleIdx = value;
2721 else if (key == "FADCFreqMHz") m_freqMHz = value;
2722 else if (key == "nominalPedestal") m_pedestal = value;
2723 else if (key == "fitFunction") m_fitFunction = value;
2724 else if (key == "peakSample") m_peak2ndDerivMinSample = value;
2725 else if (key == "peakTolerance") m_peak2ndDerivMinTolerance = value;
2726 else if (key == "2ndDerivThreshHG") m_peak2ndDerivMinThreshHG = value;
2727 else if (key == "2ndDerivThreshLG") m_peak2ndDerivMinThreshLG = value;
2728 else if (key == "2ndDerivStep") m_2ndDerivStep = value;
2729 else if (key == "HGOverflowADC") m_HGOverflowADC = value;
2730 else if (key == "HGUnderflowADC") m_HGUnderflowADC = value;
2731 else if (key == "LGOverflowADC") m_LGOverflowADC = value;
2732 else if (key == "nominalT0HG") m_nominalT0HG = value;
2733 else if (key == "nominalT0LG") m_nominalT0LG = value;
2734 else if (key == "nominalTau1") m_nominalTau1 = value;
2735 else if (key == "nominalTau2") m_nominalTau2 = value;
2736 else if (key == "fixTau1") m_fixTau1 = value;
2737 else if (key == "fixTau2") m_fixTau2 = value;
2738 else if (key == "T0CutsHG") {
2739 m_T0CutLowHG = value[0];
2740 m_T0CutHighHG = value[1];
2741 }
2742 else if (key == "T0CutsLG") {
2743 m_T0CutLowLG = value[0];
2744 m_T0CutHighLG = value[1];
2745 m_initializedFits = false;
2746 }
2747 else if (key == "chisqDivAmpCutHG") m_chisqDivAmpCutHG = value;
2748 else if (key == "chisqDivAmpCutLG") m_chisqDivAmpCutLG = value;
2749 else if (key == "chisqDivAmpOffsetHG") m_chisqDivAmpOffsetHG = value;
2750 else if (key == "chisqDivAmpScaleLG") m_chisqDivAmpScaleLG = value;
2751 else if (key == "chisqDivAmpScaleHG") m_chisqDivAmpScaleHG = value;
2752 else if (key == "chisqDivAmpOffsetLG") m_chisqDivAmpOffsetLG = value;
2753 else if (key == "chisqDivAmpPowerHG") m_chisqDivAmpPowerHG = value;
2754 else if (key == "chisqDivAmpPowerLG") m_chisqDivAmpPowerLG = value;
2755 else if (key == "gainFactorHG") m_gainFactorHG = value;
2756 else if (key == "gainFactorLG") m_gainFactorLG = value;
2757 else if (key == "noiseSigmaHG") m_noiseSigHG = value;
2758 else if (key == "noiseSigmaLG") m_noiseSigLG = value;
2759 else if (key == "perSampleNoiseSigmaHG") {
2761 value.get_to(m_setPerSampleNoiseHG);
2762 }
2763 else if (key == "perSampleNoiseSigmaLG") {
2765 value.get_to(m_setPerSampleNoiseLG);
2766 // m_setPerSampleNoiseLG.clear();
2767
2768 // for (auto elem : value) {
2769 // m_setPerSampleNoiseLG.push_back(elem);
2770 // }
2771 }
2772 else if (key == "fitTimeMax") {
2773 SetFitTimeMax(static_cast<float>(value));
2774 }
2775 else if (key == "enableRepass") m_enableRepass = value;
2776 else if (key == "Repass2ndDerivThreshHG") m_peak2ndDerivMinRepassHG = value;
2777 else if (key == "Repass2ndDerivThreshLG") m_peak2ndDerivMinRepassLG = value;
2778 else if (key == "fitAmpMinMaxHG") {
2779 m_fitAmpMinHG = value[0];
2780 m_fitAmpMaxHG = value[1];
2781 }
2782 else if (key == "fitAmpMinMaxLG") {
2783 m_fitAmpMinLG = value[0];
2784 m_fitAmpMaxLG = value[1];
2785 }
2786 else if (key == "quietFits") {
2787 m_quietFits = value;
2788 }
2789 else if (key == "enablePreExclusion") {
2790 m_enablePreExcl = true;
2791 m_maxSamplesPreExcl = value[0];
2792 m_preExclHGADCThresh = value[1];
2793 m_preExclLGADCThresh = value[2];
2794 }
2795 else if (key == "enablePostExclusion") {
2796 m_enablePostExcl = true;
2797 m_maxSamplesPostExcl = value[0];
2798 m_postExclHGADCThresh = value[1];
2799 m_postExclLGADCThresh = value[2];
2800 }
2801 else if (key == "enableUnderflowExclusionHG") {
2803 m_underFlowExclSamplesPreHG = value[0];
2805 }
2806 else if (key == "enableUnderflowExclusionLG") {
2808 m_underFlowExclSamplesPreLG = value[0];
2810 }
2811 else if (key == "enablePrePulseDetection") {
2812 m_doPrePulseCheck = value;
2813 }
2814 else if (key == "enablePostPulseDetection") {
2815 m_doPostPulseCheck = value;
2816 }
2817 else if (key == "ampMinSignifHGLG") {
2818 m_haveSignifCuts = true;
2819 m_sigMinHG = value[0];
2820 m_sigMinLG = value[1];
2821 }
2822 else if (key == "enableFADCCorrections") {
2823 auto fileNameJson = value["filename"];
2824 auto doPerSampleCorrJson = value["doPerSampleCorr"];
2825
2826 if (fileNameJson.is_null() || doPerSampleCorrJson.is_null()) {
2827 result = false;
2828 resultString = "failure processing enableFADCCorrections object";
2829 break;
2830 }
2831
2832 m_haveFADCCorrections = true;
2833 m_FADCCorrPerSample = doPerSampleCorrJson;
2834 m_fadcCorrFileName = fileNameJson;
2835 }
2836 else if(key =="useDelayed"){
2837 if(value){
2838 m_useDelayed = true;
2839 }
2840 }
2841 else if (key == "delayDeltaT") m_delayedDeltaT = value;
2842 else if (key == "delayDefaultPedestalShift") m_delayedPedestalDiff = value;
2843 else if (key == "enableTimingCorrection") {
2844 m_timingCorrMode = value[0];
2845 m_timingCorrRefADC = value[1];
2846 m_timingCorrScale = value[2];
2847 }
2848 else if(key == "timeCorrCoeffHG"){
2849 for(int i = 0;auto coeff:value){
2850 m_HGT0CorrParams.at(i) = coeff;
2851 i++;
2852 }
2853 }
2854 else if(key == "timeCorrCoeffLG"){
2855 for(int i = 0;auto coeff:value){
2856 m_LGT0CorrParams.at(i) = coeff;
2857 i++;
2858 }
2859 }
2860 else if(key == "enableNLCorrection"){
2861 m_haveNonlinCorr = true;
2862 m_nonLinCorrRefADC = value[0];
2863 m_nonLinCorrRefScale = value[1];
2864 (*m_msgFunc_p)(
2865 ZDCMsg::Debug, ("Setting non-linear parameters"
2866 ", reference ADC = " + std::to_string(m_nonLinCorrRefADC) +
2867 ", reference scale = " + std::to_string(m_nonLinCorrRefScale)));
2868 }
2869 else if(key == "HGNLCorrCoeffs"){
2870 std::string HGParamsStr = "HG coefficients = ";
2871 for (auto coeff : value) {
2872 m_nonLinCorrParamsHG.push_back(coeff);
2873 HGParamsStr += std::to_string(m_nonLinCorrParamsHG.back()) + " ";
2874 }
2875 (*m_msgFunc_p)(ZDCMsg::Debug, std::move(HGParamsStr));
2876 }
2877 else if(key == "LGNLCorrCoeffs"){
2878 std::string LGParamsStr = "LG coefficients = ";
2879 for(auto coeff:value){
2880 m_nonLinCorrParamsLG.push_back(coeff);
2881 LGParamsStr += std::to_string(m_nonLinCorrParamsLG.back()) + " ";
2882 }
2883 (*m_msgFunc_p)(ZDCMsg::Debug, std::move(LGParamsStr));
2884 }
2885 else {
2886 result = false;
2887 resultString = "unprocessed parameter";
2888 break;
2889 }
2890 }
2891
2892 return {result, resultString};
2893}
T Sqr(T in)
ZDCJSONConfig::JSON JSON
virtual float GetTau1() const =0
void Initialize(float initialAmp, float initialT0, float ampMin, float ampMax)
virtual float GetBkgdMaxFraction() const =0
virtual void ConstrainFit()=0
virtual TF1 * GetWrapperTF1RawPtr() const
virtual std::shared_ptr< TF1 > GetWrapperTF1()
virtual float GetAmpError() const =0
virtual void UnconstrainFit()=0
virtual float GetTime() const =0
virtual float GetShapeParameter(size_t index) const =0
virtual float GetTau2() const =0
virtual void Finalize()
virtual float GetAmplitude() const =0
virtual unsigned int GetNumShapeParameters() const =0
std::map< std::string, JSONParamDescr > JSONParamList
nlohmann::json JSON
std::vector< bool > m_useSampleHG
std::shared_ptr< TGraphErrors > GetCombinedGraph(bool forceLG=false)
static TF1 * s_combinedFitFunc
bool LGOverflow() const
std::unique_ptr< const TF1 > m_timeResFuncHG_p
void dumpTF1(const TF1 *) const
std::unique_ptr< TFitter > m_prePulseCombinedFitter
bool HGOverflow() const
bool underflowExclusion() const
unsigned int m_timeCutMode
std::unique_ptr< const TH1 > m_FADCCorrLG
size_t m_peak2ndDerivMinTolerance
std::vector< float > m_fitPulls
std::vector< float > m_setPerSampleNoiseHG
void dumpConfiguration() const
std::vector< float > m_nonLinCorrParamsLG
bool excludeEarlyLG() const
std::vector< float > m_samplesNoiseLGRefit
static std::vector< float > s_pullValues
std::unique_ptr< const TF1 > m_timeResFuncLG_p
static TH1 * s_undelayedFitHist
std::pair< bool, std::string > ConfigFromJSON(const JSON &config)
static std::vector< float > calculate2ndDerivative(const std::vector< float > &inputData, unsigned int step)
std::unique_ptr< ZDCFitWrapper > m_defaultFitWrapper
std::string m_fitFunction
unsigned int m_preSampleIdx
bool prePulse() const
std::vector< float > m_LGT0CorrParams
std::vector< float > m_samplesLGRefit
std::vector< float > m_samplesNoise
void SetTauT0Values(bool fixTau1, bool fixTau2, float tau1, float tau2, float t0HG, float t0LG)
bool fitMinimumAmplitude() const
const TH1 * GetHistogramPtr(bool refitLG=false)
void SetGainFactorsHGLG(float gainFactorHG, float gainFactorLG)
ZDCJSONConfig::JSON JSON
unsigned int m_preExclLGADCThresh
bool useLowGain() const
unsigned int m_prePulseDelta
void enablePostPulseCheck(unsigned int postPulseSampleDelta, float postPulseDerivMinSig, float postPulseAbsDer2ndMinSig, float minMainDer2ndRatio)
unsigned int m_postExclLGADCThresh
std::vector< float > m_shapeParameters
unsigned int m_NSamplesAna
bool DoAnalysis(bool repass)
void setMinimumSignificance(float sigMinHG, float sigMinLG)
static std::unique_ptr< TFitter > MakeCombinedFitter(TF1 *func)
std::unique_ptr< TH1 > m_delayedHistLGRefit
bool HGUnderflow() const
unsigned int m_timingCorrMode
std::vector< float > m_sampleNoiseLG
unsigned int m_maxSampleEvt
bool fitFailed() const
unsigned int m_maxSamplesPreExcl
std::string m_fitOptions
std::vector< float > m_setPerSampleNoiseLG
std::vector< float > m_ADCSamplesLG
unsigned int m_underFlowExclSamplesPreLG
float m_postPulseMainMinDer2ndRatio
static float s_combinedFitTMin
std::vector< float > m_nonLinCorrParamsHG
ZDCPulseAnalyzer(ZDCMsg::MessageFunctionPtr msgFunc_p, const std::string &tag, int Nsample, float deltaTSample, size_t preSampleIdx, int pedestal, const std::string &fitFunction, int peak2ndDerivMinSample, float peak2DerivMinThreshHG, float peak2DerivMinThreshLG)
unsigned int m_underFlowExclSamplesPostHG
unsigned int m_maxSamplesPostExcl
void SetADCOverUnderflowValues(int HGOverflowADC, int HGUnderflowADC, int LGOverflowADC)
std::vector< float > m_ADCSamplesHGSub
std::unique_ptr< ZDCPrePulseFitWrapper > m_prePulseFitWrapper
void UpdateFitterTimeLimits(TFitter *fitter, ZDCFitWrapper *wrapper, bool prePulse)
static float obtainDelayedBaselineCorr(const std::vector< float > &samples)
void prepareLGRefit(const std::vector< float > &samplesLG, const std::vector< float > &samplesNoise, const std::vector< bool > &useSamples)
double getAmplitudeCorrection(bool highGain)
unsigned int m_underFlowExclSamplesPreHG
void enableRepass(float peak2ndDerivMinRepassHG, float peak2ndDerivMinRepassLG)
void enableDelayed(float deltaT, float pedestalShift, bool fixedBaseline=false)
void DoFit(bool refitLG=false)
std::vector< float > m_ADCSamplesLGSub
bool havePulse() const
std::vector< float > m_samplesSub
void SetFitTimeMax(float tmax)
static float s_combinedFitTMax
std::vector< bool > m_useSampleLG
void reset(bool reanalyze=false)
unsigned int m_underFlowExclSamplesPostLG
unsigned int m_postPulseDelta
std::vector< float > m_ADCSamplesHG
void checkTF1Limits(TF1 *func)
bool AnalyzeData(size_t nSamples, size_t preSample, const std::vector< float > &samples, const std::vector< float > &samplesNoise, const std::vector< bool > &useSamples, float peak2ndDerivMinThresh, const std::vector< float > &toCorrParams, ChisqCutLambdatype chisqCutLambda, float minT0Corr, float maxT0Corr)
void SetTimeCuts(float deltaT0MinHG, float deltaT0MaxHG, float deltaT0MinLG, float deltaT0MaxLG)
unsigned int m_Nsample
std::vector< float > m_sampleNoiseHG
std::vector< float > GetFitPulls(bool forceLG=false) const
bool armSumInclude() const
void SetCutValues(float chisqDivAmpCutHG, float chisqDivAmpCutLG, float deltaT0MinHG, float deltaT0MaxHG, float deltaT0MinLG, float deltaT0MaxLG)
std::vector< float > m_HGT0CorrParams
void SetChisqCuts(float chisqDivAmpCutHG, float chisqDivAmpScaleHG, float chisqDivAmpOffsetHG, float chisqDivAmpPowerHG, float chisqDivAmpCutLG, float chisqDivAmpScaleLG, float chisqDivAmpOffsetLG, float chisqDivAmpPowerLG)
std::unique_ptr< TH1 > m_fitHistLGRefit
std::unique_ptr< TH1 > m_delayedHist
std::shared_ptr< TGraphErrors > GetGraph(bool forceLG=false)
unsigned int m_preExclHGADCThresh
bool failSigCut() const
static TH1 * s_delayedFitHist
std::unique_ptr< TH1 > m_fitHist
bool badChisq() const
std::unique_ptr< TFitter > m_defaultCombinedFitter
unsigned int m_minSampleEvt
bool preExpTail() const
std::vector< float > m_ADCSSampNoiseLG
bool PSHGOverUnderflow() const
unsigned int GetStatusMask() const
bool LoadAndAnalyzeData(const std::vector< float > &ADCSamplesHG, const std::vector< float > &ADCSamplesLG)
void enableTimeSigCut(bool AND, float sigCut, const std::string &TF1String, const std::vector< double > &parsHG, const std::vector< double > &parsLG)
static void CombinedPulsesFCN(int &numParam, double *, double &f, double *par, int flag)
bool postPulse() const
unsigned int m_postExclHGADCThresh
bool repassPulse() const
bool LGUnderflow() const
void SetFitMinMaxAmp(float minAmpHG, float minAmpLG, float maxAmpHG, float maxAmpLG)
static std::vector< float > calculateDerivative(const std::vector< float > &inputData, unsigned int step)
std::unique_ptr< const TH1 > m_FADCCorrHG
std::vector< float > m_samplesDeriv2ndErr
std::unique_ptr< ZDCPreExpFitWrapper > m_preExpFitWrapper
ZDCMsg::MessageFunctionPtr m_msgFunc_p
void FillHistogram(bool refitLG)
std::vector< float >::const_iterator SampleCIter
std::vector< float > m_ADCSSampNoiseHG
static const ZDCJSONConfig::JSONParamList JSONConfigParams
std::vector< float > m_samplesDeriv2nd
std::pair< bool, std::string > ValidateJSONConfig(const JSON &config)
void DoFitCombined(bool refitLG=false)
std::string m_fadcCorrFileName
bool excludeLateLG() const
unsigned int m_LGMode
std::function< bool(float, float, float, float &)> ChisqCutLambdatype
void enableFADCCorrections(bool correctPerSample, std::unique_ptr< const TH1 > &correHistHG, std::unique_ptr< const TH1 > &correHistLG)
const std::string reset
double chi2(TH1 *h0, TH1 *h1)
double xmax
Definition listroot.cxx:61
double xmin
Definition listroot.cxx:60
@ Fatal
Definition ZDCMsg.h:23
@ Debug
Definition ZDCMsg.h:19
@ Verbose
Definition ZDCMsg.h:18
@ Error
Definition ZDCMsg.h:22
@ Info
Definition ZDCMsg.h:20
std::shared_ptr< MessageFunction > MessageFunctionPtr
Definition ZDCMsg.h:14
STL namespace.
MsgStream & msg
Definition testRead.cxx:32
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24