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