MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
PulseHistory.cpp
Go to the documentation of this file.
1#include "PulseHistory.hpp"
2#include "Constants.hpp"
3#include "FileSet.hpp"
4#include "IOUtils.hpp"
5#include "PlottingUtils.hpp"
7#include <TCanvas.h>
8#include <TDecompChol.h>
9#include <TF1.h>
10#include <TFile.h>
11#include <TFitResultPtr.h>
12#include <TGraph.h>
13#include <TH1D.h>
14#include <TH2D.h>
15#include <TLegend.h>
16#include <TLine.h>
17#include <TMath.h>
18#include <TMatrixD.h>
19#include <TProfile.h>
20#include <TTree.h>
21#include <TVectorD.h>
22#include <algorithm>
23#include <cmath>
24#include <cstring>
25#include <deque>
26#include <functional>
27#include <iostream>
28#include <map>
29#include <sstream>
30
31namespace PulseHistory {
32
33const char *GroupName(Int_t g) {
34 switch (g) {
35 case kLONG_LEFT:
36 return "long L (odd strips)";
37 case kLONG_RIGHT:
38 return "long R (even strips)";
39 case kSHORT_LEFT:
40 return "short L (even strips)";
41 case kSHORT_RIGHT:
42 return "short R (odd strips)";
43 case kSTRIP0:
44 return "strip 0 (unsegmented)";
45 case kSTRIP17:
46 return "strip 17 (unsegmented)";
47 case kGRID:
48 return "grid (seed channel)";
49 }
50 return "none";
51}
52
53const char *GroupTag(Int_t g) {
54 switch (g) {
55 case kLONG_LEFT:
56 return "L";
57 case kLONG_RIGHT:
58 return "R";
59 case kSHORT_LEFT:
60 return "Ls";
61 case kSHORT_RIGHT:
62 return "Rs";
63 case kSTRIP0:
64 return "S0";
65 case kSTRIP17:
66 return "S17";
67 case kGRID:
68 return "G";
69 }
70 return "none";
71}
72
73Int_t ChainOf(Int_t g) {
74 switch (g) {
75 case kLONG_LEFT:
76 case kSHORT_LEFT:
77 return 0;
78 case kLONG_RIGHT:
79 case kSHORT_RIGHT:
80 return 1;
81 }
82 return -1;
83}
84
85Bool_t IsLongGroup(Int_t g) { return g == kLONG_LEFT || g == kLONG_RIGHT; }
86
87namespace {
88const PulseHistoryGroupOption *GroupOption(Int_t g) {
90 switch (g) {
91 case kLONG_LEFT:
92 return &m.long_left;
93 case kLONG_RIGHT:
94 return &m.long_right;
95 case kSHORT_LEFT:
96 return &m.short_left;
97 case kSHORT_RIGHT:
98 return &m.short_right;
99 case kSTRIP0:
100 return &m.strip0;
101 case kSTRIP17:
102 return &m.strip17;
103 case kGRID:
104 return &m.grid;
105 default:
106 return nullptr;
107 }
108}
109} // namespace
110
111Bool_t GroupEnabled(Int_t g) {
112 const PulseHistoryGroupOption *o = GroupOption(g);
113 return o ? o->enabled : kFALSE;
114}
115
116Bool_t GroupWantsForm(Int_t g) {
117 const PulseHistoryGroupOption *o = GroupOption(g);
118 return o ? (o->enabled && o->kernel == kANALYTIC) : kFALSE;
119}
120
121Double_t GroupTauUs(Int_t g) {
122 const PulseHistoryGroupOption *o = GroupOption(g);
123 return (o && o->tau_us > 0.0) ? o->tau_us : 0.0;
124}
125
127 for (Int_t a = 0; a < kMAX_AMP_BINS; a++) {
128 c[a] = 0.0;
129 cb[a] = 0.0;
130 for (Int_t b = 0; b < kNBins; b++) {
131 k[a][b] = 0.0;
132 k_binned[a][b] = 0.0;
133 k_low[a][b] = 0.0;
134 }
135 }
136}
137
138Double_t Kernel::FormAt(Int_t a, Double_t dt_us) const {
139 if (a < 0 || a >= kMAX_AMP_BINS || !(tau_us > 0.0))
140 return 0.0;
141 if (dt_us < t_lo_us) {
142 // The previous trapezoid is still under the read point: free bins.
143 const Int_t b = BinOf(dt_us * 1.0e-6);
144 return (b >= 0 && b < n_free) ? k_low[a][b] : 0.0;
145 }
146 const Double_t ex = TMath::Exp(-dt_us / tau_us);
147 Double_t v = -c[a] * ex * TMath::Exp(-t_m_us / tau_us);
148 if (dt_us > t_f_us)
149 v += cb[a] * (TMath::Exp(-t_f_us / tau_us) - ex);
150 return v;
151}
152
153Double_t Kernel::EffectiveWindowUs(Int_t a) const {
154 if (a < 0 || a >= kMAX_AMP_BINS || cb[a] == 0.0)
155 return 0.0;
156 return c[a] * tau_us / cb[a];
157}
158
160 for (Int_t g = 0; g < kNGROUPS; g++) {
161 enabled[g] = kTRUE;
162 mean_shift[g] = 0.0;
163 n_clamped_group[g] = 0;
164 dev_vs_pred[g] = nullptr;
165 dev_before[g] = dev_after[g] = shift[g] = nullptr;
166 dtprev_before[g] = dtprev_after[g] = nullptr;
167 }
168}
169
171
173 for (Int_t g = 0; g < kNGROUPS; g++) {
174 delete dev_vs_pred[g];
175 delete dev_before[g];
176 delete dev_after[g];
177 delete shift[g];
178 delete dtprev_before[g];
179 delete dtprev_after[g];
180 dev_vs_pred[g] = nullptr;
181 dev_before[g] = dev_after[g] = shift[g] = nullptr;
182 dtprev_before[g] = dtprev_after[g] = nullptr;
183 }
184 for (size_t c = 0; c < dtprev_before_ch.size(); c++)
185 delete dtprev_before_ch[c];
186 for (size_t c = 0; c < dtprev_after_ch.size(); c++)
187 delete dtprev_after_ch[c];
188 dtprev_before_ch.clear();
189 dtprev_after_ch.clear();
190}
191
192Int_t BinOf(Double_t dt_s) {
193 if (!(dt_s > 0.0))
194 return -1;
195 const Double_t l = TMath::Log10(dt_s);
196 if (l < kLogLo || l >= kLogHi)
197 return -1;
198 return Int_t((l - kLogLo) / (kLogHi - kLogLo) * kNBins);
199}
200
201Double_t BinCentreUs(Int_t b) {
202 return TMath::Power(10.0, kLogLo + (b + 0.5) * (kLogHi - kLogLo) / kNBins) *
203 1.0e6;
204}
205
211void FitTauDecay(const std::vector<Double_t> &dt_us,
212 const std::vector<Double_t> &dev, Kernel::TauFit &out) {
213 out = Kernel::TauFit{};
214 const Int_t n = Int_t(dt_us.size());
215 if (n < kTauFitMinEntries)
216 return;
217 // Binned into a TProfile: its bin errors weight populated early bins, where
218 // the tail lives, against the sparse long tail, ROOT skips empty bins.
219 const Int_t nb = 60;
220 TProfile *prof = new TProfile("ph_tau_fit", "", nb, kTauFitLoUs, kTauFitHiUs);
221 for (Int_t i = 0; i < n; i++)
222 prof->Fill(dt_us[i], dev[i]);
223 TF1 *f = new TF1("ph_tau_fit_fn", "[0] + [1] * exp(-x / [2]) + [3] * x",
225 const Double_t head = prof->GetBinContent(prof->FindBin(kTauFitLoUs + 1.0));
226 const Double_t tail = prof->GetBinContent(prof->FindBin(kTauFitHiUs - 5.0));
227 f->SetParameters(tail, head - tail, 10.0, 0.0);
228 f->SetParLimits(2, 0.5, 200.0);
229 TFitResultPtr r = prof->Fit(f, "RQS0");
230 if (Int_t(r) == 0) {
231 out.ok = kTRUE;
232 out.p0 = f->GetParameter(0);
233 out.p1 = f->GetParameter(1);
234 out.p1err = f->GetParError(1);
235 out.p2 = f->GetParameter(2);
236 out.p2err = f->GetParError(2);
237 out.p3 = f->GetParameter(3);
238 out.chi2 = f->GetChisquare();
239 out.ndf = f->GetNDF();
240 out.flat = TMath::Abs(out.p1) < 2.0 * out.p1err || out.p2 <= 0.51 ||
241 out.p2 >= 199.0;
242 }
243 // "S" gives the histogram ownership of the fit function; mark it
244 // referenced so deleting the profile does not delete it under us.
245 f->SetBit(TF1::kNotDraw);
246 delete f;
247 delete prof;
248}
249
250Int_t AmpBinOf(Double_t e_prev, Double_t mode, Int_t n_amp) {
251 if (n_amp <= 1 || !(mode > 0.0))
252 return 0;
253 // Bands centred on multiples of the beam pulse: [0, 0.5), [0.5, 1.5), ...
254 const Int_t a = Int_t(e_prev / mode + 0.5);
255 return TMath::Min(a, n_amp - 1);
256}
257
258// What a band covers, in the beam pulse's units: the first is everything
259// below half a beam pulse, the last is open ended.
260TString BandLabel(Int_t a, Int_t n_amp, Bool_t latex) {
261 if (n_amp <= 1)
262 return "";
263 const char *times = latex ? "#times" : "x";
264 if (a == 0)
265 return Form("< 0.5%s beam", times);
266 if (a == n_amp - 1)
267 return Form("%s %.1f%s beam", latex ? "#geq" : ">=", a - 0.5, times);
268 return Form("~%d%s beam", a, times);
269}
270
271std::vector<Int_t> BuildGroupMap() {
272 const Int_t nch = Constants::ActiveNChannels();
273 std::vector<Int_t> gm(Constants::ActiveNBoards() * nch, kNONE);
274 const std::map<std::pair<Int_t, Int_t>, TString> &cm =
276 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
277 cm.begin();
278 it != cm.end(); ++it) {
279 const TString &name = it->second;
280 const Int_t idx = it->first.first * nch + it->first.second;
281 if (idx < 0 || idx >= Int_t(gm.size()))
282 continue;
283 if (name == "Strip0") {
284 gm[idx] = kSTRIP0;
285 continue;
286 }
287 if (name == "Strip17") {
288 gm[idx] = kSTRIP17;
289 continue;
290 }
291 if (name == "Grid") {
292 gm[idx] = kGRID;
293 continue;
294 }
295 if (name.Length() < 2 || (name[0] != 'L' && name[0] != 'R'))
296 continue;
297 TString num = name(1, name.Length() - 1);
298 if (!num.IsDigit())
299 continue;
300 const Int_t s = num.Atoi();
301 if (s < 1 || s > 16)
302 continue;
303 const Bool_t odd = (s % 2) != 0;
304 const Bool_t is_long = (name[0] == 'L') ? odd : !odd;
305 if (name[0] == 'L')
306 gm[idx] = is_long ? kLONG_LEFT : kSHORT_LEFT;
307 else
308 gm[idx] = is_long ? kLONG_RIGHT : kSHORT_RIGHT;
309 }
310 return gm;
311}
312
313namespace {
314
315// The channel map's name for a (board, channel) index; empty if unmapped.
316TString NameOfIndex(Int_t idx) {
317 const Int_t nch = Constants::ActiveNChannels();
318 const std::map<std::pair<Int_t, Int_t>, TString> &cm =
320 std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
321 cm.find(std::make_pair(idx / nch, idx % nch));
322 return it == cm.end() ? TString("") : it->second;
323}
324
325Int_t IndexOfName(const TString &want) {
326 const Int_t nch = Constants::ActiveNChannels();
327 const std::map<std::pair<Int_t, Int_t>, TString> &cm =
329 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
330 cm.begin();
331 it != cm.end(); ++it)
332 if (it->second == want)
333 return it->first.first * nch + it->first.second;
334 return -1;
335}
336
337inline Int_t HitIndex(const RawHit &h) {
338 return Int_t(h.board) * Constants::ActiveNChannels() + Int_t(h.channel);
339}
340
341struct Past {
342 Double_t t; // ps
343 Double_t e; // raw ADC
344};
345
346// Feature layout: intercept, the binned kernel's (band, dt bin), and when
347// fitted the form's free low bins and per-tau direct-tail/baseline pairs.
348struct FeatLayout {
349 Int_t n_amp = 1;
350 Int_t n_free = 0;
351 Int_t n_tau = 0;
352 Int_t Bin(Int_t a, Int_t b) const { return 1 + a * kNBins + b; }
353 Int_t Low(Int_t a, Int_t b) const {
354 return 1 + n_amp * kNBins + a * n_free + b;
355 }
356 Int_t D(Int_t j, Int_t a) const {
357 return 1 + n_amp * kNBins + n_amp * n_free + 2 * (j * n_amp + a);
358 }
359 Int_t B(Int_t j, Int_t a) const { return D(j, a) + 1; }
360 Int_t Size() const {
361 return 1 + n_amp * kNBins + n_amp * n_free + 2 * n_tau * n_amp;
362 }
363 Bool_t HasForm() const { return n_tau > 0; }
364};
365
366// The tau grid of the form and the timings from the trapezoid settings, with
367// the two exponentials that do not depend on the gap precomputed per tau.
368struct FormGrid {
369 Double_t t_m_us = 0.0, t_f_us = 0.0, t_lo_us = 0.0;
370 std::vector<Double_t> tau_us, em, ef; // tau, exp(-t_m/tau), exp(-t_f/tau)
371 // exp(-dt/tau_j) tabulated against log10(dt) and interpolated: one table
372 // read per tau instead of an exp(); 4096 points put the error below 1e-5.
373 static const Int_t kTabN = 4096;
374 std::vector<Double_t> tab; // [j * kTabN + i]
375 Double_t tab_lo = 0.0, tab_step = 0.0;
376 void Tabulate() {
377 tab_lo = kLogLo + 6.0;
378 tab_step = (kLogHi - kLogLo) / Double_t(kTabN - 1);
379 tab.assign(tau_us.size() * kTabN, 0.0);
380 for (size_t j = 0; j < tau_us.size(); j++)
381 for (Int_t i = 0; i < kTabN; i++)
382 tab[j * kTabN + i] =
383 TMath::Exp(-TMath::Power(10.0, tab_lo + i * tab_step) / tau_us[j]);
384 }
385 // exp(-dt/tau_j) at log10(dt [us]) = l.
386 inline Double_t Exp(Int_t j, Double_t l) const {
387 Double_t u = (l - tab_lo) / tab_step;
388 if (u <= 0.0)
389 return tab[j * kTabN];
390 if (u >= kTabN - 1)
391 return tab[j * kTabN + kTabN - 1];
392 const Int_t i = Int_t(u);
393 const Double_t f = u - i;
394 return tab[j * kTabN + i] * (1.0 - f) + tab[j * kTabN + i + 1] * f;
395 }
396};
397
398// Which features a scan has to build. The fit needs every one; the
399// diagnostics only the applied model's, and the tau summary none.
400struct ScanNeeds {
401 Bool_t bins = kTRUE;
402 Bool_t low = kTRUE;
403 std::vector<Int_t> taus; // grid indices whose form features are built
404};
405
406// One accumulated least-squares problem; only the normal-matrix blocks a
407// fit reads are accumulated (upper triangle, no bin/form or tau/tau terms).
408struct Normal {
409 Int_t np = 0;
410 std::vector<Double_t> A; // np x np, upper triangle of the read blocks
411 std::vector<Double_t> b;
412 Double_t syy = 0.0, sy = 0.0;
413 Long64_t n = 0;
414 void Init(Int_t np_) {
415 np = np_;
416 A.assign(size_t(np) * np, 0.0);
417 b.assign(np, 0.0);
418 syy = sy = 0.0;
419 n = 0;
420 }
421 void Add(const Normal &o) {
422 for (size_t i = 0; i < A.size(); i++)
423 A[i] += o.A[i];
424 for (size_t i = 0; i < b.size(); i++)
425 b[i] += o.b[i];
426 syy += o.syy;
427 sy += o.sy;
428 n += o.n;
429 }
430 inline Double_t &At(Int_t a, Int_t c) { return A[size_t(a) * np + c]; }
431 inline Double_t Get(Int_t a, Int_t c) const {
432 return a <= c ? A[size_t(a) * np + c] : A[size_t(c) * np + a];
433 }
434};
435
436// Predicted shift from a channel's past, with the group's applied kernel.
437Double_t Shift(const std::deque<Past> &d, Double_t t, const Kernel &K,
438 Double_t mode) {
439 Double_t s = 0.0;
440 for (size_t k = 0; k < d.size(); k++) {
441 const Past &p = d[k];
442 const Double_t dt_s = (t - p.t) * 1.0e-12;
443 const Int_t b = BinOf(dt_s);
444 if (b < 0)
445 continue;
446 const Int_t a = AmpBinOf(p.e, mode, K.n_amp);
447 s += (K.form ? K.FormAt(a, dt_s * 1.0e6) : K.k[a][b]) * p.e;
448 }
449 return s;
450}
451
452// The applied model's prediction from one feature vector (the diagnostics).
453Double_t Predict(const Kernel &K, const FeatLayout &lay,
454 const std::vector<Double_t> &x) {
455 Double_t pred = K.intercept;
456 for (Int_t a = 0; a < K.n_amp; a++) {
457 if (K.form) {
458 for (Int_t b = 0; b < lay.n_free; b++)
459 pred += K.k_low[a][b] * x[lay.Low(a, b)];
460 if (K.j_tau >= 0)
461 pred += -K.c[a] * x[lay.D(K.j_tau, a)] + K.cb[a] * x[lay.B(K.j_tau, a)];
462 } else {
463 for (Int_t b = 0; b < kNBins; b++)
464 pred += K.k_binned[a][b] * x[lay.Bin(a, b)];
465 }
466 }
467 return pred;
468}
469
470// Least squares on a feature subset: unit diagonal, kRidge ridge, Cholesky
471// (SVD stalled on sparse CoMPASS); `drop` gives ss_res = syy - drop.
472Bool_t SolveSubset(const Normal &N, const std::vector<Int_t> &keep, TVectorD &p,
473 Double_t &drop) {
474 const Double_t kRidge = 1.0e-8;
475 const Int_t nk = Int_t(keep.size());
476 if (nk < 1)
477 return kFALSE;
478 TMatrixD Ak(nk, nk);
479 TVectorD bk(nk);
480 for (Int_t a = 0; a < nk; a++) {
481 bk[a] = N.b[keep[a]];
482 for (Int_t b = 0; b < nk; b++)
483 Ak(a, b) = N.Get(keep[a], keep[b]);
484 }
485 TVectorD scale(nk);
486 for (Int_t a = 0; a < nk; a++)
487 scale[a] = Ak(a, a) > 0.0 ? 1.0 / TMath::Sqrt(Ak(a, a)) : 1.0;
488 TMatrixD As(nk, nk);
489 TVectorD bs(nk);
490 for (Int_t a = 0; a < nk; a++) {
491 bs[a] = bk[a] * scale[a];
492 for (Int_t b = 0; b < nk; b++)
493 As(a, b) = Ak(a, b) * scale[a] * scale[b];
494 As(a, a) += kRidge;
495 }
496 TDecompChol chol(As);
497 Bool_t solved = chol.Decompose();
498 if (!solved)
499 return kFALSE;
500 TVectorD ps = chol.Solve(bs, solved);
501 if (!solved)
502 return kFALSE;
503 p.ResizeTo(nk);
504 drop = 0.0;
505 for (Int_t a = 0; a < nk; a++) {
506 p[a] = ps[a] * scale[a];
507 drop += p[a] * bk[a];
508 }
509 return std::isfinite(drop);
510}
511
512// The hit window and channel maps shared by the seed pass and the scan: the
513// per-channel maxima of the current event window, refilled at every seed.
514struct ScanState {
515 const std::vector<RawHit> &hits;
516 const std::vector<Int_t> &group_of;
517 const std::vector<Int_t> &fit_ch;
518 const std::vector<Int_t> &long_ch;
519 const std::vector<Double_t> &mode;
520 std::vector<Double_t> &ev_e;
521 const std::vector<Double_t> &mean;
522 Int_t nidx;
523 Int_t strip0;
524 Double_t window_ps;
525 Double_t olo;
526 Double_t ohi;
527 Double_t blo;
528 Double_t bhi;
529 Int_t n_amp;
530 Double_t keep_ps;
531 const FeatLayout &lay;
532 const FormGrid &grid;
533};
534
535// Max hit energy per channel within the event window from seed i0; false if
536// strip 0 did not fire.
537Bool_t Lookahead(const ScanState &st, size_t i0) {
538 for (Int_t k = 0; k < Int_t(st.fit_ch.size()); k++)
539 st.ev_e[st.fit_ch[k]] = 0.0;
540 Bool_t strip0_fired = st.strip0 < 0;
541 const ULong64_t t0 = st.hits[i0].timestamp;
542 // The seed's own hit is the grid's height for this event, when the grid
543 // is a fitted channel; the window below starts after it.
544 {
545 const Int_t i = HitIndex(st.hits[i0]);
546 if (i >= 0 && i < st.nidx && st.group_of[i] != kNONE)
547 st.ev_e[i] = Double_t(st.hits[i0].energy);
548 }
549 for (size_t j = i0 + 1;
550 j < st.hits.size() && st.hits[j].timestamp - t0 < st.window_ps; j++) {
551 const Int_t i = HitIndex(st.hits[j]);
552 if (i == st.strip0 && st.hits[j].energy > 0)
553 strip0_fired = kTRUE;
554 if (i < 0 || i >= st.nidx || st.group_of[i] == kNONE)
555 continue;
556 if (Double_t(st.hits[j].energy) > st.ev_e[i])
557 st.ev_e[i] = Double_t(st.hits[j].energy);
558 }
559 return strip0_fired;
560}
561
562// Beam-like for group g: every long end inside its window around its mode,
563// g's own chain loose, the other chain and strips 0/17 tight.
564Bool_t BeamFor(const ScanState &st, Int_t g) {
565 const Int_t chain = ChainOf(g);
566 for (Int_t k = 0; k < Int_t(st.long_ch.size()); k++) {
567 const Int_t c = st.long_ch[k];
568 if (!(st.mode[c] > 0.0))
569 return kFALSE;
570 const Double_t r = st.ev_e[c] / st.mode[c];
571 const Bool_t own = chain >= 0 && ChainOf(st.group_of[c]) == chain;
572 if (own ? (r < st.olo || r > st.ohi) : (r < st.blo || r > st.bhi))
573 return kFALSE;
574 }
575 return kTRUE;
576}
577
582void Scan(const ScanState &st, Int_t g, const std::vector<size_t> &seeds_g,
583 const ScanNeeds &needs, std::vector<Double_t> &x,
584 const std::function<void(Int_t, const std::vector<Double_t> &,
585 Double_t, Double_t)> &fn) {
586 const Bool_t any_form =
587 st.lay.HasForm() && (needs.low || !needs.taus.empty());
588 std::vector<std::deque<Past>> past(st.nidx);
589 size_t next_seed = 0;
590 for (size_t j = 0; j < st.hits.size(); j++) {
591 if (next_seed < seeds_g.size() && seeds_g[next_seed] == j) {
592 next_seed++;
593 const Double_t tg = Double_t(st.hits[j].timestamp);
594 Lookahead(st, j);
595 for (Int_t k = 0; k < Int_t(st.fit_ch.size()); k++) {
596 const Int_t c = st.fit_ch[k];
597 // A short end or unsegmented strip that did not fire has no height.
598 if (st.group_of[c] != g || !(st.ev_e[c] > 0.0))
599 continue;
600 std::deque<Past> &d = past[c];
601 while (!d.empty() && d.front().t < tg - st.keep_ps)
602 d.pop_front();
603 std::fill(x.begin(), x.end(), 0.0);
604 x[0] = 1.0;
605 for (size_t p = 0; p < d.size(); p++) {
606 const Double_t dt_s = (tg - d[p].t) * 1.0e-12;
607 if (!(dt_s > 0.0))
608 continue;
609 // One log per past pulse serves the bin and the exp table.
610 const Double_t l_s = TMath::Log10(dt_s);
611 if (l_s < kLogLo || l_s >= kLogHi)
612 continue; // outside the kernel reach
613 const Int_t b = Int_t((l_s - kLogLo) / (kLogHi - kLogLo) * kNBins);
614 const Int_t a = AmpBinOf(d[p].e, st.mode[c], st.n_amp);
615 const Double_t e = d[p].e;
616 if (needs.bins)
617 x[st.lay.Bin(a, b)] += e;
618 if (!any_form)
619 continue;
620 const Double_t dt_us = dt_s * 1.0e6;
621 if (dt_us < st.grid.t_lo_us) {
622 if (needs.low && b < st.lay.n_free)
623 x[st.lay.Low(a, b)] += e;
624 continue;
625 }
626 if (needs.taus.empty())
627 continue;
628 const Double_t l = l_s + 6.0;
629 const Bool_t past_freeze = dt_us > st.grid.t_f_us;
630 for (size_t q = 0; q < needs.taus.size(); q++) {
631 const Int_t j = needs.taus[q];
632 const Double_t ex = st.grid.Exp(j, l);
633 x[st.lay.D(j, a)] += e * ex * st.grid.em[j];
634 if (past_freeze)
635 x[st.lay.B(j, a)] += e * (st.grid.ef[j] - ex);
636 }
637 }
638 const Double_t dt_prev = d.empty() ? -1.0 : (tg - d.back().t) * 1.0e-12;
639 fn(c, x, st.ev_e[c] - st.mean[c], dt_prev);
640 }
641 }
642 const Int_t i = HitIndex(st.hits[j]);
643 if (i >= 0 && i < st.nidx && st.group_of[i] == g)
644 past[i].push_back(
645 {Double_t(st.hits[j].timestamp), Double_t(st.hits[j].energy)});
646 }
647}
648
649// Fits one kernel from an accumulated problem: the binned model and, when
650// the layout carries the form, the form at every grid tau and `given_j`.
651
652// R^2 that a kernel fitted elsewhere (the group's) reaches on this problem,
653// so a channel's own fit can be judged against the fallback.
654Double_t R2Of(const Kernel &K, const Normal &N, const FeatLayout &lay) {
655 if (N.n < 1)
656 return 0.0;
657 const Double_t ybar = N.sy / N.n;
658 const Double_t ss_tot = N.syy - N.n * ybar * ybar;
659 if (!(ss_tot > 0.0))
660 return 0.0;
661 TVectorD p(lay.Size());
662 p.Zero();
663 p[0] = K.intercept;
664 for (Int_t a = 0; a < K.n_amp; a++) {
665 if (K.form) {
666 for (Int_t b = 0; b < lay.n_free; b++)
667 p[lay.Low(a, b)] = K.k_low[a][b];
668 if (K.j_tau >= 0) {
669 p[lay.D(K.j_tau, a)] = -K.c[a];
670 p[lay.B(K.j_tau, a)] = K.cb[a];
671 }
672 } else {
673 for (Int_t b = 0; b < kNBins; b++)
674 p[lay.Bin(a, b)] = K.k_binned[a][b];
675 }
676 }
677 // Only the applied model's features are nonzero in p, and only their
678 // mutual blocks were accumulated, so sum over those.
679 std::vector<Int_t> nz;
680 for (Int_t i = 0; i < lay.Size(); i++)
681 if (p[i] != 0.0)
682 nz.push_back(i);
683 Double_t ss_res = N.syy;
684 for (size_t u = 0; u < nz.size(); u++) {
685 ss_res -= 2.0 * p[nz[u]] * N.b[nz[u]];
686 for (size_t v = 0; v < nz.size(); v++)
687 ss_res += p[nz[u]] * p[nz[v]] * N.Get(nz[u], nz[v]);
688 }
689 return 1.0 - ss_res / ss_tot;
690}
691
692Bool_t FitKernel(const Normal &N, const FeatLayout &lay, const FormGrid &grid,
693 Int_t given_j, Bool_t want_form, Kernel &K, TString &why) {
694 why = "";
695 K.n = N.n;
696 K.n_amp = lay.n_amp;
697 K.n_free = lay.n_free;
698 K.t_m_us = grid.t_m_us;
699 K.t_f_us = grid.t_f_us;
700 K.t_lo_us = grid.t_lo_us;
701 K.ok = kFALSE;
702 K.j_tau = -1;
703 if (N.n < 100) {
704 why = Form("only %lld pairs", N.n);
705 return kFALSE;
706 }
707 const Double_t ybar = N.sy / N.n;
708 const Double_t ss_tot = N.syy - N.n * ybar * ybar;
709 if (!(ss_tot > 0.0)) {
710 why = "no variance";
711 return kFALSE;
712 }
713 const Int_t n_amp = lay.n_amp;
714 K.rms_before = TMath::Sqrt(ss_tot / N.n);
715
716 // The binned kernel: intercept plus every (band, bin) somebody populated.
717 Bool_t binned_ok = kFALSE;
718 Double_t intercept_binned = 0.0;
719 {
720 std::vector<Int_t> keep(1, 0);
721 for (Int_t a = 0; a < n_amp; a++)
722 for (Int_t b = 0; b < kNBins; b++)
723 if (N.Get(lay.Bin(a, b), lay.Bin(a, b)) > 0.0)
724 keep.push_back(lay.Bin(a, b));
725 TVectorD p;
726 Double_t drop = 0.0;
727 if (SolveSubset(N, keep, p, drop)) {
728 const Double_t ss_res = N.syy - drop;
729 for (Int_t i = 0; i < Int_t(keep.size()); i++) {
730 if (keep[i] == 0)
731 intercept_binned = p[i];
732 else
733 K.k_binned[(keep[i] - 1) / kNBins][(keep[i] - 1) % kNBins] = p[i];
734 }
735 K.r2_binned = 1.0 - ss_res / ss_tot;
736 K.rms_after_binned = TMath::Sqrt(TMath::Max(0.0, ss_res) / N.n);
737 binned_ok = K.r2_binned > 0.0;
738 }
739 }
740
741 // The form: for each grid tau, intercept plus the free low bins and the
742 // two form features per band; c is minus the direct-tail coefficient.
743 K.form_ok = kFALSE;
744 if (lay.HasForm()) {
745 Double_t best_res = 0.0, given_res = 0.0;
746 Int_t free_j = -1;
747 TVectorD best_p, given_p;
748 std::vector<Int_t> best_keep, given_keep;
749 for (Int_t j = 0; j < lay.n_tau; j++) {
750 if (j >= kNTauGrid && j != given_j)
751 continue; // another group's given tau
752 std::vector<Int_t> keep(1, 0);
753 for (Int_t a = 0; a < n_amp; a++) {
754 for (Int_t b = 0; b < lay.n_free; b++)
755 if (N.Get(lay.Low(a, b), lay.Low(a, b)) > 0.0)
756 keep.push_back(lay.Low(a, b));
757 if (N.Get(lay.D(j, a), lay.D(j, a)) > 0.0)
758 keep.push_back(lay.D(j, a));
759 if (N.Get(lay.B(j, a), lay.B(j, a)) > 0.0)
760 keep.push_back(lay.B(j, a));
761 }
762 TVectorD p;
763 Double_t drop = 0.0;
764 if (!SolveSubset(N, keep, p, drop))
765 continue;
766 const Double_t ss_res = N.syy - drop;
767 if (j == given_j) {
768 given_res = ss_res;
769 given_p.ResizeTo(p.GetNrows());
770 given_p = p;
771 given_keep = keep;
772 continue;
773 }
774 if (free_j < 0 || ss_res < best_res) {
775 best_res = ss_res;
776 free_j = j;
777 best_p.ResizeTo(p.GetNrows());
778 best_p = p;
779 best_keep = keep;
780 }
781 }
782 if (free_j >= 0) {
783 K.tau_free_us = grid.tau_us[free_j];
784 K.r2_form_free = 1.0 - best_res / ss_tot;
785 }
786 // Applied: the given tau's solution when there is one, else the
787 // profile's; the free result stays alongside as the check.
788 if (given_j >= 0 && given_keep.empty())
789 why = Form("form at the given tau %.1f us did not solve; profiled tau "
790 "used",
791 grid.tau_us[given_j]);
792 const Bool_t use_given = given_j >= 0 && !given_keep.empty();
793 const Int_t j = use_given ? given_j : free_j;
794 if (use_given) {
795 best_res = given_res;
796 best_p.ResizeTo(given_p.GetNrows());
797 best_p = given_p;
798 best_keep = given_keep;
799 }
800 if (j >= 0) {
801 K.form_ok = kTRUE;
802 K.j_tau = j;
803 K.tau_us = grid.tau_us[j];
804 K.tau_given = use_given;
805 if (free_j < 0) {
806 K.tau_free_us = K.tau_us;
807 K.r2_form_free = 1.0 - best_res / ss_tot;
808 }
809 for (Int_t i = 0; i < Int_t(best_keep.size()); i++) {
810 const Int_t f = best_keep[i];
811 if (f == 0) {
812 K.intercept_form = best_p[i];
813 continue;
814 }
815 Bool_t placed = kFALSE;
816 for (Int_t a = 0; a < n_amp && !placed; a++) {
817 for (Int_t b = 0; b < lay.n_free && !placed; b++)
818 if (f == lay.Low(a, b)) {
819 K.k_low[a][b] = best_p[i];
820 placed = kTRUE;
821 }
822 if (!placed && f == lay.D(j, a)) {
823 K.c[a] = -best_p[i];
824 placed = kTRUE;
825 } else if (!placed && f == lay.B(j, a)) {
826 K.cb[a] = best_p[i];
827 placed = kTRUE;
828 }
829 }
830 }
831 K.r2_form = 1.0 - best_res / ss_tot;
832 K.rms_after_form = TMath::Sqrt(TMath::Max(0.0, best_res) / N.n);
833 if (!(K.r2_form > 0.0))
834 K.form_ok = kFALSE;
835 }
836 }
837
838 // Which one is applied: the form where asked for and fitted, the bins
839 // otherwise (a failed form falls back).
840 K.form = want_form && K.form_ok;
841 if (K.form) {
842 K.intercept = K.intercept_form;
843 K.r2 = K.r2_form;
844 K.rms_after = K.rms_after_form;
845 for (Int_t a = 0; a < n_amp; a++)
846 for (Int_t b = 0; b < kNBins; b++)
847 K.k[a][b] = K.FormAt(a, BinCentreUs(b));
848 K.ok = kTRUE;
849 } else {
850 K.intercept = intercept_binned;
851 K.r2 = K.r2_binned;
852 K.rms_after = K.rms_after_binned;
853 for (Int_t a = 0; a < n_amp; a++)
854 for (Int_t b = 0; b < kNBins; b++)
855 K.k[a][b] = K.k_binned[a][b];
856 K.ok = binned_ok;
857 if (!K.ok && why.IsNull())
858 why = "binned fit did not solve";
859 }
860 return K.ok;
861}
862
863// One feature class against the intercept, the right-hand side and itself
864// (upper triangle: a class's features against each other from `ia`).
865void AccumulateClassBlock(Normal &Nc, const std::vector<Double_t> &xx,
866 Double_t y, const std::vector<Int_t> &v) {
867 for (size_t ia = 0; ia < v.size(); ia++) {
868 const Int_t i = v[ia];
869 const Double_t xi = xx[i];
870 Nc.At(0, i) += xi;
871 Nc.b[i] += xi * y;
872 for (size_t ib = ia; ib < v.size(); ib++)
873 Nc.At(i, v[ib]) += xi * xx[v[ib]];
874 }
875}
876
877// One (x, y) sample of a channel's least-squares problem, filling every
878// nonzero feature class (see Normal) against its partners.
879struct FitSample {
880 std::vector<Normal> &N;
881 const FeatLayout &lay;
882 Int_t n_amp;
883 Int_t bin_lo, bin_hi, low_lo, low_hi;
884 std::vector<Int_t> &nz_bin;
885 std::vector<Int_t> &nz_low;
886 std::vector<std::vector<Int_t>> &nz_tau;
887
888 void operator()(Int_t c, const std::vector<Double_t> &xx, Double_t y,
889 Double_t) {
890 Normal &Nc = N[c];
891 Nc.n++;
892 Nc.sy += y;
893 Nc.syy += y * y;
894 Nc.At(0, 0) += 1.0;
895 Nc.b[0] += y;
896 nz_bin.clear();
897 for (Int_t i = bin_lo; i < bin_hi; i++)
898 if (xx[i] != 0.0)
899 nz_bin.push_back(i);
900 nz_low.clear();
901 for (Int_t i = low_lo; i < low_hi; i++)
902 if (xx[i] != 0.0)
903 nz_low.push_back(i);
904 for (Int_t j = 0; j < lay.n_tau; j++) {
905 nz_tau[j].clear();
906 for (Int_t a = 0; a < n_amp; a++) {
907 if (xx[lay.D(j, a)] != 0.0)
908 nz_tau[j].push_back(lay.D(j, a));
909 if (xx[lay.B(j, a)] != 0.0)
910 nz_tau[j].push_back(lay.B(j, a));
911 }
912 }
913 AccumulateClassBlock(Nc, xx, y, nz_bin);
914 AccumulateClassBlock(Nc, xx, y, nz_low);
915 for (Int_t j = 0; j < lay.n_tau; j++) {
916 const std::vector<Int_t> &vt = nz_tau[j];
917 if (vt.empty())
918 continue;
919 AccumulateClassBlock(Nc, xx, y, vt);
920 for (size_t ia = 0; ia < nz_low.size(); ia++)
921 for (size_t ib = 0; ib < vt.size(); ib++)
922 Nc.At(nz_low[ia], vt[ib]) += xx[nz_low[ia]] * xx[vt[ib]];
923 }
924 }
925};
926
927// The raw dt profile sample for the tau fit: the previous-pulse gap and the
928// deviation, kept in the fit's window.
929struct TauSample {
930 std::vector<Double_t> &dt_us;
931 std::vector<Double_t> &dev;
932
933 void operator()(Int_t, const std::vector<Double_t> &, Double_t y,
934 Double_t dt_prev) {
935 if (dt_prev >= kTauFitLoUs && dt_prev <= kTauFitHiUs) {
936 dt_us.push_back(dt_prev);
937 dev.push_back(y);
938 }
939 }
940};
941
942// One diagnostics sample: the deviation before and after the channel's own
943// kernel against the prediction, and the deviation vs the previous-pulse gap.
944struct DiagSample {
945 const Result &res;
946 const FeatLayout &lay;
947 Int_t g;
948
949 void operator()(Int_t c, const std::vector<Double_t> &xx, Double_t y,
950 Double_t dt_prev) {
951 const Kernel &K = res.kernel_ch[c];
952 if (!K.ok)
953 return;
954 const Double_t pred = Predict(K, lay, xx);
955 const Double_t after = y - pred;
956 res.dev_vs_pred[g]->Fill(pred, y);
957 res.dev_before[g]->Fill(y);
958 res.dev_after[g]->Fill(after);
959 if (dt_prev > 0.0) {
960 const Double_t l = TMath::Log10(dt_prev * 1.0e6);
961 res.dtprev_before[g]->Fill(l, y);
962 res.dtprev_after[g]->Fill(l, after);
963 res.dtprev_before_ch[c]->Fill(l, y);
964 res.dtprev_after_ch[c]->Fill(l, after);
965 }
966 }
967};
968
969} // namespace
970
971Bool_t Measure(std::vector<RawHit> &hits, const std::vector<Int_t> &group_of,
972 Result &res, const TString &file_label) {
973 res.n_hits = Long64_t(hits.size());
974 if (hits.empty())
975 return kFALSE;
976 Bool_t sorted = kTRUE;
977 for (size_t j = 1; j < hits.size() && sorted; j++)
978 sorted = hits[j].timestamp >= hits[j - 1].timestamp;
979 res.sorted_input = sorted;
980 if (!sorted)
981 std::stable_sort(hits.begin(), hits.end(),
982 [](const RawHit &a, const RawHit &b) {
983 return a.timestamp < b.timestamp;
984 });
985
986 const Int_t ref = IndexOfName(Constants::ActiveReferenceChannel());
987 // Strip 0 must have fired too, else it is not a beam particle through the
988 // whole chamber (as in the downstream beam-only selection).
989 const Int_t strip0 = IndexOfName("Strip0");
990 if (ref < 0) {
991 std::cerr << " " << file_label
992 << ": pulse history: no reference channel in the map"
993 << std::endl;
994 return kFALSE;
995 }
996 const Double_t ref_lo = Constants::ActiveReferenceChannelMinAdc();
997 const Double_t ref_hi = Constants::ActiveReferenceChannelMaxAdc();
998 const Double_t window_ps = Constants::ActiveEventTimeWindowUs() * 1.0e6;
999 const Int_t nidx = Int_t(group_of.size());
1000 const Int_t n_amp = TMath::Max(
1001 1, TMath::Min(kMAX_AMP_BINS, Constants::cfg.PULSE_HISTORY_AMP_BINS));
1002 // Every channel with a kernel, and the long ends alone, which are what the
1003 // beam-like selection is made of.
1004 std::vector<Int_t> fit_ch, long_ch;
1005 for (Int_t i = 0; i < nidx; i++) {
1006 if (group_of[i] == kNONE)
1007 continue;
1008 fit_ch.push_back(i);
1009 if (IsLongGroup(group_of[i]))
1010 long_ch.push_back(i);
1011 }
1012 if (long_ch.empty())
1013 return kFALSE;
1014
1015 // Beam peak per channel: mode of the raw spectrum above the noise pile.
1016 // Beam-like: every long end in [lo, hi] x mode; short: rough amp bands.
1017 const Double_t emax = Constants::ActiveStripEMaxAdc();
1018 const Int_t nb = 128;
1019 std::vector<std::vector<Long64_t>> spec(nidx, std::vector<Long64_t>(nb, 0));
1020 for (size_t j = 0; j < hits.size(); j++) {
1021 const RawHit &h = hits[j];
1022 const Int_t i = HitIndex(h);
1023 if (i < 0 || i >= nidx || group_of[i] == kNONE)
1024 continue;
1025 const Int_t b = Int_t(Double_t(h.energy) / emax * nb);
1026 if (b >= 0 && b < nb)
1027 spec[i][b]++;
1028 }
1029 res.mode.assign(nidx, 0.0);
1030 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1031 const Int_t i = fit_ch[k];
1032 Int_t best = -1;
1033 for (Int_t b = nb / 16; b < nb; b++) // skip the lowest 1/16 of the range
1034 if (best < 0 || spec[i][b] > spec[i][best])
1035 best = b;
1036 res.mode[i] = best >= 0 ? (best + 0.5) * emax / nb : 0.0;
1037 }
1038 const std::vector<Double_t> &mode = res.mode;
1039 const Double_t blo = Constants::cfg.PULSE_HISTORY_BEAM_LO;
1040 const Double_t bhi = Constants::cfg.PULSE_HISTORY_BEAM_HI;
1041
1042 // Pass A: seeds making a beam-like event for each group, and the channel
1043 // means; short ends and strips 0/17 enter the fit only where they fired.
1044 std::vector<size_t> seeds[kNGROUPS];
1045 std::vector<Double_t> mean(nidx, 0.0);
1046 std::vector<Double_t> ev_e(nidx, 0.0);
1047 const Double_t olo = Constants::cfg.PULSE_HISTORY_OWN_LO;
1048 const Double_t ohi = Constants::cfg.PULSE_HISTORY_OWN_HI;
1049 // The form is fitted for every group as soon as one asks for it, so the
1050 // report can compare it everywhere; otherwise the scan keeps its cost.
1051 Bool_t want_form = kFALSE;
1052 for (Int_t g = 1; g < kNGROUPS; g++)
1053 want_form = want_form || (GroupEnabled(g) && GroupWantsForm(g));
1054 FormGrid grid;
1055 {
1056 const Double_t rise = Constants::cfg.PULSE_HISTORY_TRAP_RISE_US;
1057 const Double_t flat = Constants::cfg.PULSE_HISTORY_TRAP_FLAT_US;
1058 grid.t_m_us = rise + Constants::cfg.PULSE_HISTORY_PEAKING_US;
1059 grid.t_f_us = 2.0 * rise + flat;
1060 grid.t_lo_us = grid.t_f_us - grid.t_m_us;
1061 if (want_form)
1062 for (Int_t j = 0; j < kNTauGrid; j++) {
1063 const Double_t tau =
1064 kTauGridLoUs * TMath::Power(kTauGridHiUs / kTauGridLoUs,
1065 Double_t(j) / Double_t(kNTauGrid - 1));
1066 grid.tau_us.push_back(tau);
1067 grid.em.push_back(TMath::Exp(-grid.t_m_us / tau));
1068 grid.ef.push_back(TMath::Exp(-grid.t_f_us / tau));
1069 }
1070 }
1071 // A tau given for a group is appended to the grid so the applied form is
1072 // solved at exactly that value; given_j[g] is -1 when profiled.
1073 Int_t given_j[kNGROUPS];
1074 for (Int_t g = 0; g < kNGROUPS; g++)
1075 given_j[g] = -1;
1076 if (want_form)
1077 for (Int_t g = 1; g < kNGROUPS; g++) {
1078 const Double_t tau = GroupTauUs(g);
1079 if (!(tau > 0.0) || !GroupEnabled(g))
1080 continue;
1081 for (Int_t j = kNTauGrid; j < Int_t(grid.tau_us.size()); j++)
1082 if (TMath::Abs(grid.tau_us[j] - tau) < 1.0e-6)
1083 given_j[g] = j;
1084 if (given_j[g] < 0) {
1085 given_j[g] = Int_t(grid.tau_us.size());
1086 grid.tau_us.push_back(tau);
1087 grid.em.push_back(TMath::Exp(-grid.t_m_us / tau));
1088 grid.ef.push_back(TMath::Exp(-grid.t_f_us / tau));
1089 }
1090 }
1091 grid.Tabulate();
1092 FeatLayout lay;
1093 lay.n_amp = n_amp;
1094 lay.n_tau = Int_t(grid.tau_us.size());
1095 // Bins that start below t_lo stay free under the form; the last of them is
1096 // only partly below and gets the part that is.
1097 lay.n_free = want_form ? TMath::Max(0, BinOf(grid.t_lo_us * 1.0e-6) + 1) : 0;
1098 const Int_t np = lay.Size();
1099 const Double_t keep_ps = TMath::Power(10.0, kLogHi) * 1.0e12;
1100 std::vector<Double_t> x(np, 0.0);
1101 const ScanState st = {hits, group_of, fit_ch, long_ch, mode, ev_e,
1102 mean, nidx, strip0, window_ps, olo, ohi,
1103 blo, bhi, n_amp, keep_ps, lay, grid};
1104 std::vector<Long64_t> nmean(nidx, 0);
1105 for (size_t j = 0; j < hits.size(); j++) {
1106 const RawHit &h = hits[j];
1107 if (HitIndex(h) != ref)
1108 continue;
1109 if (Double_t(h.energy) < ref_lo || Double_t(h.energy) > ref_hi)
1110 continue;
1111 res.n_seeds++;
1112 if (!Lookahead(st, j))
1113 continue;
1114 for (Int_t g = 1; g < kNGROUPS; g++) {
1115 if (!BeamFor(st, g))
1116 continue;
1117 seeds[g].push_back(j);
1118 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1119 const Int_t c = fit_ch[k];
1120 if (group_of[c] == g && ev_e[c] > 0.0) {
1121 mean[c] += ev_e[c];
1122 nmean[c]++;
1123 }
1124 }
1125 }
1126 }
1127 res.n_beam_events = 0;
1128 for (Int_t g = 1; g < kNGROUPS; g++)
1129 res.n_beam_events =
1130 TMath::Max(res.n_beam_events, Long64_t(seeds[g].size()));
1131 if (res.n_beam_events < Constants::cfg.PULSE_HISTORY_MIN_EVENTS) {
1132 std::cerr << " " << file_label << ": pulse history: only "
1133 << res.n_beam_events << " beam-like events; not measured"
1134 << std::endl;
1135 return kFALSE;
1136 }
1137 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1138 const Int_t c = fit_ch[k];
1139 mean[c] = nmean[c] ? mean[c] / Double_t(nmean[c]) : 0.0;
1140 }
1141
1142 // Pass B: normal equations, one per channel; a group's is their sum.
1143 // Every channel gets its own kernel (the preamps differ within a chain).
1144 std::vector<Normal> N(nidx);
1145 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++)
1146 N[fit_ch[k]].Init(np);
1147 // The fit scan builds every feature; only the blocks a fit reads are
1148 // accumulated (see Normal): nonzero features sorted into classes first.
1149 ScanNeeds all_needs;
1150 for (Int_t j = 0; j < lay.n_tau; j++)
1151 all_needs.taus.push_back(j);
1152 std::vector<Int_t> nz_bin, nz_low;
1153 std::vector<std::vector<Int_t>> nz_tau(lay.n_tau);
1154 const Int_t bin_lo = 1, bin_hi = 1 + n_amp * kNBins;
1155 const Int_t low_lo = bin_hi, low_hi = bin_hi + n_amp * lay.n_free;
1156 for (Int_t g = 1; g < kNGROUPS; g++) {
1157 res.enabled[g] = GroupEnabled(g);
1158 if (!res.enabled[g])
1159 continue; // configured off: no kernel, no correction
1160 FitSample fit_sample{N, lay, n_amp, bin_lo, bin_hi,
1161 low_lo, low_hi, nz_bin, nz_low, nz_tau};
1162 Scan(st, g, seeds[g], all_needs, x, fit_sample);
1163 }
1164
1165 res.group_of = group_of;
1166 res.name_ch.assign(nidx, "");
1167 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++)
1168 res.name_ch[fit_ch[k]] = NameOfIndex(fit_ch[k]);
1169 res.kernel_ch.assign(nidx, Kernel());
1170 Bool_t any = kFALSE;
1171 for (Int_t g = 1; g < kNGROUPS; g++) {
1172 Kernel &G = res.kernel[g];
1173 if (!res.enabled[g]) {
1174 G.ok = kFALSE;
1175 continue;
1176 }
1177 // The group problem is the sum over its channels.
1178 Normal Ng;
1179 Ng.Init(np);
1180 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++)
1181 if (group_of[fit_ch[k]] == g)
1182 Ng.Add(N[fit_ch[k]]);
1183 TString why;
1184 FitKernel(Ng, lay, grid, given_j[g], GroupWantsForm(g), G, why);
1185 G.n_beam = Long64_t(seeds[g].size());
1186 if (!why.IsNull())
1187 std::cerr << " " << file_label << ": pulse history " << GroupName(g)
1188 << ": " << why << std::endl;
1189 // Each channel: its own kernel when it has the pairs for one and the fit
1190 // succeeded, else a copy of the group's, flagged.
1191 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1192 const Int_t c = fit_ch[k];
1193 if (group_of[c] != g)
1194 continue;
1195 Kernel &K = res.kernel_ch[c];
1196 TString why_c;
1197 Bool_t own =
1198 N[c].n >= kMinPairsPerChannel &&
1199 FitKernel(N[c], lay, grid, given_j[g], GroupWantsForm(g), K, why_c);
1200 if (!own) {
1201 if (why_c.IsNull())
1202 why_c = Form("only %lld pairs, fewer than %lld", N[c].n,
1204 const Long64_t n_own = N[c].n;
1205 K = G;
1206 K.n = n_own;
1207 K.from_group = kTRUE;
1208 K.why_group = why_c;
1209 } else if (!why_c.IsNull()) {
1210 Constants::DetailErr() << " " << file_label << ": pulse history "
1211 << res.name_ch[c] << ": " << why_c << std::endl;
1212 }
1213 // What the group's kernel would have done on this channel: the gain
1214 // from a kernel of its own is the difference to K.r2.
1215 K.r2_group = G.ok ? R2Of(G, N[c], lay) : 0.0;
1216 K.n_beam = G.n_beam;
1217 any = any || K.ok;
1218 }
1219 }
1220 if (!any)
1221 return kFALSE;
1222
1223 // Decay summary per fitted group: pre-correction deviation vs previous-pulse
1224 // time. p2 = the preamp decay the pole-zero missed, hardware-study number.
1225 for (Int_t g = 1; g < kNGROUPS; g++) {
1226 Kernel &K = res.kernel[g];
1227 if (!K.ok)
1228 continue;
1229 std::vector<Double_t> dt_us, dev;
1230 // Only the deviation and the gap are needed: no features built.
1231 ScanNeeds none;
1232 none.bins = kFALSE;
1233 none.low = kFALSE;
1234 TauSample tau_sample{dt_us, dev};
1235 Scan(st, g, seeds[g], none, x, tau_sample);
1236 FitTauDecay(dt_us, dev, K.tau);
1237 }
1238
1239 // Pass C: diagnostics, with the applied (per-channel) kernels. Log-time
1240 // axes are log10(dt) where dt is the time difference in microseconds.
1241 if (Constants::SavePlots()) {
1242 const TString tag = file_label;
1243 const Double_t xlo = kLogLo + 6.0, xhi = kLogHi + 6.0;
1244 for (Int_t g = 1; g < kNGROUPS; g++) {
1245 const char *gn = GroupTag(g);
1246 res.dev_vs_pred[g] =
1247 new TH2D(Form("h_ph_dev_vs_pred_%s_%s", gn, tag.Data()),
1248 ";Predicted Deviation [ADC];Measured Deviation [ADC]", 120,
1249 -600.0, 600.0, 120, -600.0, 600.0);
1250 res.dev_before[g] = new TH1D(
1251 Form("h_ph_dev_before_%s_%s", gn, tag.Data()),
1252 Form(";%s #minus Mean %s [ADC];Events", gn, gn), 240, -600.0, 600.0);
1253 res.dev_after[g] = new TH1D(
1254 Form("h_ph_dev_after_%s_%s", gn, tag.Data()),
1255 Form(";%s #minus Mean %s [ADC];Events", gn, gn), 240, -600.0, 600.0);
1256 res.dtprev_before[g] = new TProfile(
1257 Form("p_ph_dtprev_before_%s_%s", gn, tag.Data()),
1258 Form(";log_{10}(#Deltat [#mus]);%s #minus Mean %s [ADC]", gn, gn), 48,
1259 xlo, xhi);
1260 res.dtprev_after[g] = new TProfile(
1261 Form("p_ph_dtprev_after_%s_%s", gn, tag.Data()),
1262 Form(";log_{10}(#Deltat [#mus]);%s #minus Mean %s [ADC]", gn, gn), 48,
1263 xlo, xhi);
1264 res.shift[g] = new TH1D(Form("h_ph_shift_%s_%s", gn, tag.Data()),
1265 ";Applied Shift [ADC];Hits", 240, -600.0, 600.0);
1266 TH1 *hists[6] = {static_cast<TH1 *>(res.dev_vs_pred[g]),
1267 static_cast<TH1 *>(res.dev_before[g]),
1268 static_cast<TH1 *>(res.dev_after[g]),
1269 static_cast<TH1 *>(res.dtprev_before[g]),
1270 static_cast<TH1 *>(res.dtprev_after[g]),
1271 static_cast<TH1 *>(res.shift[g])};
1272 for (Int_t h = 0; h < 6; h++)
1273 hists[h]->SetDirectory(nullptr);
1274 }
1275 // Per channel: the deviation against dt before and after its own kernel,
1276 // which is where a channel the group kernel did not serve shows up.
1277 res.dtprev_before_ch.assign(nidx, nullptr);
1278 res.dtprev_after_ch.assign(nidx, nullptr);
1279 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1280 const Int_t c = fit_ch[k];
1281 const char *cn = res.name_ch[c].Data();
1282 res.dtprev_before_ch[c] = new TProfile(
1283 Form("p_ph_dtprev_before_ch_%s_%s", cn, tag.Data()),
1284 Form(";log_{10}(#Deltat [#mus]);%s #minus Mean %s [ADC]", cn, cn), 48,
1285 xlo, xhi);
1286 res.dtprev_after_ch[c] = new TProfile(
1287 Form("p_ph_dtprev_after_ch_%s_%s", cn, tag.Data()),
1288 Form(";log_{10}(#Deltat [#mus]);%s #minus Mean %s [ADC]", cn, cn), 48,
1289 xlo, xhi);
1290 res.dtprev_before_ch[c]->SetDirectory(nullptr);
1291 res.dtprev_after_ch[c]->SetDirectory(nullptr);
1292 }
1293 for (Int_t g = 1; g < kNGROUPS; g++) {
1294 if (!res.kernel[g].ok)
1295 continue;
1296 // Only the applied models' features: bins where a channel is binned,
1297 // the low bins and the applied taus where one is on the form.
1298 ScanNeeds needs;
1299 needs.bins = kFALSE;
1300 needs.low = kFALSE;
1301 for (Int_t k = 0; k < Int_t(fit_ch.size()); k++) {
1302 const Kernel &K = res.kernel_ch[fit_ch[k]];
1303 if (group_of[fit_ch[k]] != g || !K.ok)
1304 continue;
1305 if (!K.form) {
1306 needs.bins = kTRUE;
1307 continue;
1308 }
1309 needs.low = kTRUE;
1310 if (K.j_tau >= 0 && std::find(needs.taus.begin(), needs.taus.end(),
1311 K.j_tau) == needs.taus.end())
1312 needs.taus.push_back(K.j_tau);
1313 }
1314 DiagSample diag_sample{res, lay, g};
1315 Scan(st, g, seeds[g], needs, x, diag_sample);
1316 }
1317 }
1318 return kTRUE;
1319}
1320void Apply(std::vector<RawHit> &hits, const std::vector<Int_t> &group_of,
1321 Result &res) {
1322 const Int_t nidx = Int_t(group_of.size());
1323 const Double_t keep_ps = Constants::cfg.PULSE_HISTORY_APPLY_MAX_US * 1.0e6;
1324 std::vector<std::deque<Past>> past(nidx);
1325 Double_t shift_sum[kNGROUPS] = {0};
1326 Long64_t shift_n[kNGROUPS] = {0};
1327 for (size_t j = 0; j < hits.size(); j++) {
1328 RawHit &h = hits[j];
1329 const Int_t i = HitIndex(h);
1330 if (i < 0 || i >= nidx || group_of[i] == kNONE)
1331 continue;
1332 const Int_t g = group_of[i];
1333 const Double_t t = Double_t(h.timestamp);
1334 const Double_t e_raw = Double_t(h.energy);
1335 std::deque<Past> &d = past[i];
1336 while (!d.empty() && d.front().t < t - keep_ps)
1337 d.pop_front();
1338 // The channel's own kernel (or the group's copy it was handed).
1339 const Kernel *K =
1340 i < Int_t(res.kernel_ch.size()) ? &res.kernel_ch[i] : nullptr;
1341 if (K && K->ok) {
1342 const Double_t s =
1343 Shift(d, t, *K, i < Int_t(res.mode.size()) ? res.mode[i] : 0.0);
1344 Double_t e = e_raw - s;
1345 if (e < 0.0) {
1346 e = 0.0;
1347 h.flags |= kFlagClamped;
1348 res.n_clamped++;
1349 res.n_clamped_group[g]++;
1350 } else if (e > 65535.0) {
1351 e = 65535.0;
1352 }
1353 h.energy = UShort_t(e + 0.5);
1354 res.n_corrected++;
1355 shift_sum[g] += s;
1356 shift_n[g]++;
1357 if (res.shift[g])
1358 res.shift[g]->Fill(-s);
1359 }
1360 // The kernel was fitted on raw amplitudes of the previous pulses.
1361 d.push_back({t, e_raw});
1362 }
1363 for (Int_t g = 0; g < kNGROUPS; g++)
1364 res.mean_shift[g] = shift_n[g] ? shift_sum[g] / shift_n[g] : 0.0;
1365}
1366
1367namespace {
1368
1369// The form's line of a report entry: which tau, how the free profile compares
1370// with a given one, and the per-band amplitudes.
1371TString FormLines(const Kernel &K, const char *indent) {
1372 std::ostringstream s;
1373 s << Form("%sform: tau %.1f us (%s) R^2 %.3f vs binned %.3f rms %.1f vs "
1374 "%.1f ADC%s",
1375 indent, K.tau_us, K.tau_given ? "given" : "profiled", K.r2_form,
1376 K.r2_binned, K.rms_after_form, K.rms_after_binned,
1377 (!K.form && K.form_ok) ? " [bins applied]" : "")
1378 << std::endl;
1379 if (K.tau_given) {
1380 // The check: where the free profile lands; the grid step is about 15
1381 // percent, so a free tau within one step of the given one is a match.
1382 const Double_t ratio = K.tau_us > 0.0 ? K.tau_free_us / K.tau_us : 0.0;
1383 s << Form("%s profile prefers tau %.1f us (%.2f x given, grid step "
1384 "1.15) R^2 %.4f there vs %.4f at given%s",
1385 indent, K.tau_free_us, ratio, K.r2_form_free, K.r2_form,
1386 (ratio > 0.0 && ratio < 1.0 / 1.15) || ratio > 1.15
1387 ? " <-- given tau not preferred"
1388 : "")
1389 << std::endl;
1390 }
1391 for (Int_t a = 0; a < K.n_amp; a++)
1392 s << Form("%s band %d: c %+.4f (undershoot > 0) c_B %+.5f W_eff %.0f "
1393 "us",
1394 indent, a, K.c[a], K.cb[a], K.EffectiveWindowUs(a))
1395 << std::endl;
1396 return s.str();
1397}
1398
1399const char *StatusOf(const Kernel &K, Bool_t enabled) {
1400 if (!enabled)
1401 return "DISABLED";
1402 if (!K.ok)
1403 return "NOT USED";
1404 return K.form ? "FORM " : "BINNED ";
1405}
1406
1407} // namespace
1408
1409TString Report(const Result &res, const TString &file_label, Bool_t detail) {
1410 std::ostringstream s;
1411 s << Form(" pulse history %s: %lld hits%s, %lld reference seeds, %lld "
1412 "beam-like events; %lld hits corrected, %lld clamped to 0",
1413 file_label.Data(), res.n_hits,
1414 res.sorted_input ? "" : " (SORTED: input was not time ordered)",
1415 res.n_seeds, res.n_beam_events, res.n_corrected, res.n_clamped)
1416 << std::endl;
1417 for (Int_t g = 1; g < kNGROUPS; g++) {
1418 const Kernel &K = res.kernel[g];
1419 // The group's own fit (the sum of its channels) is the fallback kernel
1420 // and the reference the per-channel lines are read against.
1421 if (!detail && !res.enabled[g])
1422 continue;
1423 s << Form(" %-22s %s beam events %lld n %lld R^2 %.3f rms %.1f -> "
1424 "%.1f ADC mean shift %.1f ADC clamped %lld [group fit]",
1425 GroupName(g), StatusOf(K, res.enabled[g]), K.n_beam, K.n, K.r2,
1426 K.rms_before, K.rms_after, res.mean_shift[g],
1427 res.n_clamped_group[g])
1428 << std::endl;
1429 if (!res.enabled[g])
1430 continue;
1431 // Outside the per-file sample the group lines above are the report; the
1432 // form, the kernel rows, the tau fit and the per-channel lines are detail.
1433 if (!detail)
1434 continue;
1435 if (K.form_ok)
1436 s << FormLines(K, " ");
1437 for (Int_t a = 0; a < K.n_amp; a++) {
1438 if (K.n_amp == 1)
1439 s << " kernel:";
1440 else
1441 s << Form(" kernel, previous pulse %s:",
1442 BandLabel(a, K.n_amp, kFALSE).Data());
1443 for (Int_t b = 0; b < kNBins; b++)
1444 s << Form(" %.0fus:%+.3f", BinCentreUs(b), K.k[a][b]);
1445 s << std::endl;
1446 }
1447 // Decay-time summary of the raw dt profile: p2 = the preamp decay the
1448 // pole-zero missed; flat profiles (matched chains) report no meaningful p2.
1449 if (K.tau.ok) {
1450 if (K.tau.flat)
1451 s << Form(
1452 " tau fit: flat (A %.1f +- %.1f ADC within 2 sigma of 0)",
1453 K.tau.p1, K.tau.p1err)
1454 << std::endl;
1455 else
1456 s << Form(" tau fit: %.1f +- %.1f us (A %+.1f +- %.1f ADC,"
1457 " level %+.1f, baseline lift %+.4f ADC/us, chi2/ndf %.1f/%d)",
1458 K.tau.p2, K.tau.p2err, K.tau.p1, K.tau.p1err, K.tau.p0,
1459 K.tau.p3, K.tau.chi2, K.tau.ndf)
1460 << std::endl;
1461 }
1462 // The channels of the group: the kernel each one is corrected with.
1463 for (Int_t c = 0; c < Int_t(res.kernel_ch.size()); c++) {
1464 if (res.group_of[c] != g)
1465 continue;
1466 const Kernel &C = res.kernel_ch[c];
1467 s << Form(" %-8s %s n %-9lld R^2 %.3f rms %.1f -> %.1f ADC",
1468 res.name_ch[c].Data(), StatusOf(C, kTRUE), C.n, C.r2,
1469 C.rms_before, C.rms_after);
1470 if (C.from_group) {
1471 s << Form(" [group kernel: %s]", C.why_group.Data()) << std::endl;
1472 continue;
1473 }
1474 if (C.form_ok) {
1475 s << Form(" tau %.1f us%s", C.tau_us, C.tau_given ? " given" : "");
1476 if (C.tau_given)
1477 s << Form(", profile %.1f", C.tau_free_us);
1478 s << " c";
1479 for (Int_t a = 0; a < C.n_amp; a++)
1480 s << Form(" %+.3f", C.c[a]);
1481 s << Form(" (binned R^2 %.3f, group kernel %.3f)", C.r2_binned,
1482 C.r2_group);
1483 } else {
1484 // No form: the size of the binned kernel past the trapezoid, so a
1485 // channel that differs from its group can be seen in the log.
1486 const Int_t b_from = BinOf(4.7e-6);
1487 const Double_t from_us =
1488 TMath::Power(10.0, kLogLo + b_from * (kLogHi - kLogLo) / kNBins) *
1489 1.0e6;
1490 Double_t kmax = 0.0;
1491 for (Int_t a = 0; a < C.n_amp; a++)
1492 for (Int_t b = b_from; b < kNBins; b++)
1493 if (TMath::Abs(C.k[a][b]) > TMath::Abs(kmax))
1494 kmax = C.k[a][b];
1495 s << Form(" max|k| beyond %.1f us %+.3f (group kernel R^2 %.3f)",
1496 from_us, kmax, C.r2_group);
1497 }
1498 s << std::endl;
1499 }
1500 }
1501 return s.str();
1502}
1503
1504namespace {
1505
1506// A legend in the right-hand column, sized to its rows so long entries
1507// neither shrink nor spill; top or bottom, one or two columns.
1508TLegend *RightLegend(Int_t nentries, Bool_t header, Bool_t top,
1509 Double_t x1 = 0.62, Int_t ncols = 1) {
1510 const Int_t nrows = (header ? 1 : 0) + (nentries + ncols - 1) / ncols;
1511 const Double_t h = 0.048 * nrows + 0.02;
1512 TLegend *leg = top ? PlottingUtils::AddLegend(x1, 0.89, 0.87 - h, 0.87)
1513 : PlottingUtils::AddLegend(x1, 0.89, 0.15, 0.15 + h);
1514 leg->SetNColumns(ncols);
1515 return leg;
1516}
1517
1518// One kernel figure: the binned coefficients as points, the form as a curve
1519// over them (solid where applied, dashed where the bins are).
1520void DrawKernelFigure(const Kernel &K, const TString &subdir,
1521 const char *fname) {
1522 const Int_t colors[kMAX_AMP_BINS] = {kBlack, kRed + 1, kAzure + 1,
1523 kGreen + 2, kOrange + 7, kMagenta + 1};
1524 const Double_t xlo = kLogLo + 6.0, xhi = kLogHi + 6.0;
1525 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1526 Double_t ylo = 0.0, yhi = 0.0;
1527 for (Int_t a = 0; a < K.n_amp; a++)
1528 for (Int_t b = 0; b < kNBins; b++) {
1529 ylo = TMath::Min(ylo, K.k_binned[a][b]);
1530 yhi = TMath::Max(yhi, K.k_binned[a][b]);
1531 }
1532 TH1F *frame = c->DrawFrame(xlo, ylo - 0.02, xhi, yhi + 0.02);
1533 frame->SetTitle(";log_{10}(#Deltat [#mus]);"
1534 "Relative Amplitude Shift");
1535 TLegend *leg = nullptr;
1536 if (K.n_amp > 1 || K.form_ok) {
1537 // The tail of every kernel runs to zero at the right, so the legend
1538 // goes into the right-hand corner: top for an overshoot, bottom otherwise.
1539 leg = RightLegend(K.n_amp * (K.form_ok ? 2 : 1), K.form_ok,
1540 !(TMath::Abs(ylo) > yhi), 0.45);
1541 if (K.form_ok)
1542 leg->SetHeader(Form("#tau = %.0f #mus%s", K.tau_us,
1543 K.tau_given ? " (given)" : " (profiled)"));
1544 }
1545 for (Int_t a = 0; a < K.n_amp; a++) {
1546 TGraph *gr = new TGraph();
1547 for (Int_t b = 0; b < kNBins; b++)
1548 gr->SetPoint(b, xlo + (b + 0.5) * (xhi - xlo) / kNBins, K.k_binned[a][b]);
1549 gr->SetMarkerStyle(20);
1550 gr->SetMarkerColor(colors[a % kMAX_AMP_BINS]);
1551 gr->SetLineColor(colors[a % kMAX_AMP_BINS]);
1552 gr->SetLineWidth(2);
1553 gr->Draw(K.form_ok ? "P SAME" : "PL SAME");
1554 // "< 0.5x beam, bins" per band, or just "Bins" for one kernel.
1555 const TString band = BandLabel(a, K.n_amp, kTRUE);
1556 const char *sep = K.n_amp > 1 ? ", " : "";
1557 if (leg)
1558 leg->AddEntry(gr,
1559 Form("%s%s%s%s", band.Data(), sep,
1560 K.n_amp > 1 ? "bins" : "Bins",
1561 K.form_ok && !K.form ? " (applied)" : ""),
1562 "p");
1563 if (K.form_ok) {
1564 const Int_t nf = 200;
1565 TGraph *gf = new TGraph();
1566 for (Int_t i = 0; i < nf; i++) {
1567 const Double_t l = xlo + (i + 0.5) * (xhi - xlo) / nf;
1568 gf->SetPoint(i, l, K.FormAt(a, TMath::Power(10.0, l)));
1569 }
1570 gf->SetLineColor(colors[a % kMAX_AMP_BINS]);
1571 gf->SetLineWidth(2);
1572 gf->SetLineStyle(K.form ? 1 : 2);
1573 gf->Draw("L SAME");
1574 leg->AddEntry(gf,
1575 Form("%s%s%s%s", band.Data(), sep,
1576 K.n_amp > 1 ? "form" : "Form",
1577 K.form ? " (applied)" : ""),
1578 "l");
1579 }
1580 }
1581 if (leg)
1582 leg->Draw();
1583 TLine *zero = new TLine(xlo, 0.0, xhi, 0.0);
1584 zero->SetLineStyle(2);
1585 zero->Draw();
1586 PlottingUtils::SaveFigure(c, fname, subdir, PlotSaveOptions::kLINEAR);
1587 delete c;
1588}
1589
1590// A palette for up to 16 channels on one canvas.
1591Int_t ChannelColor(Int_t k) {
1592 const Int_t colors[16] = {kBlack, kRed + 1, kAzure + 1, kGreen + 2,
1593 kOrange + 7, kMagenta + 1, kCyan + 2, kYellow + 2,
1594 kGray + 2, kRed - 7, kAzure - 3, kGreen - 6,
1595 kOrange - 3, kMagenta - 7, kCyan - 6, kPink + 7};
1596 return colors[k % 16];
1597}
1598
1599// One row of the pulse_history tree: the branch variables, registered by
1600// address in WriteToEventsFile. Group rows have Board and Channel -1.
1601struct PulseRow {
1602 Int_t group = 0, board = -1, channel = -1, n_amp = 1;
1603 Char_t name[32] = {0};
1604 Bool_t ok = kFALSE, enabled = kTRUE, from_group = kFALSE, form = kFALSE,
1605 form_ok = kFALSE, form_tau_given = kFALSE;
1606 Double_t form_tau_us = 0.0, form_tau_free_us = 0.0, r2_form_free = 0.0,
1607 r2_form = 0.0, r2_binned = 0.0, r2_group = 0.0;
1608 Double_t form_c[kMAX_AMP_BINS], form_cb[kMAX_AMP_BINS];
1609 Double_t k_binned[kMAX_AMP_BINS * kNBins], k[kMAX_AMP_BINS * kNBins],
1610 centre_us[kNBins];
1611 Double_t tau_us = 0.0, tau_err_us = 0.0, tau_p0 = 0.0, tau_p1 = 0.0,
1612 tau_p3 = 0.0;
1613 Bool_t tau_ok = kFALSE, tau_flat = kTRUE;
1614 Double_t intercept = 0.0, r2 = 0.0, rms_before = 0.0, rms_after = 0.0,
1615 mean_shift = 0.0, apply_max_us = 0.0;
1616 Long64_t n = 0, n_beam = 0, n_corrected = 0, n_clamped = 0;
1617};
1618
1619// Fill the tree's current row from a group or channel kernel and fill the
1620// row: `idx` is the channel index, -1 for a group row.
1621void FillPulseRow(PulseRow &row, const Kernel &K, Int_t g, Int_t idx,
1622 const Result &res, Int_t nch, TTree *t) {
1623 row.group = g;
1624 row.board = idx >= 0 ? idx / nch : -1;
1625 row.channel = idx >= 0 ? idx % nch : -1;
1626 strncpy(row.name, idx >= 0 ? res.name_ch[idx].Data() : GroupTag(g),
1627 sizeof(row.name) - 1);
1628 row.name[sizeof(row.name) - 1] = 0;
1629 row.ok = K.ok;
1630 row.enabled = res.enabled[g];
1631 row.from_group = K.from_group;
1632 row.form = K.form;
1633 row.form_ok = K.form_ok;
1634 row.form_tau_us = K.tau_us;
1635 row.form_tau_given = K.tau_given;
1636 row.form_tau_free_us = K.tau_free_us;
1637 row.r2_form_free = K.r2_form_free;
1638 row.r2_form = K.r2_form;
1639 row.r2_binned = K.r2_binned;
1640 row.r2_group = K.r2_group;
1641 row.n_amp = K.n_amp;
1642 for (Int_t a = 0; a < kMAX_AMP_BINS; a++) {
1643 row.form_c[a] = K.c[a];
1644 row.form_cb[a] = K.cb[a];
1645 for (Int_t b = 0; b < kNBins; b++) {
1646 row.k[a * kNBins + b] = K.k[a][b];
1647 row.k_binned[a * kNBins + b] = K.k_binned[a][b];
1648 }
1649 }
1650 for (Int_t b = 0; b < kNBins; b++)
1651 row.centre_us[b] = BinCentreUs(b);
1652 row.intercept = K.intercept;
1653 row.r2 = K.r2;
1654 row.rms_before = K.rms_before;
1655 row.rms_after = K.rms_after;
1656 row.mean_shift = res.mean_shift[g];
1657 row.apply_max_us = Constants::cfg.PULSE_HISTORY_APPLY_MAX_US;
1658 row.tau_ok = K.tau.ok;
1659 row.tau_flat = K.tau.flat;
1660 row.tau_us = K.tau.ok ? K.tau.p2 : 0.0;
1661 row.tau_err_us = K.tau.ok ? K.tau.p2err : 0.0;
1662 row.tau_p0 = K.tau.p0;
1663 row.tau_p1 = K.tau.p1;
1664 row.tau_p3 = K.tau.p3;
1665 row.n = K.n;
1666 row.n_beam = res.n_beam_events;
1667 row.n_corrected = res.n_corrected;
1668 row.n_clamped = res.n_clamped;
1669 t->Fill();
1670}
1671
1672} // namespace
1673
1674void SavePlots(Result &res, const TString &file_label) {
1675 if (!Constants::SavePlots())
1676 return;
1677 std::lock_guard<std::mutex> lock(g_plot_mutex);
1678 const TString subdir = "pulse_history/" + file_label;
1679 const TString subdir_ch = subdir + "/channels";
1680 const Double_t xlo = kLogLo + 6.0, xhi = kLogHi + 6.0;
1681 // Kernels: the group fit, one canvas per group.
1682 for (Int_t g = 1; g < kNGROUPS; g++)
1683 if (res.kernel[g].ok)
1684 DrawKernelFigure(res.kernel[g], subdir, Form("kernel_%s", GroupTag(g)));
1685 // And every channel's own, under channels/.
1686 for (Int_t c = 0; c < Int_t(res.kernel_ch.size()); c++)
1687 if (res.kernel_ch[c].ok && !res.kernel_ch[c].from_group)
1688 DrawKernelFigure(res.kernel_ch[c], subdir_ch,
1689 Form("kernel_%s", res.name_ch[c].Data()));
1690 // Per group, the applied kernels of its channels on one canvas (beam band),
1691 // the group's own dashed, so a channel that differs stands out.
1692 for (Int_t g = 1; g < kNGROUPS; g++) {
1693 const Kernel &G = res.kernel[g];
1694 if (!G.ok)
1695 continue;
1696 std::vector<Int_t> chans;
1697 for (Int_t c = 0; c < Int_t(res.kernel_ch.size()); c++)
1698 if (res.group_of[c] == g && res.kernel_ch[c].ok)
1699 chans.push_back(c);
1700 if (chans.empty())
1701 continue;
1702 const Int_t band = G.n_amp > 1 ? 1 : 0;
1703 Double_t ylo = 0.0, yhi = 0.0;
1704 for (size_t k = 0; k < chans.size(); k++)
1705 for (Int_t b = 0; b < kNBins; b++) {
1706 ylo = TMath::Min(ylo, res.kernel_ch[chans[k]].k[band][b]);
1707 yhi = TMath::Max(yhi, res.kernel_ch[chans[k]].k[band][b]);
1708 }
1709 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1710 TH1F *frame = c->DrawFrame(xlo, ylo - 0.02, xhi, yhi + 0.02);
1711 frame->SetTitle(";log_{10}(#Deltat [#mus]);Relative Amplitude Shift");
1712 TLegend *leg = RightLegend(Int_t(chans.size()) + 1, G.n_amp > 1,
1713 !(TMath::Abs(ylo) > yhi), 0.58, 2);
1714 if (G.n_amp > 1)
1715 leg->SetHeader(
1716 Form("Previous pulse %s", BandLabel(band, G.n_amp, kTRUE).Data()));
1717 for (size_t k = 0; k < chans.size(); k++) {
1718 const Kernel &K = res.kernel_ch[chans[k]];
1719 TGraph *gr = new TGraph();
1720 for (Int_t b = 0; b < kNBins; b++)
1721 gr->SetPoint(b, xlo + (b + 0.5) * (xhi - xlo) / kNBins, K.k[band][b]);
1722 gr->SetLineColor(ChannelColor(Int_t(k)));
1723 gr->SetMarkerColor(ChannelColor(Int_t(k)));
1724 gr->SetMarkerStyle(20);
1725 gr->SetMarkerSize(0.7);
1726 gr->SetLineWidth(2);
1727 gr->SetLineStyle(K.from_group ? 3 : 1);
1728 gr->Draw("PL SAME");
1729 leg->AddEntry(gr,
1730 Form("%s%s", res.name_ch[chans[k]].Data(),
1731 K.from_group ? " (group)" : ""),
1732 "l");
1733 }
1734 TGraph *gg = new TGraph();
1735 for (Int_t b = 0; b < kNBins; b++)
1736 gg->SetPoint(b, xlo + (b + 0.5) * (xhi - xlo) / kNBins, G.k[band][b]);
1737 gg->SetLineColor(kGray + 1);
1738 gg->SetLineWidth(3);
1739 gg->SetLineStyle(2);
1740 gg->Draw("L SAME");
1741 leg->AddEntry(gg, "Group fit", "l");
1742 leg->Draw();
1743 TLine *zero = new TLine(xlo, 0.0, xhi, 0.0);
1744 zero->SetLineStyle(2);
1745 zero->Draw();
1746 PlottingUtils::SaveFigure(c, Form("kernel_channels_%s", GroupTag(g)),
1747 subdir, PlotSaveOptions::kLINEAR);
1748 delete c;
1749 }
1750 for (Int_t g = 1; g < kNGROUPS; g++) {
1751 const char *gn = GroupTag(g);
1752 if (res.dev_vs_pred[g] && res.dev_vs_pred[g]->GetEntries() > 0) {
1753 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1754 c->SetLogz(kTRUE);
1755 PlottingUtils::ConfigureAndDraw2DHistogram(res.dev_vs_pred[g], c);
1756 TLine *diag = new TLine(-600.0, -600.0, 600.0, 600.0);
1757 diag->SetLineColor(kRed + 1);
1758 diag->SetLineStyle(2);
1759 diag->Draw();
1760 PlottingUtils::SaveFigure(c, Form("deviation_vs_predicted_%s", gn),
1761 subdir, PlotSaveOptions::kLINEAR);
1762 delete c;
1763 }
1764 if (res.dev_before[g] && res.dev_before[g]->GetEntries() > 0) {
1765 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kTRUE);
1766 PlottingUtils::ConfigureAndDrawHistogram(res.dev_before[g], kBlack);
1767 res.dev_after[g]->SetLineColor(kRed + 1);
1768 res.dev_after[g]->SetLineWidth(2);
1769 res.dev_after[g]->Draw("HIST SAME");
1770 TLegend *leg = RightLegend(2, kFALSE, kTRUE, 0.55);
1771 leg->AddEntry(res.dev_before[g],
1772 Form("Before, RMS %.1f", res.kernel[g].rms_before), "l");
1773 leg->AddEntry(res.dev_after[g],
1774 Form("After, RMS %.1f", res.kernel[g].rms_after), "l");
1775 leg->Draw();
1776 PlottingUtils::SaveFigure(c, Form("deviation_before_after_%s", gn),
1777 subdir, PlotSaveOptions::kLOG);
1778 delete c;
1779 }
1780 if (res.dtprev_before[g] && res.dtprev_before[g]->GetEntries() > 0) {
1781 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1782 res.dtprev_before[g]->SetMinimum(-300.0);
1783 res.dtprev_before[g]->SetMaximum(200.0);
1784 PlottingUtils::ConfigureAndDrawHistogram(res.dtprev_before[g], kBlack);
1785 res.dtprev_after[g]->SetLineColor(kRed + 1);
1786 res.dtprev_after[g]->SetMarkerColor(kRed + 1);
1787 res.dtprev_after[g]->SetLineWidth(2);
1788 res.dtprev_after[g]->Draw("SAME");
1789 TLegend *leg = RightLegend(2, kFALSE, kFALSE);
1790 leg->AddEntry(res.dtprev_before[g], "Before", "l");
1791 leg->AddEntry(res.dtprev_after[g], "After", "l");
1792 leg->Draw();
1793 PlottingUtils::SaveFigure(c, Form("deviation_vs_dt_previous_%s", gn),
1794 subdir, PlotSaveOptions::kLINEAR);
1795 delete c;
1796 }
1797 // The channels of the group after their own kernels, on one canvas:
1798 // a channel its kernel did not serve keeps a dt dependence here.
1799 for (Int_t pass = 0; pass < 2; pass++) {
1800 std::vector<TProfile *> &prof =
1801 pass == 0 ? res.dtprev_before_ch : res.dtprev_after_ch;
1802 std::vector<Int_t> chans;
1803 for (Int_t c = 0; c < Int_t(prof.size()); c++)
1804 if (res.group_of[c] == g && prof[c] && prof[c]->GetEntries() > 0)
1805 chans.push_back(c);
1806 if (chans.empty())
1807 continue;
1808 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1809 TProfile *first = prof[chans[0]];
1810 first->SetMinimum(pass == 0 ? -300.0 : -100.0);
1811 first->SetMaximum(pass == 0 ? 200.0 : 100.0);
1812 first->SetTitle(";log_{10}(#Deltat [#mus]);Channel #minus Mean [ADC]");
1813 PlottingUtils::ConfigureAndDrawHistogram(first, ChannelColor(0));
1814 TLegend *leg = RightLegend(Int_t(chans.size()), kTRUE, kFALSE, 0.58, 2);
1815 leg->SetHeader(pass == 0 ? "Before correction" : "After correction");
1816 leg->AddEntry(first, res.name_ch[chans[0]].Data(), "l");
1817 for (size_t k = 1; k < chans.size(); k++) {
1818 TProfile *p = prof[chans[k]];
1819 p->SetLineColor(ChannelColor(Int_t(k)));
1820 p->SetMarkerColor(ChannelColor(Int_t(k)));
1821 p->SetLineWidth(2);
1822 p->Draw("SAME");
1823 leg->AddEntry(p, res.name_ch[chans[k]].Data(), "l");
1824 }
1825 leg->Draw();
1826 PlottingUtils::SaveFigure(c,
1827 Form("deviation_vs_dt_channels_%s_%s",
1828 pass == 0 ? "before" : "after", gn),
1829 subdir, PlotSaveOptions::kLINEAR);
1830 delete c;
1831 }
1832 if (res.shift[g] && res.shift[g]->GetEntries() > 0) {
1833 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kTRUE);
1834 PlottingUtils::ConfigureAndDrawHistogram(res.shift[g], kBlack);
1835 PlottingUtils::SaveFigure(c, Form("applied_shift_%s", gn), subdir,
1836 PlotSaveOptions::kLOG);
1837 delete c;
1838 }
1839 }
1840 res.FreeDiagnostics();
1841}
1842
1843void WriteToEventsFile(const TString &events_subpath, const Result &res) {
1844 TFile *f = IO::OpenForWriting(events_subpath, "UPDATE");
1845 if (!f || f->IsZombie()) {
1846 if (f)
1847 delete f;
1848 return;
1849 }
1850 f->cd();
1851 if (TObject *old = f->Get("pulse_history"))
1852 old->Delete();
1853 // One entry per channel with a kernel (Board/Channel from the map) and
1854 // one per group (Board/Channel -1, Name the group's tag).
1855 TTree *t = new TTree("pulse_history",
1856 "Pulse-history kernel per channel and per group");
1857 PulseRow row;
1858 t->Branch("Group", &row.group, "Group/I");
1859 t->Branch("Board", &row.board, "Board/I");
1860 t->Branch("Channel", &row.channel, "Channel/I");
1861 t->Branch("Name", row.name, "Name/C");
1862 t->Branch("Ok", &row.ok, "Ok/O");
1863 // Whether PULSE_HISTORY_GROUPS had the group on; off means the group was
1864 // never fitted, as opposed to fitted and rejected (Ok false, Enabled true).
1865 t->Branch("Enabled", &row.enabled, "Enabled/O");
1866 // Channel rows only: the channel is corrected with the group's kernel
1867 // because its own could not be fitted.
1868 t->Branch("FromGroup", &row.from_group, "FromGroup/O");
1869 t->Branch("NAmpBins", &row.n_amp, "NAmpBins/I");
1870 // Kernel is what was applied, at the bin centres (the form evaluated there
1871 // when Form is true); KernelBinned the binned fit, always.
1872 t->Branch("Kernel", row.k, Form("Kernel[%d]/D", kMAX_AMP_BINS * kNBins));
1873 t->Branch("KernelBinned", row.k_binned,
1874 Form("KernelBinned[%d]/D", kMAX_AMP_BINS * kNBins));
1875 t->Branch("Form", &row.form, "Form/O");
1876 t->Branch("FormOk", &row.form_ok, "FormOk/O");
1877 t->Branch("FormTauUs", &row.form_tau_us, "FormTauUs/D");
1878 t->Branch("FormTauGiven", &row.form_tau_given, "FormTauGiven/O");
1879 t->Branch("FormTauFreeUs", &row.form_tau_free_us, "FormTauFreeUs/D");
1880 t->Branch("R2FormFree", &row.r2_form_free, "R2FormFree/D");
1881 t->Branch("FormC", row.form_c, Form("FormC[%d]/D", kMAX_AMP_BINS));
1882 t->Branch("FormCB", row.form_cb, Form("FormCB[%d]/D", kMAX_AMP_BINS));
1883 t->Branch("R2Form", &row.r2_form, "R2Form/D");
1884 t->Branch("R2Binned", &row.r2_binned, "R2Binned/D");
1885 t->Branch("R2Group", &row.r2_group, "R2Group/D");
1886 t->Branch("BinCentreUs", row.centre_us, Form("BinCentreUs[%d]/D", kNBins));
1887 t->Branch("Intercept", &row.intercept, "Intercept/D");
1888 t->Branch("R2", &row.r2, "R2/D");
1889 t->Branch("RmsBefore", &row.rms_before, "RmsBefore/D");
1890 t->Branch("RmsAfter", &row.rms_after, "RmsAfter/D");
1891 t->Branch("MeanShift", &row.mean_shift, "MeanShift/D");
1892 t->Branch("ApplyMaxUs", &row.apply_max_us, "ApplyMaxUs/D");
1893 // Decay-time summary of the raw dt profile (p0 + p1 exp(-dt/p2) + p3 dt);
1894 // see Kernel::TauFit. Flat groups have tau_ok and tau_flat with p2 = 0.
1895 t->Branch("TauOk", &row.tau_ok, "TauOk/O");
1896 t->Branch("TauFlat", &row.tau_flat, "TauFlat/O");
1897 t->Branch("TauUs", &row.tau_us, "TauUs/D");
1898 t->Branch("TauErrUs", &row.tau_err_us, "TauErrUs/D");
1899 t->Branch("TauP0", &row.tau_p0, "TauP0/D");
1900 t->Branch("TauAmp", &row.tau_p1, "TauAmp/D");
1901 t->Branch("TauLift", &row.tau_p3, "TauLift/D");
1902 t->Branch("N", &row.n, "N/L");
1903 t->Branch("NBeamEvents", &row.n_beam, "NBeamEvents/L");
1904 t->Branch("NCorrected", &row.n_corrected, "NCorrected/L");
1905 t->Branch("NClamped", &row.n_clamped, "NClamped/L");
1906 const Int_t nch = Constants::ActiveNChannels();
1907 for (Int_t g = 1; g < kNGROUPS; g++)
1908 FillPulseRow(row, res.kernel[g], g, -1, res, nch, t);
1909 for (Int_t c = 0; c < Int_t(res.kernel_ch.size()); c++)
1910 if (res.group_of[c] != kNONE)
1911 FillPulseRow(row, res.kernel_ch[c], res.group_of[c], c, res, nch, t);
1912 t->Write("pulse_history", TObject::kOverwrite);
1913 f->Close();
1914 delete f;
1915}
1916
1917} // namespace PulseHistory
Dataset configuration.
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
Definition FileSet.cpp:3
The per-group settings of the pulse-history correction.
Pole-zero pulse-history correction on the raw hit stream.
Double_t PULSE_HISTORY_APPLY_MAX_US
few hundred microseconds, so pulses beyond 100 us still move it.
Double_t ActiveReferenceChannelMaxAdc()
Upper energy gate on the seed channel, in ADC.
const TString & ActiveReferenceChannel()
Channel whose hits seed events.
Double_t ActiveStripEMaxAdc()
Upper bound of the per-strip energy range, in ADC.
Double_t ActiveEventTimeWindowUs()
Coincidence window for event building, in microseconds.
Bool_t SavePlots()
FileInSample(), by the name the plot call sites use.
const std::map< std::pair< Int_t, Int_t >, TString > & ActiveChannelMap()
The channel map in force.
Double_t ActiveReferenceChannelMinAdc()
Lower energy gate on the seed channel, in ADC.
Int_t ActiveNBoards()
Boards in the active epoch's setup.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
Int_t ActiveNChannels()
Channels per board.
std::ostream & DetailErr()
const PulseHistoryGroups & ActivePulseHistoryGroups()
The pulse-history groups in force: the active epoch's set, or the flat PULSE_HISTORY_GROUPS when no e...
Bool_t GroupWantsForm(Int_t g)
Whether the configuration asks for the pole-zero form on this group rather than the binned kernel.
@ kLONG_LEFT
Long end, left chain.
@ kNONE
Not part of any corrected group.
@ kSHORT_RIGHT
Short end, right chain.
@ kNGROUPS
Count of groups; not a group itself.
@ kSTRIP0
Strip 0, unsegmented.
@ kGRID
The Frisch grid: the seed channel, one per event.
@ kSTRIP17
Strip 17, unsegmented.
@ kSHORT_LEFT
Short end, left chain.
@ kLONG_RIGHT
Long end, right chain.
const Double_t kTauFitLoUs
const Double_t kTauGridLoUs
const Double_t kLogHi
Highest dt bin edge: log10 of 316 us in seconds.
Double_t GroupTauUs(Int_t g)
The preamp decay time the configuration gives for this group, in microseconds; 0 when it is to be pro...
Int_t ChainOf(Int_t g)
Which readout chain a group belongs to.
const Int_t kNBins
Bins in the kernel, spanning kLogLo to kLogHi in log10 of dt: twelve per decade, 0....
const Double_t kLogLo
Lowest dt bin edge: log10 of 1 us in seconds.
const Int_t kTauFitMinEntries
Below this many (event, channel) pairs in the window the fit is skipped.
void FitTauDecay(const std::vector< Double_t > &dt_us, const std::vector< Double_t > &dev, Kernel::TauFit &out)
p0 + p1 exp(-dt / p2) + p3 dt through (dt us, deviation ADC) pairs, the form the pole-zero residual p...
const Double_t kTauFitHiUs
Double_t BinCentreUs(Int_t b)
Centre of a kernel bin, in microseconds, for reports.
const Int_t kMAX_AMP_BINS
Maximum amplitude bands a kernel may split the previous pulse into.
const Int_t kNTauGrid
TString BandLabel(Int_t a, Int_t n_amp, Bool_t latex)
const char * GroupTag(Int_t g)
Short tag for filenames: L, R, Ls, Rs, S0, S17.
std::vector< Int_t > BuildGroupMap()
Group lookup for every (board, channel) under the active map.
const Long64_t kMinPairsPerChannel
Fewest (event, channel) pairs a channel needs for a kernel of its own; below it the channel is correc...
Int_t BinOf(Double_t dt_s)
Kernel bin for a time since the previous pulse.
void Apply(std::vector< RawHit > &hits, const std::vector< Int_t > &group_of, Result &res)
Apply the measured kernels to the hit stream, in place.
Bool_t Measure(std::vector< RawHit > &hits, const std::vector< Int_t > &group_of, Result &res, const TString &file_label)
Measure the kernels on this subfile's beam-like events.
Bool_t IsLongGroup(Int_t g)
Whether a group is a long end rather than a short end or an unsegmented strip.
void SavePlots(Result &res, const TString &file_label)
Draw and save the diagnostics, then free them.
Bool_t GroupEnabled(Int_t g)
Whether the groups in force (Constants::ActivePulseHistoryGroups()) have this group corrected.
const UInt_t kFlagClamped
Flag set on a hit whose correction was clamped rather than applied in full.
const Double_t kTauGridHiUs
Int_t AmpBinOf(Double_t e_prev, Double_t mode, Int_t n_amp)
Amplitude band of a previous pulse.
void WriteToEventsFile(const TString &events_subpath, const Result &res)
Record the kernels and counters alongside a subfile's events.
const char * GroupName(Int_t g)
Human-readable name of a Group.
TString Report(const Result &res, const TString &file_label, Bool_t detail=kTRUE)
Format the pass as a human-readable report.
Per-group pulse-history setting: whether the group is corrected and with which kernel shape.
Double_t tau_us
Known preamp decay time in microseconds, for the form.
Which channel groups the pulse-history correction fits and applies to, one setting per kernel group o...
PulseHistoryGroupOption long_right
Long end of the even strips (R).
PulseHistoryGroupOption strip17
Strip 17, unsegmented.
PulseHistoryGroupOption grid
The Frisch grid, the channel whose hits seed the events.
PulseHistoryGroupOption short_right
Short end of the odd strips (R).
PulseHistoryGroupOption strip0
Strip 0, unsegmented.
PulseHistoryGroupOption long_left
Long end of the odd strips (L).
PulseHistoryGroupOption short_left
Short end of the even strips (L).
Single-exponential summary of the dt profile for this group.
Double_t p0
Level at large dt, ADC.
Bool_t flat
Amplitude within 2 sigma of zero: no decay.
Double_t chi2
Chi2 of the fit over the window.
Double_t p3
Linear lift (the baseline term), ADC/us.
Int_t ndf
Its degrees of freedom.
Bool_t ok
Whether the fit converged.
Double_t p1err
Its error, ADC.
Double_t p2err
Its error, us.
Double_t p1
Tail amplitude at dt = 0, ADC.
Double_t p2
Decay time, us.
One fitted correction: a channel's own, or a group's.
Double_t rms_before
Channel-deviation RMS before correction.
Double_t t_m_us
Read point after a pulse: rise + peaking.
Double_t r2
Coefficient of determination.
Double_t tau_us
The applied form's decay time: given or profiled.
Int_t j_tau
Grid index of the applied tau; internal.
Double_t FormAt(Int_t a, Double_t dt_us) const
The form at one gap for one band, per unit previous height.
Double_t k_low[kMAX_AMP_BINS][kNBins]
Free coefficients, bins < n_free.
Long64_t n_beam
Beam-like events passing this group's selection.
Double_t EffectiveWindowUs(Int_t a) const
Effective restorer window implied by the fitted amplitudes, c tau / c_B, in microseconds; 0 when c_B ...
Double_t c[kMAX_AMP_BINS]
Direct-tail amplitude per band; > 0 undershoot.
Double_t r2_binned
And the binned fit's, for comparison.
Double_t k_binned[kMAX_AMP_BINS][kNBins]
The binned fit's coefficients, always kept so the form can be judged against them.
Double_t cb[kMAX_AMP_BINS]
Baseline-term amplitude per band.
Bool_t ok
Whether this kernel was successfully fitted.
Double_t t_lo_us
From where the form holds: (2 rise + flat) - t_m.
Int_t n_free
Bins below t_lo that stay free under the form.
Bool_t tau_given
Whether tau_us came from the configuration.
Long64_t n
(event, channel) pairs entering the fit.
TauFit tau
Summary of the dt profile, all hits.
Bool_t from_group
Channel kernels only: this is a copy of the group's kernel because the channel's own could not be fit...
Double_t tau_free_us
The profile's own preference, on the grid, and its fit quality: the check on a given tau_us.
Double_t r2_group
Channel kernels only: the R^2 the group's kernel reaches on this channel's pairs, so the gain from a ...
Double_t rms_after
And after, so the gain is visible.
Double_t t_f_us
Baseline freeze after a trigger: 2 rise + flat.
Int_t n_amp
Amplitude bands actually used, at most kMAX_AMP_BINS.
Kernel()
Construct unfitted, with zeroed coefficients.
Double_t k[kMAX_AMP_BINS][kNBins]
The applied kernel at the bin centres, [amplitude band][dt bin]: the binned coefficients themselves,...
Everything one subfile's pulse-history pass produced.
TH1D * dev_after[kNGROUPS]
And after.
std::vector< TProfile * > dtprev_before_ch
Per channel index, deviation against time to previous pulse before and after the channel's own kernel...
~Result()
Frees any diagnostic histograms still held.
Long64_t n_corrected
Hits that received a correction.
std::vector< Double_t > mode
Beam peak per (board, channel) index, in ADC.
TH2D * dev_vs_pred[kNGROUPS]
Channel deviation against predicted shift.
void FreeDiagnostics()
Delete every diagnostic histogram and null the pointers.
Long64_t n_beam_events
Beam-like events the fit drew on.
TProfile * dtprev_after[kNGROUPS]
And after.
Bool_t enabled[kNGROUPS]
Whether the group was configured for correction.
TProfile * dtprev_before[kNGROUPS]
Deviation against time to previous pulse.
Long64_t n_clamped_group[kNGROUPS]
Clamps per group.
Long64_t n_seeds
Hits usable as a previous pulse.
Bool_t sorted_input
Whether the input arrived time-ordered.
std::vector< Int_t > group_of
The group map Measure() was given.
std::vector< TString > name_ch
Channel-map name per index.
Double_t mean_shift[kNGROUPS]
Mean subtracted term per group, ADC.
Long64_t n_clamped
Corrections clamped rather than applied in full.
std::vector< Kernel > kernel_ch
The applied kernels, one per (board, channel) index as in the group map (empty before Measure()): the...
Kernel kernel[kNGROUPS]
The group fits, one per Group: each the fit over the sum of its channels, the fallback for a channel ...
TH1D * shift[kNGROUPS]
Shift applied per hit.
std::vector< TProfile * > dtprev_after_ch
Result()
Construct with zeroed counters and null diagnostics.
Long64_t n_hits
Hits examined.
TH1D * dev_before[kNGROUPS]
Deviation distribution before correction.