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