MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
StripSumScatter.cpp
Go to the documentation of this file.
1#include "StripSumScatter.hpp"
2#include "RegionCuts.hpp"
4#include <TParameter.h>
5#include <algorithm>
6#include <fstream>
7#include <sstream>
8
9const char *const kTagCutName[kNTAGCUTS] = {
10 "tagged", "upstream beam", "jump", "reac level",
11 "tail rise", "re-rise", "post above", "beam crossing",
12 "end strip", "cliff", "other strip"};
13const char *const kPreCutName[kNPRECUTS] = {
14 "reached tag", "all strips", "beam gate", "pileup",
15 "noise", "smoothness", "both mult"};
16
17namespace {
18// A beam-gate axis by name: "s<N>" / "strip N" for a strip, "grid" for the
19// grid (GATE_AXIS_GRID). The figure and histogram names keep the strip form.
20TString AxisTag(Int_t axis) {
21 return axis == GATE_AXIS_GRID ? TString("grid") : TString(Form("s%d", axis));
22}
23TString AxisName(Int_t axis) {
24 return axis == GATE_AXIS_GRID ? TString("grid")
25 : TString(Form("strip %d", axis));
26}
27// Axis title, as the events-summary figures write it.
28TString AxisTitle(Int_t axis) {
29 return axis == GATE_AXIS_GRID ? TString("Grid")
30 : TString(Form("Strip%d", axis));
31}
32// The pure-beam entrance ellipse's axes, per PURE_BEAM_GATE.
33void EntranceAxes(Int_t &sx, Int_t &sy) {
34 switch (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PURE_BEAM_GATE) {
36 sx = 1;
37 sy = 2;
38 break;
40 sx = 1;
41 sy = GATE_AXIS_GRID;
42 break;
43 default:
44 sx = 0;
45 sy = 1;
46 }
47}
48} // namespace
49
51 for (Int_t i = 0; i < 64; i++) {
52 m_yLo[i] = 0.0;
53 m_yHi[i] = 0.0;
54 }
55 m_nSeen = 0;
56 m_nNormed = 0;
57 m_normedAt.assign(
58 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MAX -
59 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN + 1,
60 0);
61 m_tagged.assign(
62 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MAX -
63 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN + 1,
64 0);
65 m_preCounts.assign(kNPRECUTS, 0);
66 m_cutCounts.assign(m_tagged.size() * kNTAGCUTS, 0);
67}
68
70 std::map<Int_t, TH2F *>::iterator it;
71 for (it = m_scatter.begin(); it != m_scatter.end(); ++it)
72 delete it->second;
73 m_scatter.clear();
74}
75
76Int_t StripSumScatter::ReacIndex(Int_t reac) {
77 return reac - Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN;
78}
79
80Int_t StripSumScatter::YLoOf(Int_t reac) { return reac + 1; }
81
82Int_t StripSumScatter::YHiOf(Int_t reac) {
83 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
84 // The window shrinks where it would otherwise run past the last strip it
85 // may reach, and is never shorter than one strip.
86 const Int_t hi = TMath::Min(reac + C.POST_TRIGGER_SUM_STRIPS,
87 TMath::Min(C.POST_WINDOW_LAST_STRIP, 17));
88 return TMath::Max(hi, reac + 1);
89}
90
91Double_t StripSumScatter::s_stripSigma[18] = {0.0};
92Double_t StripSumScatter::s_stripMean[18] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
93 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
94 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
95
96Double_t StripSumScatter::StripSigma(Int_t strip) {
97 return (strip >= 0 && strip < 18) ? s_stripSigma[strip] : 0.0;
98}
99
100Double_t StripSumScatter::StripMean(Int_t strip) {
101 return (strip >= 0 && strip < 18) ? s_stripMean[strip] : 1.0;
102}
103
104void StripSumScatter::SetStripNoise(const Double_t *mean,
105 const Double_t *sigma) {
106 for (Int_t s = 0; s < 18; s++) {
107 s_stripMean[s] = mean ? mean[s] : 1.0;
108 s_stripSigma[s] = sigma[s];
109 }
110}
111
133
134std::vector<std::pair<TString, TagThresholds>>
136 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
137 std::vector<std::pair<TString, TagThresholds>> out;
138 if (!C.CUT_VARIATION)
139 return out;
140 const TagThresholds nom = NominalThresholds();
141 const Double_t ds = C.CUT_VARIATION_NSIGMA_STEP;
142 // One threshold at a time, up and down; only the conditions that are on.
143 // The sign convention is the threshold's own: "+" is the larger value.
144 struct Var {
145 const char *name;
146 Double_t TagThresholds::*field;
147 Bool_t on;
148 Double_t step;
149 Bool_t any_sign; // the threshold stays meaningful at or below zero
150 };
151 const Var vars[] = {
152 // The beam selection: the event-level cuts every count and denominator
153 // pass through.
154 {"gate", &TagThresholds::gate_nsigma, nom.gate_nsigma > 0.0, ds, kFALSE},
155 {"pileup", &TagThresholds::pileup_nsigma, nom.pileup_nsigma > 0.0, ds,
156 kFALSE},
157 {"noise", &TagThresholds::noise_nsigma, nom.noise_nsigma > 0.0, ds,
158 kFALSE},
160 kFALSE},
161 {"jump", &TagThresholds::jump_nsigma, kTRUE, ds, kFALSE},
162 {"smooth", &TagThresholds::smooth_nsigma, nom.smooth_nsigma > 0.0, ds,
163 kFALSE},
165 nom.tail_rise_nsigma > 0.0 && nom.tail_fall_from_strip > 0, ds, kFALSE},
167 nom.tail_rerise_nsigma > 0.0, ds, kFALSE},
169 nom.tail_rerise_nsigma > 0.0, ds, kFALSE},
171 nom.post_above_nsigma > 0.0 && nom.post_above_strips > 0, ds, kFALSE},
172 // The end strip is always tested; its threshold, mean - n sigma, is
173 // meaningful for any n, so the shift is never skipped.
174 {"end", &TagThresholds::end_strip_nsigma, kTRUE, ds, kTRUE},
175 // A count: the variation is one strip either way (never down to off).
176 {"cross", &TagThresholds::cross_min_strip, nom.cross_min_strip > 0.0, 1.0,
177 kFALSE},
178 {"cliff", &TagThresholds::cliff_max, nom.cliff_max > 0.0,
179 C.CUT_VARIATION_CLIFF_STEP, kFALSE},
180 };
181 for (size_t v = 0; v < sizeof(vars) / sizeof(vars[0]); v++) {
182 if (!vars[v].on || !(vars[v].step > 0.0))
183 continue;
184 for (Int_t dir = +1; dir >= -1; dir -= 2) {
185 TagThresholds T = nom;
186 T.*(vars[v].field) = nom.*(vars[v].field) + dir * vars[v].step;
187 // A variation must not switch a condition off (or on): keep it above 0
188 // where zero means off.
189 if (!vars[v].any_sign && !(T.*(vars[v].field) > 0.0))
190 continue;
191 out.push_back(
192 std::make_pair(TString(vars[v].name) + (dir > 0 ? "+" : "-"), T));
193 }
194 }
195 return out;
196}
197
198Double_t StripSumScatter::JumpMin(Int_t reac) {
199 return Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REAC_JUMP_NSIGMA *
200 StripSigma(reac);
201}
202
203Bool_t StripSumScatter::BeamUpstreamOf(const EnergyView &ev, Int_t reac,
204 const TagThresholds &T) {
205 if (!T.require_upstream)
206 return kTRUE;
207 for (Int_t s = 1; s < reac; s++)
208 if (TMath::Abs(ev.Total(s) - StripMean(s)) >
210 return kFALSE;
211 return kTRUE;
212}
213
214Bool_t StripSumScatter::BeamUpstreamOf(const EnergyView &ev, Int_t reac) {
215 return BeamUpstreamOf(ev, reac, NominalThresholds());
216}
217
219 switch (c) {
220 case kCUT_UPSTREAM:
221 return T.require_upstream;
222 case kCUT_JUMP:
223 case kCUT_REAC_LEVEL:
224 case kCUT_END_STRIP:
225 return kTRUE;
226 case kCUT_TAIL_RISE:
227 return T.tail_rise_nsigma > 0.0 && T.tail_fall_from_strip > 0;
228 case kCUT_RERISE:
229 return T.tail_rerise_nsigma > 0.0;
230 case kCUT_POST_ABOVE:
231 return T.post_above_nsigma > 0.0 && T.post_above_strips > 0;
232 case kCUT_CROSS:
233 return T.cross_min_strip > 0.0;
234 case kCUT_CLIFF:
235 return T.cliff_max > 0.0;
236 default:
237 return kFALSE;
238 }
239}
240
247Bool_t StripSumScatter::Condition(TagCut c, const EnergyView &ev, Int_t reac,
248 const TagThresholds &T) {
249 const Int_t kLast = Constants::cfg.IGNORE_STRIP_17 ? 16 : 17;
250 const Int_t end_strip =
251 (Constants::cfg.IGNORE_STRIP_17 ||
252 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REQUIRE_STRIP_16_BELOW_BEAM)
253 ? 16
254 : 17;
255 switch (c) {
256 case kCUT_UPSTREAM:
257 // Otherwise a reaction at an earlier strip can pass this strip's jump
258 // gate on a noise fluctuation and be counted here as well.
259 return BeamUpstreamOf(ev, reac, T);
260 case kCUT_JUMP:
261 return ev.Total(reac) - ev.Total(reac - 1) >
262 T.jump_nsigma * StripSigma(reac);
263 case kCUT_REAC_LEVEL:
264 return ev.Total(reac) > StripMean(reac) + T.jump_nsigma * StripSigma(reac);
265 case kCUT_TAIL_RISE: {
266 // Falling tail: no rise beyond the noise from the start strip on.
267 const Int_t start = TMath::Max(reac + 1, T.tail_fall_from_strip + 1);
268 for (Int_t s = start; s <= kLast; s++)
269 if (StripSigma(s) > 0.0 &&
270 ev.Total(s) - ev.Total(s - 1) > T.tail_rise_nsigma * StripSigma(s))
271 return kFALSE;
272 return kTRUE;
273 }
274 case kCUT_RERISE: {
275 // No return: back at the beam after the peak, then above it again.
276 Int_t peak = reac;
277 for (Int_t s = reac + 1; s <= TMath::Min(YHiOf(reac), kLast); s++)
278 if (ev.Total(s) > ev.Total(peak))
279 peak = s;
280 Bool_t returned = kFALSE;
281 for (Int_t s = peak + 1; s <= kLast; s++) {
282 if (!(StripSigma(s) > 0.0))
283 continue;
284 if (returned &&
285 ev.Total(s) > StripMean(s) + T.tail_rerise_nsigma * StripSigma(s))
286 return kFALSE;
287 if (ev.Total(s) <= StripMean(s) + T.tail_return_nsigma * StripSigma(s))
288 returned = kTRUE;
289 }
290 return kTRUE;
291 }
292 case kCUT_POST_ABOVE:
293 // Persistence: the excess must hold for post_above_strips strips
294 // after the reaction, each more than post_above_nsigma of its spread.
295 for (Int_t s = reac + 1; s <= TMath::Min(reac + T.post_above_strips, kLast);
296 s++)
297 if (StripSigma(s) > 0.0 &&
298 !(ev.Total(s) > StripMean(s) + T.post_above_nsigma * StripSigma(s)))
299 return kFALSE;
300 return kTRUE;
301 case kCUT_CROSS: {
302 // The residue's range: the trace may not read below the beam before
303 // strip cross_min_strip (an absolute strip); the window is reac+1..S.
304 const Int_t upto = TMath::Min(Int_t(T.cross_min_strip + 0.5), kLast);
305 for (Int_t s = reac + 1; s <= upto; s++)
306 if (StripSigma(s) > 0.0 && ev.Total(s) < StripMean(s))
307 return kFALSE;
308 return kTRUE;
309 }
310 case kCUT_END_STRIP:
311 // The end strip below the beam by n sigma of its own spread: a residue
312 // that has stopped (or is stopping) reads low there, the beam does not.
313 return ev.Total(end_strip) <
314 StripMean(end_strip) - T.end_strip_nsigma * StripSigma(end_strip);
315 case kCUT_CLIFF: {
316 // A stop is gradual: the last step may take only part of the fall from
317 // the peak; the elastic class keeps its excess to end_strip-1.
318 if (!(end_strip - 1 > reac))
319 return kTRUE;
320 Double_t peak = ev.Total(reac);
321 for (Int_t s = reac + 1; s < end_strip; s++)
322 peak = TMath::Max(peak, ev.Total(s));
323 const Double_t fall = peak - ev.Total(end_strip);
324 const Double_t last = ev.Total(end_strip - 1) - ev.Total(end_strip);
325 return !(fall > 0.0 && last / fall > T.cliff_max);
326 }
327 default:
328 return kTRUE;
329 }
330}
331
333 const TagThresholds &T) {
334 for (Int_t c = kCUT_TAIL_RISE; c <= kCUT_POST_ABOVE; c++)
335 if (ConditionActive(TagCut(c), T) && !Condition(TagCut(c), ev, reac, T))
336 return TagCut(c);
337 return kTAG_PASS;
338}
339
340// Sequential: the first active condition that fails, in enum order, so the
341// cut report reads as a funnel.
343 const TagThresholds &T) {
344 for (Int_t c = kCUT_UPSTREAM; c <= kCUT_CLIFF; c++)
345 if (ConditionActive(TagCut(c), T) && !Condition(TagCut(c), ev, reac, T))
346 return TagCut(c);
347 return kTAG_PASS;
348}
349
351 return RejectReason(ev, reac, NominalThresholds());
352}
353
355 const std::vector<Bool_t> &pass) {
356 const Int_t kReacMin =
357 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN;
358 Int_t best = -1;
359 Double_t best_jump = -1.0e9;
360 for (Int_t k = 0; k < Int_t(pass.size()); k++) {
361 if (!pass[k])
362 continue;
363 const Int_t reac = kReacMin + k;
364 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.TAG_RESOLVE ==
366 return reac;
367 const Double_t sig = StripSigma(reac);
368 const Double_t jump =
369 (ev.Total(reac) - ev.Total(reac - 1)) / (sig > 0.0 ? sig : 1.0);
370 if (jump > best_jump) {
371 best_jump = jump;
372 best = reac;
373 }
374 }
375 return best;
376}
377
378namespace {
379
380// One step of the selection description: the condition, what an event must
381// satisfy to go on, and the cut count it counts to.
382void AddSelectionStep(std::vector<SelectionStep> &out,
383 SelectionStep::Stage stage, const char *id,
384 const char *name, const TString &detail, Bool_t on,
385 Int_t pre_cut, Int_t tag_cut) {
386 SelectionStep st;
387 st.stage = stage;
388 st.id = id;
389 st.name = name;
390 st.detail = detail;
391 st.on = on;
392 st.pre_cut = pre_cut;
393 st.tag_cut = tag_cut;
394 out.push_back(st);
395}
396
397// "fewer than 1" reads better as "none".
398TString FewerStrips(Int_t n) {
399 return n <= 1 ? TString("none") : TString(Form("fewer than %d", n));
400}
401
402} // namespace
403
404// The selection in words, one step per condition in the order RejectReason
405// and the fill apply them; disabled steps stay in the list with `on` false.
406std::vector<SelectionStep> StripSumScatter::DescribeSelection() {
407 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
408 const Bool_t ign0 = Constants::cfg.IGNORE_STRIP_0;
409 const Bool_t req0 = !ign0 && Constants::cfg.REQUIRE_STRIP_0;
410 const Bool_t ign17 = Constants::cfg.IGNORE_STRIP_17;
411 const Int_t last = ign17 ? 16 : 17;
412 const Int_t end_strip = (ign17 || C.REQUIRE_STRIP_16_BELOW_BEAM) ? 16 : 17;
413 std::vector<SelectionStep> out;
414
415 Int_t ent_x = 0, ent_y = 1;
416 EntranceAxes(ent_x, ent_y);
417 const TString entrance = AxisName(ent_x) + " vs " + AxisName(ent_y);
418 // Input: what the numbers below are measured against.
419 AddSelectionStep(
420 out, SelectionStep::kINPUT, "events", "calibrated events",
421 Form("E(s) = strip total in beam units (beam = 1)%s; beam gates fitted "
422 "per %s",
423 Constants::ActiveIgnoreShortStrips() ? ", long ends only" : "",
424 Constants::ActiveUseSolarisData() ? "run" : "subfile"),
425 kTRUE, -1, -1);
426 AddSelectionStep(
427 out, SelectionStep::kINPUT, "beam_ref", "beam reference",
428 Form("beam mean and σ per strip from the first %s's pure-beam sample: "
429 "every strip fired, inside the entrance (%s) and exit (%s) "
430 "ellipses at %.1f σ, 3σ-clipped",
431 Constants::ActiveUseSolarisData() ? "run" : "subfile",
432 entrance.Data(), ign17 ? "strips 15 vs 16" : "strips 16 vs 17",
434 kTRUE, -1, -1);
435
436 // Event level, in the order of the fill loop.
437 AddSelectionStep(
438 out, SelectionStep::kEVENT_LEVEL, "all_strips",
440 Form("every strip %d-%d read above zero", req0 ? 0 : 1, last), kTRUE,
441 kPRE_ALL_STRIPS, -1);
442 const TString gates =
443 C.GATE_NSIGMA > 0.0
444 ? TString(Form("strip %d within %.1f σ of its fitted beam peak",
446 : TString("no beam gate");
447 AddSelectionStep(out, SelectionStep::kEVENT_LEVEL, "gate",
448 kPreCutName[kPRE_GATE], gates, kTRUE, kPRE_GATE, -1);
449 AddSelectionStep(
451 Form("%s of strips 1-16 at or above beam + %.1f σ",
452 FewerStrips(C.PILEUP_MIN_STRIPS).Data(), C.PILEUP_NSIGMA),
453 C.PILEUP_NSIGMA > 0.0, kPRE_PILEUP, -1);
454 AddSelectionStep(out, SelectionStep::kEVENT_LEVEL, "noise",
456 Form("%s of strips 1-16 at or below beam − %.1f σ",
457 FewerStrips(C.NOISE_MIN_STRIPS).Data(), C.NOISE_NSIGMA),
458 C.NOISE_NSIGMA > 0.0, kPRE_NOISE, -1);
459 AddSelectionStep(
461 Form("every step between strips 1-%d within %.1f σ of the later "
462 "strip's spread, the single largest rise excepted",
463 last, C.SMOOTHNESS_NSIGMA),
464 C.SMOOTHNESS_NSIGMA > 0.0, kPRE_SMOOTH, -1);
465 AddSelectionStep(
466 out, SelectionStep::kEVENT_LEVEL, "both_mult",
468 Form("at most %d of strips 1-%d with both ends fired (raw ADC)",
469 C.BOTH_MULT_MAX, TMath::Min(16, C.BOTH_MULT_COUNT_TO)),
470 C.BOTH_MULT_MAX >= 0, kPRE_BOTH_MULT, -1);
471
472 // Per reaction strip, in the order of RejectReason. The loop marker first:
473 // it carries the strip range and is not a cut.
474 AddSelectionStep(
475 out, SelectionStep::kPER_STRIP, "reac_loop",
476 Form("for each reaction strip reac = %d..%d", C.REACTION_STRIP_MIN,
478 "every strip is tested on its own; the first failing condition ends "
479 "its test",
480 kTRUE, -1, -1);
481 AddSelectionStep(out, SelectionStep::kPER_STRIP, "upstream",
483 Form("every strip 1..reac−1 within %.1f σ of the beam",
486 AddSelectionStep(
488 Form("E(reac) − E(reac−1) > %.1f σ(reac)", C.REAC_JUMP_NSIGMA), kTRUE, -1,
489 kCUT_JUMP);
490 AddSelectionStep(out, SelectionStep::kPER_STRIP, "reac_level",
492 Form("E(reac) > beam + %.1f σ(reac)", C.REAC_JUMP_NSIGMA),
493 kTRUE, -1, kCUT_REAC_LEVEL);
494 AddSelectionStep(
496 Form("no step up above %.1f σ from strip max(reac, %d)+1 to %d",
498 C.TAIL_FALL_FROM_STRIP > 0 && C.TAIL_RISE_NSIGMA > 0.0, -1,
500 AddSelectionStep(
502 Form("once back within %.1f σ of the beam after the peak, never above "
503 "beam + %.1f σ again",
505 C.TAIL_RERISE_NSIGMA > 0.0, -1, kCUT_RERISE);
506 AddSelectionStep(out, SelectionStep::kPER_STRIP, "post_above",
508 Form("strips reac+1 to reac+%d all above beam + %.1f σ",
510 C.POST_ABOVE_NSIGMA > 0.0 && C.POST_ABOVE_STRIPS > 0, -1,
512 AddSelectionStep(
514 Form("strips reac+1 through %d all at or above the beam mean: the "
515 "residue reaches strip %d before crossing the beam",
518 AddSelectionStep(
520 Form("strip %d below beam − %.1f σ", end_strip, C.END_STRIP_NSIGMA),
521 kTRUE, -1, kCUT_END_STRIP);
522 AddSelectionStep(
524 Form("the last step, strip %d to %d, at most %.0f%% of the fall from "
525 "the peak",
526 end_strip - 1, end_strip, 100.0 * C.TAIL_CLIFF_MAX_FRACTION),
528 AddSelectionStep(
531 ? "one tag per event: the first strip that passes takes it"
532 : "one tag per event: the passing strip with the largest jump takes "
533 "it",
534 kTRUE, -1, kCUT_OTHER_TAG);
535
536 // What becomes of a tag.
537 AddSelectionStep(out, SelectionStep::kOUTCOME, "tagged", "tagged",
538 "a reaction at reac; one strip per event", kTRUE, -1,
539 kTAG_PASS);
540 AddSelectionStep(
541 out, SelectionStep::kOUTCOME, "n_beam", "beam count",
542 "events past the event-level cuts with beam upstream of reac and no "
543 "tag before it: the beam incident on reac, the denominator",
544 kTRUE, -1, -1);
546 TString sys = "no cut variation";
547 if (C.CUT_VARIATION)
548 sys = Form("systematic: each beam-selection and identification "
549 "condition shifted ±%.1f σ (cliff ±%.2f), one at a time, "
550 "largest change of σ per condition in quadrature, with the "
551 "gas pressure uncertainty",
553 AddSelectionStep(
554 out, SelectionStep::kOUTCOME, "xs", "cross section, all tagged",
555 "σ(reac) = tagged / (beam count × target atoms per strip); " + sys,
556 kTRUE, -1, -1);
557 } else {
558 AddSelectionStep(
559 out, SelectionStep::kOUTCOME, "xs", "cross section, mixture fit",
560 Form("scatter x = ΣE(%d..%d), y = ΣE(reac+1..reac+%d)%s; bivariate "
561 "Gaussian mixture per strip, (a,n) = reaction component within "
562 "%.1f σ, count as attributed by the fit",
564 C.Y_RATIO_TO_UPSTREAM ? " over the upstream mean" : "",
566 kTRUE, -1, -1);
567 }
568 return out;
569}
570
571// Sigma-clipped mean and width, started from the median and MAD, not the
572// RMS: never sheds a second population (0.41 a.u. vs 0.06 on 37Cl 97).
573static Double_t ClippedWidth(const std::vector<Double_t> &values,
574 Double_t *mean_out = nullptr) {
575 const Int_t kClipPasses = 3;
576 const Double_t kClipNSigma = 3.0;
577 // The variance a Gaussian keeps inside the clip (0.973 at 3 sigma), so the
578 // width is a Gaussian's sigma, as BeamFitUtils::ClippedMoments' is.
579 const Double_t kKeptVar =
580 1.0 - 2.0 * kClipNSigma * TMath::Gaus(kClipNSigma, 0.0, 1.0, kTRUE) /
581 TMath::Erf(kClipNSigma / TMath::Sqrt2());
582 if (mean_out)
583 *mean_out = 0.0;
584 if (values.size() < 2)
585 return 0.0;
586 std::vector<Double_t> sorted(values);
587 std::sort(sorted.begin(), sorted.end());
588 Double_t mean = sorted[sorted.size() / 2];
589 for (size_t k = 0; k < sorted.size(); k++)
590 sorted[k] = TMath::Abs(sorted[k] - mean);
591 std::sort(sorted.begin(), sorted.end());
592 Double_t width = 1.4826 * sorted[sorted.size() / 2];
593 if (!(width > 0.0))
594 return 0.0;
595 for (Int_t pass = 0; pass < kClipPasses; pass++) {
596 Double_t sum = 0.0, sum2 = 0.0;
597 Long64_t kept = 0;
598 for (Int_t k = 0; k < Int_t(values.size()); k++) {
599 const Double_t v = values[k];
600 if (TMath::Abs(v - mean) > kClipNSigma * width)
601 continue;
602 sum += v;
603 sum2 += v * v;
604 kept++;
605 }
606 if (kept < 2)
607 break;
608 mean = sum / Double_t(kept);
609 width = TMath::Sqrt(TMath::Max(0.0, sum2 / Double_t(kept) - mean * mean) /
610 kKeptVar);
611 }
612 if (mean_out)
613 *mean_out = mean;
614 return width;
615}
616
617Bool_t StripSumScatter::MeasureBeamNoise(TChain *chain, const BeamEllipses &be,
618 Double_t *strip_mean,
619 Double_t *strip_sigma) {
620 // Enough gated events for a percent-level width on every strip; the gate
621 // passes about half of what it sees.
622 const Long64_t kMaxGated = 100000;
623 const Long64_t kMinEvents = 1000;
624 for (Int_t s = 0; s < 18; s++) {
625 strip_mean[s] = 1.0;
626 strip_sigma[s] = 0.0;
627 }
628 if (!chain || !be.ok)
629 return kFALSE;
630
631 EnergyView ev;
632 ev.Attach(chain);
633 EnableEventBranches(chain);
634 const Long64_t n = chain->GetEntries();
635 std::vector<std::vector<Double_t>> deposit(18);
636 for (Long64_t j = 0; j < n && Long64_t(deposit[1].size()) < kMaxGated; j++) {
637 chain->GetEntry(j);
638 ev.Decode();
639 // The beam sample: every strip fired and inside the entrance and exit
640 // ellipses, the same selection the scatters call pure beam.
641 if (!IsPureBeam(ev, be))
642 continue;
643 for (Int_t s = 0; s < 18; s++)
644 deposit[s].push_back(ev.Total(s));
645 }
646 chain->ResetBranchAddresses();
647 if (Long64_t(deposit[1].size()) < kMinEvents)
648 return kFALSE;
649
650 // Per strip, the mean and width of that sample, clipped for the residual
651 // partial pile-up and reactions the end gates do not see.
652 for (Int_t s = 0; s < 18; s++) {
653 Double_t mean = 0.0;
654 strip_sigma[s] = ClippedWidth(deposit[s], &mean);
655 if (mean > 0.0)
656 strip_mean[s] = mean;
657 }
658 for (Int_t s = 1; s <= 16; s++)
659 if (!(strip_sigma[s] > 0.0))
660 return kFALSE;
661 return kTRUE;
662}
663
664void StripSumScatter::EnableEventBranches(TChain *chain) {
665 chain->SetBranchStatus("*", 0);
666 chain->SetBranchStatus("LeftdE", 1);
667 chain->SetBranchStatus("RightdE", 1);
668 chain->SetBranchStatus("Strip0dE", 1);
669 chain->SetBranchStatus("Strip17dE", 1);
670 chain->SetBranchStatus("Cathode", 1);
671 chain->SetBranchStatus("Grid", 1);
672 // Absent in files built before the branch existed; harmless to enable.
673 if (chain->GetBranch("SeedTs"))
674 chain->SetBranchStatus("SeedTs", 1);
675}
676
677Bool_t StripSumScatter::AllStripsFired(const EnergyView &ev) {
678 if (!Constants::cfg.IGNORE_STRIP_0 && Constants::cfg.REQUIRE_STRIP_0 &&
679 !(ev.strip0 > 0.0))
680 return kFALSE;
681 if (!Constants::cfg.IGNORE_STRIP_17 && !(ev.strip17 > 0.0))
682 return kFALSE;
683 for (Int_t s = 1; s <= 16; s++)
684 if (!(ev.Total(s) > 0.0))
685 return kFALSE;
686 return kTRUE;
687}
688
689Bool_t StripSumScatter::IsPureBeam(const EnergyView &ev,
690 const BeamEllipses &be) {
691 if (!be.ok)
692 return kFALSE;
693 if (!AllStripsFired(ev))
694 return kFALSE;
695 // Must pass BOTH the entrance AND exit ellipses, at the beam sample's own
696 // level (PURE_BEAM_NSIGMA), not the event-level gate's.
698 switch (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PURE_BEAM_GATE) {
700 if (!PassesEllipse(be.s1_s2, ev, 1, 2, n))
701 return kFALSE;
702 break;
704 if (!PassesEllipse(be.s1_grid, ev, 1, GATE_AXIS_GRID, n))
705 return kFALSE;
706 break;
707 default:
708 if (!PassesEllipse(be.s0_s1, ev, 0, 1, n))
709 return kFALSE;
710 }
711 if (be.use_s15_s16) {
712 if (!PassesEllipse(be.s15_s16, ev, 15, 16, n))
713 return kFALSE;
714 } else {
715 if (!PassesEllipse(be.s16_s17, ev, 16, 17, n))
716 return kFALSE;
717 }
718 return kTRUE;
719}
720
722 return IsPileup(ev, Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PILEUP_NSIGMA);
723}
724
725Bool_t StripSumScatter::IsPileup(const EnergyView &ev, Double_t kNSigma) {
726 const Int_t kMinStrips =
727 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PILEUP_MIN_STRIPS;
728 if (!(kNSigma > 0.0))
729 return kFALSE;
730 Int_t n = 0;
731 // A strip whose spread is not yet measured cannot be judged: the cut is
732 // off there, which is what makes MeasureBeamNoise itself unaffected by it.
733 for (Int_t s = 1; s <= 16; s++)
734 if (StripSigma(s) > 0.0 &&
735 ev.Total(s) >= StripMean(s) + kNSigma * StripSigma(s) &&
736 ++n >= kMinStrips)
737 return kTRUE;
738 return kFALSE;
739}
740
742 return IsNoise(ev, Constants::cfg.STRIP_SUM_SCATTER_CONFIG.NOISE_NSIGMA);
743}
744
745Bool_t StripSumScatter::IsNoise(const EnergyView &ev, Double_t kNSigma) {
746 const Int_t kMinStrips =
747 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.NOISE_MIN_STRIPS;
748 if (!(kNSigma > 0.0))
749 return kFALSE;
750 Int_t n = 0;
751 for (Int_t s = 1; s <= 16; s++)
752 if (StripSigma(s) > 0.0 &&
753 ev.Total(s) <= StripMean(s) - kNSigma * StripSigma(s) &&
754 ++n >= kMinStrips)
755 return kTRUE;
756 return kFALSE;
757}
758// The largest step with its single largest rise exempted: a reaction is
759// one rise then gradual change; strip 0 stays out (its own scale).
761 const Int_t kLast = Constants::cfg.IGNORE_STRIP_17 ? 16 : 17;
762 Double_t worst = 0.0, best_rise = 0.0, second_rise = 0.0;
763 for (Int_t s = 2; s <= kLast; s++) {
764 if (!(StripSigma(s) > 0.0))
765 continue;
766 const Double_t d = (ev.Total(s) - ev.Total(s - 1)) / StripSigma(s);
767 if (d > 0.0) {
768 // Rises are ranked: the largest is exempt, the runner-up counts.
769 if (d > best_rise) {
770 second_rise = best_rise;
771 best_rise = d;
772 } else if (d > second_rise) {
773 second_rise = d;
774 }
775 } else if (-d > worst) {
776 worst = -d;
777 }
778 }
779 return TMath::Max(worst, second_rise);
780}
781
782Bool_t StripSumScatter::IsSmooth(const EnergyView &ev, Double_t nsigma) {
783 return !(nsigma > 0.0) || MaxStepNSigma(ev) <= nsigma;
784}
785
786Double_t StripSumScatter::SumRange(const Double_t *total, Int_t lo, Int_t hi) {
787 Double_t sum = 0.0;
788 for (Int_t s = lo; s <= hi; s++)
789 sum += total[s];
790 return sum;
791}
792
793void StripSumScatter::PlaneXY(const Double_t *total, Int_t reac, Double_t &x,
794 Double_t &y) {
795 x = SumRange(total, Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO,
796 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI);
797 y = SumRange(total, YLoOf(reac), YHiOf(reac));
798 // The published macros' `ratio`: the post window over the event's own
799 // upstream mean, so per-event jitter common to every strip cancels.
800 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.Y_RATIO_TO_UPSTREAM && reac > 1) {
801 const Double_t up = SumRange(total, 1, reac - 1) / Double_t(reac - 1);
802 if (up > 0.0)
803 y /= up;
804 }
805}
806
808 // A tagged epoch keeps its own cache: its scatters and reservoir are of
809 // different events files than the untagged eras'.
810 TString name = "StripSumScatter_cache";
811 if (Constants::ActiveFileTag().Length() > 0)
812 name += "_" + Constants::ActiveFileTag();
813 name += ".root";
814 return name;
815}
816
817Bool_t StripSumScatter::PassesGate(const BeamGate1D &gate, const EnergyView &ev,
818 Int_t strip, Double_t nsigma) {
819 if (!(nsigma > 0.0))
820 return kTRUE;
821 const Double_t v = ev.Axis(strip);
822 if (!(v > 0.0) || !gate.ok)
823 return kFALSE;
824 return TMath::Abs(v - gate.mu) < nsigma * gate.sigma;
825}
826
827Bool_t StripSumScatter::PassesEllipse(const BeamFit2D &gate,
828 const EnergyView &ev, Int_t sx, Int_t sy,
829 Double_t nsigma) {
830 Double_t g0 = ev.Axis(sx);
831 Double_t g1 = ev.Axis(sy);
832 if (!(g0 > 0.0 && g1 > 0.0))
833 return kFALSE;
834 return BeamFitUtils::InEllipseXY(gate, g0, g1, nsigma);
835}
836
837BeamFit2D StripSumScatter::FindBeamGate(TChain *chain, Int_t sx, Int_t sy,
838 const TString &tag,
839 const TString &subdir,
840 Double_t nsigma) {
841 // The figure histogram is coarse; the fit is on a fine one around the seed.
842 const Int_t kGateBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_BINS;
843 const Double_t kGateMin = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_MIN;
844 const Double_t kGateMax = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_MAX;
845 const Int_t kSeedHalfBins =
847 const Long64_t kSampleMaxPoints =
849
850 BeamFit2D out;
851 EnergyView ev;
852 ev.Attach(chain);
853 EnableEventBranches(chain);
854 TH2F *h =
855 new TH2F(Form("h2_beamgate_%s_%s_%s", AxisTag(sx).Data(),
856 AxisTag(sy).Data(), tag.Data()),
857 Form(";%s #DeltaE [a.u.];%s #DeltaE [a.u.]",
858 AxisTitle(sx).Data(), AxisTitle(sy).Data()),
859 kGateBins, kGateMin, kGateMax, kGateBins, kGateMin, kGateMax);
860 h->SetDirectory(nullptr);
861 Long64_t n = chain->GetEntries();
862 Long64_t stride = FileSet::SampleStride(n, kSampleMaxPoints);
863 // The same events unbinned, for the clipped moments below.
864 std::vector<std::pair<Float_t, Float_t>> pts;
865 pts.reserve(std::min(n, kSampleMaxPoints > 0 ? kSampleMaxPoints : n));
866 for (Long64_t j = 0; j < n; j += stride) {
867 chain->GetEntry(j);
868 ev.Decode();
869 Double_t x = ev.Axis(sx);
870 Double_t y = ev.Axis(sy);
871 if (x > 0.0 && y > 0.0) {
872 h->Fill(x, y);
873 pts.push_back(std::make_pair(Float_t(x), Float_t(y)));
874 }
875 }
876 if (h->GetEntries() < 100) {
877 delete h;
878 return out;
879 }
880 Moments2D m = BeamFitUtils::SeedSpotMoments(h, kSeedHalfBins);
881 if (m.weight <= 0) {
882 delete h;
883 return out;
884 }
885 // The width the cuts are in is a fitted sigma: the spot events, finely
886 // binned around the seed, fitted with a correlated Gaussian on a pedestal.
887 BeamFitUtils::SpotFit spot = BeamFitUtils::FitSpotFromPoints(h, pts, m);
888 out = spot.fit;
889 {
890 std::lock_guard<std::mutex> lk(g_log_mutex);
891 Constants::Detail() << " beam spot " << AxisTag(sx) << "/" << AxisTag(sy)
892 << " " << tag << ": "
893 << (spot.fit_used ? "fit"
894 : "CLIPPED MOMENTS (fit failed)")
895 << " sigma=(" << out.sigma_x << "," << out.sigma_y
896 << ") rho=" << out.rho << " chi2/ndf=" << spot.chi2_ndf
897 << std::endl;
898 }
899
900 // The per-group beam-gate figures are the only thing written under
901 // strip_sum_scatter/<group>; groups outside the plot sample write none.
902 if (!Constants::SavePlots()) {
903 delete h;
904 return out;
905 }
906 std::lock_guard<std::mutex> lock(g_plot_mutex);
907 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
908 PlottingUtils::ConfigureAndDraw2DHistogram(h, c);
909 // The ellipse PassesEllipse applies.
910 BeamFitUtils::DrawEllipse(out, nsigma, kRed + 1);
911 // Same folder and naming as the strip gates: beam_gate_s0_s1,
912 // beam_gate_s1_grid, ... under strip_sum_scatter/<group>.
913 PlottingUtils::SaveFigure(
914 c, Form("beam_gate_%s_%s", AxisTag(sx).Data(), AxisTag(sy).Data()),
915 subdir, PlotSaveOptions::kLINEAR);
916 delete c;
917
918 delete h;
919 return out;
920}
921
922BeamGate1D StripSumScatter::FindStripGate(TChain *chain, Int_t strip,
923 const TString &tag,
924 const TString &subdir,
925 Double_t nsigma, Double_t center) {
926 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
927 const Double_t kSpotWindow = 4.0;
928 const Double_t kFitWindow = 2.0;
929 const Int_t kSpotBins = 160;
930 const Int_t kMinEvents = 100;
931 BeamGate1D out;
932 EnergyView ev;
933 ev.Attach(chain);
934 EnableEventBranches(chain);
935 TH1F *h = new TH1F(Form("h1_gate_s%d_%s", strip, tag.Data()),
936 Form(";%s #DeltaE [a.u.];Events", AxisTitle(strip).Data()),
937 C.GATE_BINS, C.GATE_MIN, C.GATE_MAX);
938 h->SetDirectory(nullptr);
939 const Long64_t n = chain->GetEntries();
940 const Long64_t stride = FileSet::SampleStride(n, C.SAMPLE_MAX_POINTS);
941 std::vector<Double_t> values;
942 values.reserve(C.SAMPLE_MAX_POINTS > 0 ? std::min(n, C.SAMPLE_MAX_POINTS)
943 : n);
944 for (Long64_t j = 0; j < n; j += stride) {
945 chain->GetEntry(j);
946 ev.Decode();
947 const Double_t v = ev.Axis(strip);
948 if (!(v > 0.0))
949 continue;
950 h->Fill(v);
951 values.push_back(v);
952 }
953 if (Int_t(values.size()) < kMinEvents) {
954 delete h;
955 return out;
956 }
957 Double_t mean = 0.0;
958 const Double_t width = ClippedWidth(values, &mean);
959 if (!(width > 0.0)) {
960 delete h;
961 return out;
962 }
963 if (center > 0.0)
964 mean = center;
965 // The clip seeds the fit and is the fallback; the fit is on a fine
966 // histogram around the seed, the inner kFitWindow fitted and the rest judged.
967 TH1F *hf = new TH1F(Form("%s_fit", h->GetName()), "", kSpotBins,
968 mean - kSpotWindow * width, mean + kSpotWindow * width);
969 hf->SetDirectory(nullptr);
970 for (Int_t k = 0; k < Int_t(values.size()); k++)
971 hf->Fill(values[k]);
972 Double_t chi2_ndf = -1.0;
973 Double_t fit_mu = mean;
974 Double_t fit_sigma = width;
975 const Bool_t fit_ok = BeamFitUtils::FitPeak(
976 hf, mean, width, kFitWindow, center > 0.0, fit_mu, fit_sigma, &chi2_ndf);
977 delete hf;
978 out.mu = fit_ok ? fit_mu : mean;
979 out.sigma = fit_ok ? fit_sigma : width;
980 out.ok = kTRUE;
981 {
982 std::lock_guard<std::mutex> lk(g_log_mutex);
983 Constants::Detail() << " strip gate s" << strip << " " << tag << ": "
984 << (fit_ok ? "fit" : "CLIPPED WIDTH (fit failed)")
985 << " mu=" << out.mu << " sigma=" << out.sigma
986 << " chi2/ndf=" << chi2_ndf << std::endl;
987 }
988 if (Constants::SavePlots()) {
989 std::lock_guard<std::mutex> lock(g_plot_mutex);
990 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kTRUE);
991 PlottingUtils::ConfigureAndDrawHistogram(h, kBlue + 1);
992 const Double_t top = h->GetMaximum();
993 TLine *lo = new TLine(out.mu - nsigma * out.sigma, 0.0,
994 out.mu - nsigma * out.sigma, top);
995 TLine *hi = new TLine(out.mu + nsigma * out.sigma, 0.0,
996 out.mu + nsigma * out.sigma, top);
997 lo->SetLineColor(kRed + 1);
998 hi->SetLineColor(kRed + 1);
999 lo->SetLineWidth(2);
1000 hi->SetLineWidth(2);
1001 lo->Draw();
1002 hi->Draw();
1003 PlottingUtils::SaveFigure(c, Form("beam_gate_s%d", strip), subdir);
1004 delete lo;
1005 delete hi;
1006 delete c;
1007 }
1008 delete h;
1009 return out;
1010}
1011
1012void StripSumScatter::DrawTraceSet(const std::vector<TGraph *> &traces,
1013 Int_t color) {
1014 for (Int_t i = 0; i < Int_t(traces.size()); i++) {
1015 traces[i]->SetLineColor(color);
1016 traces[i]->SetLineWidth(1);
1017 traces[i]->Draw("L SAME");
1018 }
1019}
1020
1021TGraph *StripSumScatter::TraceFromTotal(const Double_t *total) {
1023}
1024
1025void StripSumScatter::DrawRegionTraces(
1026 const TString &save_name, const TString &subdir,
1027 const std::vector<TGraph *> &beam, const std::vector<TGraph *> &aa,
1028 const std::vector<TGraph *> &an, Double_t y_min, Double_t y_max,
1029 const char *y_title, Bool_t beam_sigma_measured, const char *an_label) {
1030 std::vector<TraceClass> classes;
1031 TraceClass c_aa;
1032 c_aa.traces = aa;
1033 c_aa.label = "(#alpha,#alpha')";
1034 c_aa.color = SimClassColor("aa");
1035 classes.push_back(c_aa);
1036 TraceClass c_an;
1037 c_an.traces = an;
1038 c_an.label = an_label ? an_label : "(#alpha,n)";
1039 c_an.color = SimClassColor("an");
1040 classes.push_back(c_an);
1041 DrawRegionTraces(save_name, subdir, beam, classes, y_min, y_max, y_title,
1042 beam_sigma_measured);
1043}
1044
1045Int_t StripSumScatter::SimClassColor(const TString &base) {
1046 if (base == "aa")
1047 return kAzure + 2;
1048 if (base == "an")
1049 return kRed + 1;
1050 if (base == "ap")
1051 return kGreen + 2;
1052 if (base == "a2n")
1053 return kMagenta + 2;
1054 if (base == "ag")
1055 return kOrange + 7;
1056 return kGray + 2;
1057}
1058
1059// The per-point mean of a trace set with the +-1 RMS band as the y error:
1060// the shared core of the beam and region mean trace plots. Caller draws it.
1061static TGraphErrors *TraceMeanBand(const std::vector<TGraph *> &traces) {
1062 const Int_t npts = traces[0]->GetN();
1063 std::vector<Double_t> mean(npts, 0.0), m2(npts, 0.0);
1064 for (Int_t t = 0; t < Int_t(traces.size()); t++) {
1065 const Double_t *yv = traces[t]->GetY();
1066 for (Int_t p = 0; p < npts; p++) {
1067 mean[p] += yv[p];
1068 m2[p] += yv[p] * yv[p];
1069 }
1070 }
1071 const Double_t nt = Double_t(traces.size());
1072 const Double_t *xv = traces[0]->GetX();
1073 TGraphErrors *ge = new TGraphErrors(npts);
1074 for (Int_t p = 0; p < npts; p++) {
1075 mean[p] /= nt;
1076 const Double_t var = m2[p] / nt - mean[p] * mean[p];
1077 ge->SetPoint(p, xv[p], mean[p]);
1078 ge->SetPointError(p, 0.0, var > 0.0 ? TMath::Sqrt(var) : 0.0);
1079 }
1080 return ge;
1081}
1082
1083void StripSumScatter::DrawRegionTraces(const TString &save_name,
1084 const TString &subdir,
1085 const std::vector<TGraph *> &beam,
1086 const std::vector<TraceClass> &classes,
1087 Double_t y_min, Double_t y_max,
1088 const char *y_title,
1089 Bool_t beam_sigma_measured) {
1090 std::lock_guard<std::mutex> lock(g_plot_mutex);
1091 Int_t s_lo = Constants::cfg.IGNORE_STRIP_0 ? 1 : 0;
1092 Int_t s_hi = Constants::cfg.IGNORE_STRIP_17 ? 16 : 17;
1093 TH2F *frame =
1094 new TH2F("h_region_trace_frame", Form(";Strip;%s", y_title),
1095 s_hi - s_lo + 1, s_lo - 0.5, s_hi + 0.5, 100, y_min, y_max);
1096 frame->SetStats(0);
1097 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1098 frame->Draw();
1099
1100 // Beam: mean of the sampled beam traces with a +-1 sigma band, so the
1101 // region traces read against the noise the sigma cuts resolve through.
1102 TGraphErrors *beam_band = nullptr;
1103 if (!beam.empty()) {
1104 beam_band = TraceMeanBand(beam);
1105 // Where the strip noise was measured it reads better than the trace
1106 // spread: swap it in over the RMS.
1107 if (beam_sigma_measured) {
1108 for (Int_t p = 0; p < beam_band->GetN(); p++) {
1109 const Double_t measured =
1110 StripSigma(Int_t(TMath::Nint(beam_band->GetX()[p]))) *
1111 beam_band->GetY()[p];
1112 if (measured > 0.0)
1113 beam_band->SetPointError(p, 0.0, measured);
1114 }
1115 }
1116 // Dark mean line over a light band.
1117 beam_band->SetLineColor(kGray + 3);
1118 beam_band->SetLineWidth(3);
1119 beam_band->SetFillColorAlpha(kGray + 1, 0.35);
1120 beam_band->Draw("3 SAME");
1121 }
1122 for (size_t k = 0; k < classes.size(); k++)
1123 DrawTraceSet(classes[k].traces, classes[k].color);
1124 if (beam_band)
1125 beam_band->Draw("LX SAME"); // mean line above the traces
1126
1127 // A legend proxy per class; an empty class is left out: the all-tagged
1128 // overlay has one class of traces against the beam.
1129 std::vector<TGraph *> proxies;
1130 TLegend *leg = PlottingUtils::AddLegend(0.71, 0.88, 0.70, 0.86);
1131 if (beam_band)
1132 leg->AddEntry(beam_band, "Beam #pm 1#sigma", "lf");
1133 for (size_t k = 0; k < classes.size(); k++) {
1134 if (classes[k].traces.empty())
1135 continue;
1136 TGraph *p = new TGraph(1);
1137 p->SetPoint(0, -1e9, -1e9);
1138 p->SetLineColor(classes[k].color);
1139 p->SetLineWidth(3);
1140 proxies.push_back(p);
1141 leg->AddEntry(p, classes[k].label, "l");
1142 }
1143 leg->Draw();
1144
1145 PlottingUtils::SaveFigure(c, save_name, subdir, PlotSaveOptions::kLINEAR);
1146 delete c;
1147}
1148
1149void StripSumScatter::DrawRegionMeanTraces(const TString &save_name,
1150 const TString &subdir,
1151 const std::vector<TGraph *> &beam,
1152 const std::vector<TGraph *> &aa,
1153 const std::vector<TGraph *> &an,
1154 Double_t y_min, Double_t y_max,
1155 const char *y_title) {
1156 std::lock_guard<std::mutex> lock(g_plot_mutex);
1157 TH2F *frame = new TH2F("h_region_mean_frame", Form(";Strip;%s", y_title), 18,
1158 -0.5, 17.5, 100, y_min, y_max);
1159 frame->SetStats(0);
1160 frame->SetDirectory(nullptr);
1161 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
1162 frame->Draw();
1163
1164 const std::vector<TGraph *> *regions[3] = {&beam, &aa, &an};
1165 // Dark mean lines over light bands of the same hue.
1166 Int_t colors[3] = {kGray + 3, kAzure + 3, kRed + 2};
1167 Int_t bands[3] = {kGray + 1, kAzure + 1, kRed - 7};
1168 const char *labels[3] = {"Beam", "(#alpha,#alpha')", "(#alpha,n)"};
1169 std::vector<TGraphErrors *> means;
1170 TLegend *leg = PlottingUtils::AddLegend(0.725, 0.875, 0.70, 0.86);
1171
1172 for (Int_t r = 0; r < 3; r++) {
1173 const std::vector<TGraph *> &tr = *regions[r];
1174 if (tr.empty())
1175 continue;
1176 TGraphErrors *ge = TraceMeanBand(tr);
1177 ge->SetLineColor(colors[r]);
1178 ge->SetLineWidth(3);
1179 ge->SetFillColorAlpha(bands[r], 0.35);
1180 ge->Draw("3 SAME"); // +-1 RMS band (all bands first, behind the lines)
1181 means.push_back(ge);
1182 leg->AddEntry(ge, labels[r], "lf");
1183 }
1184 // Mean lines on top of every band.
1185 for (Int_t i = 0; i < Int_t(means.size()); i++)
1186 means[i]->Draw("LX SAME"); // mean line, no end caps
1187 leg->Draw();
1188
1189 PlottingUtils::SaveFigure(c, save_name, subdir, PlotSaveOptions::kLINEAR);
1190 for (Int_t i = 0; i < Int_t(means.size()); i++)
1191 delete means[i];
1192 delete leg;
1193 delete c;
1194 delete frame;
1195}
1196
1197void StripSumScatter::TraceYRange(const std::vector<TGraph *> &beam,
1198 const std::vector<TGraph *> &aa,
1199 const std::vector<TGraph *> &an,
1200 Double_t &y_min, Double_t &y_max) {
1201 y_min = std::numeric_limits<Double_t>::max();
1202 y_max = -std::numeric_limits<Double_t>::max();
1203 const std::vector<TGraph *> *sets[3] = {&beam, &aa, &an};
1204 for (Int_t si = 0; si < 3; si++) {
1205 const std::vector<TGraph *> &v = *sets[si];
1206 for (Int_t i = 0; i < Int_t(v.size()); i++) {
1207 Double_t x = 0.0, y = 0.0;
1208 for (Int_t k = 0; k < v[i]->GetN(); k++) {
1209 v[i]->GetPoint(k, x, y);
1210 if (x < 0.5 || x > 16.5)
1211 continue;
1212 if (y < y_min)
1213 y_min = y;
1214 if (y > y_max)
1215 y_max = y;
1216 }
1217 }
1218 }
1219 if (y_min > y_max) { // no in-range points sampled
1220 y_min = 0.0;
1221 y_max = 1.0;
1222 }
1223 Double_t pad = 0.05 * (y_max - y_min);
1224 if (pad <= 0.0)
1225 pad = 1.0;
1226 y_min -= pad;
1227 y_max += pad;
1228}
1229
1230TCutG *StripSumScatter::PromptCut(TCanvas *c, const char *name,
1231 const char *label) {
1232 std::cout << " >>> draw the " << label
1233 << " region: left-click vertices, double-click to close"
1234 << std::endl;
1235 c->cd();
1236 TCutG *cut = static_cast<TCutG *>(c->WaitPrimitive("CUTG", "CutG"));
1237 if (!cut) {
1238 std::cerr << " no " << label << " cut drawn" << std::endl;
1239 return nullptr;
1240 }
1241 cut->SetName(name);
1242 cut->SetLineColor(kBlack);
1243 cut->SetLineWidth(2);
1244 return cut;
1245}
1246
1255void StripSumScatter::SaveRegionCuts(Int_t reac, TCutG *cut_an, TCutG *cut_aa) {
1256 RegionCutStore::Save(reac, cut_an, cut_aa);
1257}
1258
1259TCutG *StripSumScatter::LoadRegionCut(const char *name, Int_t reac) {
1260 return RegionCutStore::Load(name, reac);
1261}
1262
1268void StripSumScatter::SavitzkyGolay(const Double_t *in, Double_t *out) {
1269 static const Int_t K = 2; // half-width (5-point window)
1270
1271 // Standard SG coefficients for 5-point, 3rd-degree polynomial (smoothed
1272 // value): These are translation-invariant - same for all center positions.
1273 static const Double_t sg_coeff[2 * K + 1] = {
1274 -3.0 / 35.0, // coefficient for t = s - 2
1275 12.0 / 35.0, // coefficient for t = s - 1
1276 17.0 / 35.0, // coefficient for t = s (center)
1277 12.0 / 35.0, // coefficient for t = s + 1
1278 -3.0 / 35.0 // coefficient for t = s + 2
1279 };
1280
1281 for (Int_t s = 0; s < 18; s++) {
1282 Int_t lo = TMath::Max(0, s - K);
1283 Int_t hi = TMath::Min(17, s + K);
1284 Double_t val = 0.0;
1285 Double_t wsum = 0.0;
1286
1287 // Apply SG coefficients for the positions within the clipped window
1288 for (Int_t t = lo; t <= hi; t++) {
1289 Int_t offset = t - s + K; // 0..4, position within 5-point window
1290 val += sg_coeff[offset] * in[t];
1291 wsum += sg_coeff[offset];
1292 }
1293
1294 // Renormalise at edges where window shrinks
1295 out[s] = (wsum != 0.0) ? val / wsum : in[s];
1296 }
1297}
1298
1299// Build a TGraph trace from a Savitzky-Golay-smoothed set of per-strip totals.
1300TGraph *StripSumScatter::SmoothedTraceFromTotal(const Double_t *total) {
1301 Double_t sgd[18];
1302 SavitzkyGolay(total, sgd);
1304}
1305
1306TString StripSumScatter::BuildFingerprint(const FileSet::GateGroups &groups) {
1307 const Int_t kReacMin =
1309 const Int_t kReacMax =
1311 const Double_t kReacJumpNSigma =
1313 const Double_t kSmoothNSigma =
1315 const Double_t kEndStripMax =
1317
1318 const Double_t kGateNSigma =
1320 const Double_t kGateCenter =
1322 const Int_t kGateBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_BINS;
1323 const Double_t kGateMin = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_MIN;
1324 const Double_t kGateMax = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_MAX;
1325
1326 const Int_t kXBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.XBINS;
1327 const Int_t kYBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.YBINS;
1328 const Int_t kGateStrip = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_STRIP;
1329
1330 // Two parts split by the bar. Before: what decides tagging and keeping; a
1331 // change there refills. After: plane-only, re-projected from the reservoir.
1332 TString s =
1333 Form("v45 reac[%d,%d] short=%d bmult[%d,%d] pileup=%.2fsig,%d "
1334 "noise=%.2fsig,%d "
1335 "jump=%.2fsig smooth=%.2fsig "
1336 "end=%.2fsig gate[s%d,%.2f,c%.4f,%d,%.3f,%.3f]",
1337 kReacMin, kReacMax, Int_t(Constants::ActiveIgnoreShortStrips()),
1338 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BOTH_MULT_MAX,
1344 kReacJumpNSigma, kSmoothNSigma, kEndStripMax, kGateStrip,
1345 kGateNSigma, kGateCenter, kGateBins, kGateMin, kGateMax);
1346 // Which unsegmented strips the all-strips cut requires: a change refills.
1347 s += Form(
1348 " req[s0=%d,s17=%d]",
1349 Int_t(!Constants::cfg.IGNORE_STRIP_0 && Constants::cfg.REQUIRE_STRIP_0),
1350 Int_t(!Constants::cfg.IGNORE_STRIP_17));
1351 // The pure-beam entrance ellipse decides the beam sample and its widths.
1352 s += Form(" one=%d",
1353 Int_t(Constants::cfg.STRIP_SUM_SCATTER_CONFIG.TAG_RESOLVE));
1354 s += Form(" pure=%d,%.2fsig",
1355 Int_t(Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PURE_BEAM_GATE),
1357 // The tail-shape conditions, in sigma, and the measured noise every
1358 // sigma-unit cut resolves through: a drift in the noise refills.
1359 {
1360 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
1361 s += Form(" tail[fall=%d,%.2fsig above=%d,%.2fsig rerise=%.2fsig,%.2fsig "
1362 "cross=%d cliff=%.2f]",
1366 // The variation steps: the variant counts in the cache depend on them.
1367 if (C.CUT_VARIATION)
1368 s += Form(" var[%.2fsig,%.2f]", C.CUT_VARIATION_NSIGMA_STEP,
1370 s += " beam[";
1371 for (Int_t strip = 1; strip <= 17; strip++)
1372 s += Form("%s%.4f/%.4f", strip == 1 ? "" : ",", StripMean(strip),
1373 StripSigma(strip));
1374 s += "]";
1375 }
1376 // The gate resolves through the measured noise, so the thresholds actually
1377 // applied are stamped too: a drift in the noise refills.
1378 s += " jmin[";
1379 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
1380 s += Form("%s%.4f", reac == kReacMin ? "" : ",", JumpMin(reac));
1381 s += "]";
1382 // The upstream-beam precondition changes which events are tagged; like the
1383 // jump gate, it resolves through measured noise, so tolerances stamped.
1384 s += Form(" up=%d,%.2fsig[",
1385 Int_t(Constants::cfg.STRIP_SUM_SCATTER_CONFIG
1386 .REQUIRE_BEAM_UPSTREAM_OF_REAC),
1387 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BEAM_UPSTREAM_NSIGMA);
1388 for (Int_t strip = 1; strip < kReacMax; strip++)
1389 s += Form("%s%.4f", strip == 1 ? "" : ",",
1390 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BEAM_UPSTREAM_NSIGMA *
1391 StripSigma(strip));
1392 s += "]";
1393 // Display windows are deliberately absent: they no longer change what is
1394 // built, so retuning them must not invalidate the cache.
1395 for (Int_t i = 0; i < Int_t(groups.order.size()); i++) {
1396 const Int_t key = groups.order[i];
1397 s += Form(" %s:%lld", groups.label.find(key)->second.Data(),
1398 groups.chain.find(key)->second->GetEntries());
1399 }
1400
1401 // The built quantity: build range, binning, the x range and each strip's
1402 // y window, so a change to the window rule re-projects.
1403 TString plane =
1404 Form("buildx[%.3f,%.3f] buildy[%.3f,%.3f] bins[%d,%d] x[%d,%d]",
1407 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO,
1409 plane += " y";
1410 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
1411 plane +=
1412 Form("%s%d-%d", reac == kReacMin ? "[" : ",", YLoOf(reac), YHiOf(reac));
1413 plane += "]";
1414 plane +=
1415 Form(" yratio=%d",
1416 Int_t(Constants::cfg.STRIP_SUM_SCATTER_CONFIG.Y_RATIO_TO_UPSTREAM));
1417 return s + " | " + plane;
1418}
1419
1420// The tagging half of a fingerprint; a cache from before the split has no
1421// bar and never matches.
1422static TString TagPart(const TString &fingerprint) {
1423 const Ssiz_t bar = fingerprint.Index(" | ");
1424 return bar < 0 ? TString("") : TString(fingerprint(0, bar));
1425}
1426
1431void StripSumScatter::YBounds(Double_t *y_lo, Double_t *y_hi) {
1432 const Int_t kReacMin =
1434 const Int_t kReacMax =
1436 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
1437 Int_t ri = reac - kReacMin;
1438 std::map<Int_t, std::pair<Double_t, Double_t>>::const_iterator it =
1440 if (it != Constants::cfg.STRIP_SUM_SCATTER_CONFIG.Y_DISPLAY_RANGE.end()) {
1441 y_lo[ri] = it->second.first;
1442 y_hi[ri] = it->second.second;
1443 } else {
1446 }
1447 }
1448}
1449
1450TString StripSumScatter::PrettyLabel(const TString &tag) {
1451 TString base = RemixSim::TagWithoutStrip(tag);
1452 if (base == "aa")
1453 return "(#alpha,#alpha')";
1454 if (base == "an")
1455 return "(#alpha,n)";
1456 if (base == "ap")
1457 return "(#alpha,p)";
1458 if (base == "a2n")
1459 return "(#alpha,2n)";
1460 if (base == "ag")
1461 return "(#alpha,#gamma)";
1462 if (base == "beam")
1463 return "Beam";
1464 return base;
1465}
1466
1475Bool_t StripSumScatter::SimBeamGains(Double_t *gain) {
1476 const Long64_t kSampleMaxPoints =
1478
1479 for (Int_t s = 0; s < 18; s++)
1480 gain[s] = 0.0;
1481 // Reference the simulated beam file (the same sim as the overlay
1482 // populations) so the sim beam lands exactly on 1; else SIM_BEAM_FILE.
1483 TString file;
1484 std::vector<RemixSim::SimFileSpec> specs = RemixSim::BuildFileSpecs();
1485 for (Int_t i = 0; i < Int_t(specs.size()); i++) {
1486 if (RemixSim::TagWithoutStrip(specs[i].tag) == "beam") {
1487 file = RemixSim::SimRootPath(specs[i]);
1488 break;
1489 }
1490 }
1491 if (file.Length() == 0)
1492 file =
1493 Paths::DatasetDir() + "/sim_root_files/" + Constants::cfg.SIM_BEAM_FILE;
1494 TFile *f = IO::OpenForReading(file);
1495 if (!f || f->IsZombie()) {
1496 std::cerr << "strip-sum-scatter: cannot open sim beam file " << file
1497 << "; sim overlay stays in raw sim units." << std::endl;
1498 if (f)
1499 delete f;
1500 return kFALSE;
1501 }
1502 TTree *t = static_cast<TTree *>(f->Get("events_MeV"));
1503 if (!t) {
1504 std::cerr << "strip-sum-scatter: no events_MeV tree in sim beam file "
1505 << file << "; sim overlay stays in raw sim units." << std::endl;
1506 f->Close();
1507 delete f;
1508 return kFALSE;
1509 }
1510 RemixSim::Event e;
1511 if (!e.Attach(t)) {
1512 f->Close();
1513 delete f;
1514 return kFALSE;
1515 }
1516 Long64_t n = t->GetEntries();
1517 Long64_t stride = FileSet::SampleStride(n, kSampleMaxPoints);
1518 Double_t sum[18] = {0};
1519 Long64_t cnt[18] = {0};
1520 // Unit gains so SimTotal yields the raw per-strip beam total (IGNORE_SHORT
1521 // aware), which is exactly the quantity these gains will later normalize.
1522 Double_t unit[18];
1523 for (Int_t s = 0; s < 18; s++)
1524 unit[s] = 1.0;
1525 for (Long64_t j = 0; j < n; j += stride) {
1526 t->GetEntry(j);
1527 Double_t total[18];
1528 SimTotal(e, unit, total);
1529 for (Int_t s = 0; s < 18; s++)
1530 if (total[s] > 0.0) {
1531 sum[s] += total[s];
1532 cnt[s]++;
1533 }
1534 }
1535 f->Close();
1536 delete f;
1537 Int_t n_set = 0;
1538 for (Int_t s = 0; s < 18; s++) {
1539 if (cnt[s] > 0 && sum[s] > 0.0) {
1540 gain[s] = 1.0 / (sum[s] / Double_t(cnt[s]));
1541 n_set++;
1542 }
1543 }
1544 if (n_set == 0)
1545 return kFALSE;
1546 std::cout << "strip-sum-scatter: sim per-strip beam normalization to 1 a.u. ("
1547 << n_set << " strips)." << std::endl;
1548 return kTRUE;
1549}
1550
1551void StripSumScatter::SimTotal(const RemixSim::Event &e, const Double_t *gain,
1552 Double_t *total) {
1553 for (Int_t s = 0; s < 18; s++)
1554 total[s] = gain[s] * e.Total(s);
1556 for (Int_t s = 1; s <= 16; s++)
1557 total[s] = gain[s] * ((s % 2) != 0 ? e.Left(s) : e.Right(s));
1558}
1559
1560TGraph *StripSumScatter::SimPopScatter(const TString &file, Int_t reac,
1561 const Double_t *gain,
1562 Long64_t max_points) {
1563 const Int_t kXLo = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO;
1564 const Int_t kXHi = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI;
1565
1566 TFile *f = IO::OpenForReading(file);
1567 if (!f || f->IsZombie()) {
1568 if (f)
1569 delete f;
1570 return nullptr;
1571 }
1572 TTree *t = static_cast<TTree *>(f->Get("events_MeV"));
1573 if (!t) {
1574 std::cerr << " no events_MeV tree in " << file << std::endl;
1575 f->Close();
1576 delete f;
1577 return nullptr;
1578 }
1579 Int_t y_lo = reac + 1;
1580 Int_t y_hi = TMath::Min(reac + 6, 17);
1581 RemixSim::Event e;
1582 if (!e.Attach(t)) {
1583 f->Close();
1584 delete f;
1585 return nullptr;
1586 }
1587 Long64_t n = t->GetEntries();
1588 Long64_t stride = FileSet::SampleStride(n, max_points);
1589 TGraph *g = new TGraph();
1590 Long64_t k = 0;
1591 for (Long64_t j = 0; j < n; j += stride) {
1592 t->GetEntry(j);
1593 Double_t total[18];
1594 SimTotal(e, gain, total);
1595 Double_t x = SumRange(total, kXLo, kXHi);
1596 Double_t y = SumRange(total, y_lo, y_hi);
1597 if (x > 0.0)
1598 g->SetPoint(k++, x, y);
1599 }
1600 g->Set(k);
1601 f->Close();
1602 delete f;
1603 return g;
1604}
1605
1610std::vector<TGraph *> StripSumScatter::SimPopTraces(const TString &file,
1611 const Double_t *gain,
1612 Long64_t max_traces) {
1613 std::vector<TGraph *> traces;
1614 TFile *f = IO::OpenForReading(file);
1615 if (!f || f->IsZombie()) {
1616 if (f)
1617 delete f;
1618 return traces;
1619 }
1620 TTree *t = static_cast<TTree *>(f->Get("events_MeV"));
1621 if (!t) {
1622 f->Close();
1623 delete f;
1624 return traces;
1625 }
1626 RemixSim::Event e;
1627 if (!e.Attach(t)) {
1628 f->Close();
1629 delete f;
1630 return traces;
1631 }
1632 Long64_t n = t->GetEntries();
1633 Long64_t stride = FileSet::SampleStride(n, max_traces);
1634 for (Long64_t j = 0; j < n && Int_t(traces.size()) < max_traces;
1635 j += stride) {
1636 t->GetEntry(j);
1637 Double_t total[18];
1638 SimTotal(e, gain, total);
1639 traces.push_back(EventsSummary::BuildTraceFromTotals(total));
1640 }
1641 f->Close();
1642 delete f;
1643 return traces;
1644}
1645
1646// The segmented strips where both ends FIRED, read off RAW ADC (sees the
1647// short end even when IGNORE_SHORT_STRIPS zeroes it), over strips 1..hi.
1648static Int_t CountBothEnds(const EnergyView &ev, Int_t hi) {
1649 Int_t nboth = 0;
1650 for (Int_t s = 1; s <= hi; s++)
1651 if (ev.leftdE_adc[s - 1] > 0 && ev.rightdE_adc[s - 1] > 0)
1652 nboth++;
1653 return nboth;
1654}
1655
1657 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
1658 const Int_t kReacMin = C.REACTION_STRIP_MIN;
1659 const Int_t kReacMax = C.REACTION_STRIP_MAX;
1660 const Int_t nReac = kReacMax - kReacMin + 1;
1661 std::vector<RemixSim::SimFileSpec> specs = RemixSim::BuildFileSpecs();
1662 if (specs.empty())
1663 return;
1664 // One file per class and strip (BuildFileSpecs stops on a second); the
1665 // beam files as a class of their own (strip -1).
1666 struct SimClass {
1667 TString base;
1668 Int_t strip;
1669 TString file;
1670 };
1671 std::vector<SimClass> classes;
1672 for (Int_t i = 0; i < Int_t(specs.size()); i++) {
1673 SimClass c;
1674 c.base = RemixSim::TagWithoutStrip(specs[i].tag);
1675 c.strip = RemixSim::ReactionStripOf(specs[i].tag);
1676 c.file = RemixSim::SimRootPath(specs[i]);
1677 if (c.base != "beam" && (c.strip < kReacMin || c.strip > kReacMax))
1678 continue;
1679 classes.push_back(c);
1680 }
1681 Double_t gain[18];
1682 if (!SimBeamGains(gain))
1683 for (Int_t s = 0; s < 18; s++)
1684 gain[s] = 1.0;
1685
1686 std::ostringstream out;
1687 out << Form("strip-sum-scatter: the data selection over the simulated "
1688 "populations (%s), against the measured beam spread; the beam "
1689 "gate is not applied (fitted on data), the rest is the fill's "
1690 "code.",
1691 Paths::DatasetName().Data())
1692 << std::endl;
1693 out << "Event level: sequential (all strips, pileup, noise, smoothness, "
1694 "both mult). Own strip: the first failing tag condition, sequential; "
1695 "tagged = passed them all. One tag: where the event's single tag "
1696 "went under the one-tag rule (own, an earlier strip, a later strip, "
1697 "none)"
1698 << std::endl;
1699 out << std::endl;
1700 for (size_t k = 0; k < classes.size(); k++) {
1701 const SimClass &c = classes[k];
1702 TFile *fl = IO::OpenForReading(c.file);
1703 if (!fl || fl->IsZombie()) {
1704 delete fl;
1705 out << Form("%s: cannot open %s", c.base.Data(), c.file.Data())
1706 << std::endl;
1707 continue;
1708 }
1709 TTree *t = static_cast<TTree *>(fl->Get("events_MeV"));
1711 if (!t || !e.Attach(t)) {
1712 out << Form("%s: no events_MeV in %s", c.base.Data(), c.file.Data())
1713 << std::endl;
1714 fl->Close();
1715 delete fl;
1716 continue;
1717 }
1718 const Long64_t n = t->GetEntries();
1719 std::vector<Long64_t> pre(kNPRECUTS, 0);
1720 std::vector<Long64_t> own(kNTAGCUTS, 0);
1721 Long64_t tag_own = 0, tag_earlier = 0, tag_later = 0, tag_none = 0;
1722 std::vector<Long64_t> tag_at(nReac, 0);
1723 EnergyView ev;
1724 std::vector<Bool_t> pass(nReac, kFALSE);
1725 for (Long64_t j = 0; j < n; j++) {
1726 t->GetEntry(j);
1727 // The sim's deposit per end, on the data's beam scale (SimTotal's
1728 // rule), into the view the selection reads.
1729 for (Int_t s = 1; s <= 16; s++) {
1730 ev.left[s - 1] = gain[s] * e.Left(s);
1731 ev.right[s - 1] = gain[s] * e.Right(s);
1733 const Double_t tot = gain[s] * e.Total(s);
1734 ev.left[s - 1] = (s % 2) != 0 ? tot : 0.0;
1735 ev.right[s - 1] = (s % 2) != 0 ? 0.0 : tot;
1736 }
1737 ev.leftdE_adc[s - 1] = ev.left[s - 1] > 0.0 ? 1 : 0;
1738 ev.rightdE_adc[s - 1] = ev.right[s - 1] > 0.0 ? 1 : 0;
1739 }
1740 ev.strip0 = gain[0] * e.strip0;
1741 ev.strip17 = gain[17] * e.strip17;
1742 // Event level, sequential as in the fill.
1743 if (!AllStripsFired(ev)) {
1744 pre[kPRE_ALL_STRIPS]++;
1745 continue;
1746 }
1747 if (IsPileup(ev)) {
1748 pre[kPRE_PILEUP]++;
1749 continue;
1750 }
1751 if (IsNoise(ev)) {
1752 pre[kPRE_NOISE]++;
1753 continue;
1754 }
1755 if (!IsSmooth(ev, C.SMOOTHNESS_NSIGMA)) {
1756 pre[kPRE_SMOOTH]++;
1757 continue;
1758 }
1759 if (C.BOTH_MULT_MAX >= 0) {
1760 if (CountBothEnds(ev, TMath::Min(16, C.BOTH_MULT_COUNT_TO)) >
1761 C.BOTH_MULT_MAX) {
1762 pre[kPRE_BOTH_MULT]++;
1763 continue;
1764 }
1765 }
1766 pre[kPRE_PASS]++;
1767 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
1768 const TagCut why = RejectReason(ev, reac);
1769 pass[ReacIndex(reac)] = why == kTAG_PASS;
1770 if (reac == c.strip)
1771 own[why]++;
1772 }
1773 const Int_t won = ResolveTag(ev, pass);
1774 if (won < 0)
1775 tag_none++;
1776 else {
1777 tag_at[ReacIndex(won)]++;
1778 if (won == c.strip)
1779 tag_own++;
1780 else if (won < c.strip)
1781 tag_earlier++;
1782 else
1783 tag_later++;
1784 }
1785 }
1786 fl->Close();
1787 delete fl;
1788
1789 out << Form("%s%s: %lld events (%s)", PrettyLabel(c.base).Data(),
1790 c.strip >= 0 ? Form(" at strip %d", c.strip) : "", n,
1791 gSystem->BaseName(c.file))
1792 << std::endl;
1793 TString line = " event level:";
1794 for (Int_t p = 1; p < kNPRECUTS; p++)
1795 if (p != kPRE_GATE)
1796 line += Form(" %s %lld,", kPreCutName[p], pre[p]);
1797 line += Form(" reached the tag %lld (%.1f%%)", pre[kPRE_PASS],
1798 n > 0 ? 100.0 * pre[kPRE_PASS] / n : 0.0);
1799 out << line << std::endl;
1800 if (c.strip >= 0) {
1801 line = Form(" own strip %d:", c.strip);
1802 for (Int_t w = 1; w < kNTAGCUTS; w++)
1803 if (w != kCUT_OTHER_TAG)
1804 line += Form(" %s %lld,", kTagCutName[w], own[w]);
1805 line += Form(" tagged %lld (%.1f%% of events, %.1f%% of those reaching "
1806 "the tag)",
1807 own[kTAG_PASS], n > 0 ? 100.0 * own[kTAG_PASS] / n : 0.0,
1808 pre[kPRE_PASS] > 0 ? 100.0 * own[kTAG_PASS] / pre[kPRE_PASS]
1809 : 0.0);
1810 out << line << std::endl;
1811 out << Form(" one tag: own %lld, earlier strip %lld, later strip %lld, "
1812 "none %lld",
1813 tag_own, tag_earlier, tag_later, tag_none)
1814 << std::endl;
1815 }
1816 line = " tagged at:";
1817 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
1818 line += Form(" s%d %lld", reac, tag_at[ReacIndex(reac)]);
1819 line += Form(" (none %lld)", tag_none);
1820 out << line << std::endl;
1821 out << std::endl;
1822 }
1823 const std::string text = out.str();
1824 std::cout << text;
1825 const TString dir = Paths::ResultsDir() + "/plots/strip_sum_scatter";
1826 gSystem->mkdir(dir, kTRUE);
1827 std::ofstream fo((dir + "/sim_tag_report.txt").Data());
1828 fo << text;
1829}
1830
1836void StripSumScatter::SimTraceOverlay() {
1837 const Int_t kReacMin =
1838 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN;
1839 const Int_t kReacMax =
1840 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MAX;
1841 const Int_t kTracesPerRegion =
1842 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.TRACES_PER_CLASS;
1843
1844 std::vector<RemixSim::SimFileSpec> specs = RemixSim::BuildFileSpecs();
1845 if (specs.empty())
1846 return;
1847 // One file per class and strip (BuildFileSpecs stops on a second).
1848 // Classes in the order they are first seen (the specs are sorted by tag).
1849 std::vector<TString> bases;
1850 std::map<TString, std::map<Int_t, TString>> files; // base, strip -> file
1851 std::vector<TString> beam_files;
1852 for (Int_t i = 0; i < Int_t(specs.size()); i++) {
1853 const TString base = RemixSim::TagWithoutStrip(specs[i].tag);
1854 const TString file = RemixSim::SimRootPath(specs[i]);
1855 const Int_t strip = RemixSim::ReactionStripOf(specs[i].tag);
1856 if (base == "beam") {
1857 beam_files.push_back(file);
1858 } else if (strip >= kReacMin && strip <= kReacMax) {
1859 if (!files.count(base))
1860 bases.push_back(base);
1861 files[base][strip] = file;
1862 }
1863 }
1864
1865 Double_t gain[18];
1866 if (!SimBeamGains(gain))
1867 for (Int_t s = 0; s < 18; s++)
1868 gain[s] = 1.0;
1869
1870 std::vector<TGraph *> beam_traces;
1871 for (Int_t i = 0; i < Int_t(beam_files.size()) &&
1872 Int_t(beam_traces.size()) < kTracesPerRegion;
1873 i++) {
1874 std::vector<TGraph *> t = SimPopTraces(
1875 beam_files[i], gain, kTracesPerRegion - Int_t(beam_traces.size()));
1876 for (Int_t k = 0; k < Int_t(t.size()); k++)
1877 beam_traces.push_back(t[k]);
1878 }
1879
1880 for (Int_t r = kReacMin; r <= kReacMax; r++) {
1881 std::vector<TraceClass> classes;
1882 Bool_t any = kFALSE;
1883 for (size_t b = 0; b < bases.size(); b++) {
1884 TraceClass cl;
1885 cl.label = PrettyLabel(bases[b]);
1886 cl.color = SimClassColor(bases[b]);
1887 if (files[bases[b]].count(r))
1888 cl.traces = SimPopTraces(files[bases[b]][r], gain, kTracesPerRegion);
1889 any = any || !cl.traces.empty();
1890 classes.push_back(cl);
1891 }
1892 if (!any)
1893 continue;
1894 DrawRegionTraces(Form("sim_region_traces_reac%d", r), "sim_scatter",
1895 beam_traces, classes, 0.6, 1.6, "#DeltaE [a.u.]");
1896 for (size_t b = 0; b < classes.size(); b++)
1897 for (Int_t i = 0; i < Int_t(classes[b].traces.size()); i++)
1898 delete classes[b].traces[i];
1899 }
1900 for (Int_t i = 0; i < Int_t(beam_traces.size()); i++)
1901 delete beam_traces[i];
1902}
1903
1907TString StripSumScatter::SimFingerprint(
1908 const std::vector<RemixSim::SimFileSpec> &specs) {
1909 // v2: per-strip norm via SimBeamGains(); the gain file is stamped by the
1910 // per-spec loop; v3: norm hardcoded to 1 a.u. (NORM_MUSIC_MEV gone).
1911 const Int_t kReacMin =
1913 const Int_t kReacMax =
1915 const Int_t kXLo = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO;
1916 const Int_t kXHi = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI;
1917
1918 // v4: one population per class and strip, so caches that held every
1919 // population twice are rebuilt.
1920 TString s = Form("v4 reac[%d,%d] x[%d,%d]", kReacMin, kReacMax, kXLo, kXHi);
1921 for (Int_t i = 0; i < Int_t(specs.size()); i++) {
1922 TString f = RemixSim::SimRootPath(specs[i]);
1923 Long_t id = 0, flags = 0, mtime = 0;
1924 Long64_t size = -1;
1925 if (gSystem->GetPathInfo(f, &id, &size, &flags, &mtime) != 0) {
1926 size = -1;
1927 mtime = 0;
1928 }
1929 s += Form(" %s:%lld:%ld", specs[i].tag.Data(), size, mtime);
1930 }
1931 return s;
1932}
1933
1937Bool_t StripSumScatter::LoadSimCache(
1938 const TString &fp, std::map<Int_t, std::vector<TGraph *>> &by_strip) {
1939 TString full = IO::GetRootFilesBaseDir() + TString("/") +
1940 "StripSumScatter_simcache.root";
1941 if (gSystem->AccessPathName(full))
1942 return kFALSE;
1943 TFile *f = IO::OpenForReading("StripSumScatter_simcache.root");
1944 if (!f || f->IsZombie()) {
1945 if (f)
1946 delete f;
1947 return kFALSE;
1948 }
1949 TNamed *cfp = static_cast<TNamed *>(f->Get("sim_fingerprint"));
1950 if (!cfp || fp != cfp->GetTitle()) {
1951 f->Close();
1952 delete f;
1953 return kFALSE;
1954 }
1955 TIter next(f->GetListOfKeys());
1956 TKey *key;
1957 while ((key = static_cast<TKey *>(next()))) {
1958 TString name = key->GetName();
1959 if (!name.BeginsWith("simg_r"))
1960 continue;
1961 TString rest = name(6, name.Length() - 6); // after "simg_r": <strip>_p<idx>
1962 Int_t us = rest.Index("_p");
1963 if (us < 0)
1964 continue;
1965 Int_t r = TString(rest(0, us)).Atoi();
1966 TGraph *g = static_cast<TGraph *>(f->Get(name));
1967 if (!g)
1968 continue;
1969 by_strip[r].push_back(static_cast<TGraph *>(g->Clone()));
1970 }
1971 f->Close();
1972 delete f;
1973 return kTRUE;
1974}
1975
1976void StripSumScatter::WriteSimCache(
1977 const TString &fp, const std::map<Int_t, std::vector<TGraph *>> &by_strip) {
1978 TFile *out = IO::OpenForWriting("StripSumScatter_simcache.root", "RECREATE");
1979 if (!out || out->IsZombie()) {
1980 if (out)
1981 delete out;
1982 return;
1983 }
1984 out->cd();
1985 TNamed cfp("sim_fingerprint", fp.Data());
1986 cfp.Write();
1987 std::map<Int_t, std::vector<TGraph *>>::const_iterator it;
1988 for (it = by_strip.begin(); it != by_strip.end(); ++it)
1989 for (Int_t i = 0; i < Int_t(it->second.size()); i++)
1990 it->second[i]->Write(Form("simg_r%d_p%d", it->first, Int_t(i)));
1991 out->Close();
1992 delete out;
1993}
1994
2001void StripSumScatter::SimOverlay() {
2002 const Int_t kReacMin =
2004 const Int_t kReacMax =
2006 const Int_t kXLo = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO;
2007 const Int_t kXHi = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI;
2008
2009 std::vector<RemixSim::SimFileSpec> specs = RemixSim::BuildFileSpecs();
2010 if (specs.empty()) {
2011 std::cerr
2012 << "strip-sum-scatter: no sim control files; skipping sim overlay."
2013 << std::endl;
2014 return;
2015 }
2016 TString fp = SimFingerprint(specs);
2017
2018 std::map<Int_t, std::vector<TGraph *>>
2019 by_strip; // strip -> graphs (title=label)
2020 Bool_t loaded = LoadSimCache(fp, by_strip);
2021 if (!loaded) {
2022 // One population per class and strip (BuildFileSpecs stops on a second).
2023 std::map<std::pair<TString, Int_t>, std::pair<SimPop, Bool_t>> chosen;
2024 for (Int_t i = 0; i < Int_t(specs.size()); i++) {
2025 const TString base = RemixSim::TagWithoutStrip(specs[i].tag);
2026 const Int_t strip = RemixSim::ReactionStripOf(specs[i].tag);
2027 std::pair<TString, Int_t> key(base, strip);
2028 SimPop p;
2029 p.file = RemixSim::SimRootPath(specs[i]);
2030 p.label = PrettyLabel(specs[i].tag);
2031 chosen[key] = std::make_pair(p, kTRUE);
2032 }
2033 std::map<Int_t, std::vector<SimPop>> reacted;
2034 std::vector<SimPop> refs;
2035 for (std::map<std::pair<TString, Int_t>,
2036 std::pair<SimPop, Bool_t>>::const_iterator it =
2037 chosen.begin();
2038 it != chosen.end(); ++it) {
2039 if (it->first.second < 0)
2040 refs.push_back(it->second.first);
2041 else
2042 reacted[it->first.second].push_back(it->second.first);
2043 }
2044 Double_t gain[18];
2045 if (!SimBeamGains(gain))
2046 for (Int_t s = 0; s < 18; s++)
2047 gain[s] = 1.0;
2048 const Long64_t kSimMaxPoints = 25000;
2049 for (Int_t r = kReacMin; r <= kReacMax; r++) {
2050 std::vector<SimPop> group = reacted[r];
2051 for (Int_t i = 0; i < Int_t(refs.size()); i++)
2052 group.push_back(refs[i]);
2053 for (Int_t i = 0; i < Int_t(group.size()); i++) {
2054 TGraph *g = SimPopScatter(group[i].file, r, gain, kSimMaxPoints);
2055 if (!g || g->GetN() == 0) {
2056 if (g)
2057 delete g;
2058 continue;
2059 }
2060 g->SetTitle(group[i].label);
2061 by_strip[r].push_back(g);
2062 }
2063 }
2064 Int_t n_graphs = 0;
2065 std::map<Int_t, std::vector<TGraph *>>::const_iterator cit;
2066 for (cit = by_strip.begin(); cit != by_strip.end(); ++cit)
2067 n_graphs += Int_t(cit->second.size());
2068 if (n_graphs == 0) {
2069 std::cerr << "strip-sum-scatter: no sim data found (regenerate "
2070 "sim_root_files); skipping sim overlay."
2071 << std::endl;
2072 return;
2073 }
2074 WriteSimCache(fp, by_strip);
2075 std::cout << "strip-sum-scatter: built + cached sim overlay (" << n_graphs
2076 << " population graphs)." << std::endl;
2077 } else {
2078 std::cout
2079 << "strip-sum-scatter: loaded cached sim overlay (fingerprint match)."
2080 << std::endl;
2081 }
2082
2083 std::map<Int_t, std::vector<TGraph *>>::iterator it;
2084 for (it = by_strip.begin(); it != by_strip.end(); ++it) {
2085 Int_t r = it->first;
2086 std::map<Int_t, TH2F *>::const_iterator sit = m_scatter.find(r);
2087 if (sit == m_scatter.end())
2088 continue;
2089 std::lock_guard<std::mutex> lock(g_plot_mutex);
2090 TH2F *ref = sit->second;
2091 TH2F *frame =
2092 new TH2F(Form("sim_frame_r%d", r), "", 10, ref->GetXaxis()->GetXmin(),
2093 ref->GetXaxis()->GetXmax(), 10, ref->GetYaxis()->GetXmin(),
2094 ref->GetYaxis()->GetXmax());
2095 frame->SetStats(0);
2096 frame->GetXaxis()->SetTitle(ref->GetXaxis()->GetTitle());
2097 frame->GetYaxis()->SetTitle(ref->GetYaxis()->GetTitle());
2098 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
2099 c->SetLeftMargin(0.18);
2100 frame->Draw();
2101 // Match the experimental region-traces legend placement (top-right).
2102 TLegend *leg = PlottingUtils::AddLegend(0.725, 0.875, 0.70, 0.86);
2103 for (Int_t i = 0; i < Int_t(it->second.size()); i++) {
2104 TGraph *g = it->second[i];
2105 // Match the experimental region-trace colours (DrawRegionTraces): beam
2106 // grey, (a,a') azure, (a,n) red -- keyed off the population label.
2107 TString lab = g->GetTitle();
2108 Int_t color = kBlack;
2109 if (lab == "Beam")
2110 color = kGray + 2;
2111 else if (lab == "(#alpha,#alpha')")
2112 color = kAzure + 2;
2113 else if (lab == "(#alpha,n)")
2114 color = kRed + 1;
2115 g->SetMarkerStyle(20);
2116 g->SetMarkerSize(0.3);
2117 g->SetMarkerColorAlpha(color, 0.35);
2118 g->SetLineColor(color);
2119 g->Draw("P SAME");
2120 leg->AddEntry(g, g->GetTitle(), "p");
2121 }
2122 leg->Draw();
2123 PlottingUtils::SaveFigure(c,
2124 Form("sim_normsumE_reac%d_s%d_%d_vs_s%d_%d", r,
2125 YLoOf(r), YHiOf(r), kXLo, kXHi),
2126 "sim_scatter", PlotSaveOptions::kLINEAR);
2127 delete leg;
2128 delete c;
2129 delete frame;
2130 }
2131
2132 std::map<Int_t, std::vector<TGraph *>>::iterator dit;
2133 for (dit = by_strip.begin(); dit != by_strip.end(); ++dit)
2134 for (Int_t i = 0; i < Int_t(dit->second.size()); i++)
2135 delete dit->second[i];
2136}
2137
2138Bool_t StripSumScatter::TryLoadCache(const TString &cacheName,
2139 const TString &fingerprint) {
2140 const Int_t kReacMin =
2142 const Int_t kReacMax =
2144
2145 TString cache_full = IO::GetRootFilesBaseDir() + TString("/") + cacheName;
2146 if (gSystem->AccessPathName(cache_full)) {
2147 std::cout << "strip-sum-scatter: no cache file found; will rebuild."
2148 << std::endl;
2149 return kFALSE;
2150 }
2151
2152 TFile *cf = IO::OpenForReading(cacheName);
2153 if (!cf || cf->IsZombie()) {
2154 if (cf)
2155 delete cf;
2156 std::cout << "strip-sum-scatter: cache file unreadable; rebuilding."
2157 << std::endl;
2158 return kFALSE;
2159 }
2160
2161 TNamed *fp = static_cast<TNamed *>(cf->Get("fingerprint"));
2162 const Bool_t exact = fp && fingerprint == fp->GetTitle();
2163 // Same tagging, different plane: the reservoir has every tagged event, so
2164 // the scatters are rebuilt from it below and the cache rewritten.
2165 const Bool_t reproject = !exact && fp && !TagPart(fingerprint).IsNull() &&
2166 TagPart(fingerprint) == TagPart(fp->GetTitle());
2167 if (!exact && !reproject) {
2168 std::cout << "strip-sum-scatter: cache present but stale; rebuilding."
2169 << std::endl;
2170 std::cout << " cached: " << (fp ? fp->GetTitle() : "(none)") << std::endl;
2171 std::cout << " wanted: " << fingerprint << std::endl;
2172 cf->Close();
2173 delete cf;
2174 return kFALSE;
2175 }
2176
2177 Bool_t ok = kTRUE;
2178 for (Int_t reac = kReacMin; reac <= kReacMax && ok && exact; reac++) {
2179 TH2F *h = static_cast<TH2F *>(cf->Get(Form("scatter_r%d", reac)));
2180 if (!h) {
2181 ok = kFALSE;
2182 break;
2183 }
2184 TH2F *hc = static_cast<TH2F *>(h->Clone());
2185 hc->SetDirectory(nullptr);
2186 m_scatter[reac] = hc;
2187 }
2188
2189 // The normalization counts ride along, so a re-projected cache keeps them.
2190 if (TParameter<Long64_t> *p =
2191 dynamic_cast<TParameter<Long64_t> *>(cf->Get("n_seen")))
2192 m_nSeen = p->GetVal();
2193 if (TParameter<Long64_t> *p =
2194 dynamic_cast<TParameter<Long64_t> *>(cf->Get("n_normed")))
2195 m_nNormed = p->GetVal();
2196 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2197 if (TParameter<Long64_t> *p = dynamic_cast<TParameter<Long64_t> *>(
2198 cf->Get(Form("n_normed_r%d", reac))))
2199 m_normedAt[ReacIndex(reac)] = p->GetVal();
2200 if (TParameter<Long64_t> *p = dynamic_cast<TParameter<Long64_t> *>(
2201 cf->Get(Form("n_tagged_r%d", reac))))
2202 m_tagged[ReacIndex(reac)] = p->GetVal();
2203 }
2204 // The cut-variation counts, by the variant names the cache lists; a cache
2205 // written without them (or with the variation off) carries none.
2206 m_variantNames.clear();
2207 m_taggedVar.clear();
2208 m_normedVar.clear();
2209 if (TNamed *vn = dynamic_cast<TNamed *>(cf->Get("cut_variants"))) {
2210 TString names = vn->GetTitle();
2211 Int_t from = 0;
2212 TString tok;
2213 while (names.Tokenize(tok, from, ",")) {
2214 if (tok.IsNull())
2215 continue;
2216 m_variantNames.push_back(tok);
2217 std::vector<Long64_t> counts(m_tagged.size(), 0),
2218 normed(m_tagged.size(), 0);
2219 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2220 if (TParameter<Long64_t> *p = dynamic_cast<TParameter<Long64_t> *>(
2221 cf->Get(Form("n_tagged_r%d_%s", reac, tok.Data()))))
2222 counts[ReacIndex(reac)] = p->GetVal();
2223 if (TParameter<Long64_t> *p = dynamic_cast<TParameter<Long64_t> *>(
2224 cf->Get(Form("n_normed_r%d_%s", reac, tok.Data()))))
2225 normed[ReacIndex(reac)] = p->GetVal();
2226 }
2227 m_taggedVar.push_back(counts);
2228 m_normedVar.push_back(normed);
2229 }
2230 }
2231
2232 TTree *tt = static_cast<TTree *>(cf->Get("traces"));
2233 if (reproject && !tt)
2234 ok = kFALSE;
2235 if (ok && tt) {
2236 TraceEvt e;
2237 tt->SetBranchAddress("leftdE", e.leftdE);
2238 tt->SetBranchAddress("rightdE", e.rightdE);
2239 tt->SetBranchAddress("strip0dE", &e.strip0dE);
2240 tt->SetBranchAddress("strip17dE", &e.strip17dE);
2241 tt->SetBranchAddress("leftdE_adc", e.leftdE_adc);
2242 tt->SetBranchAddress("rightdE_adc", e.rightdE_adc);
2243 tt->SetBranchAddress("strip0_adc", &e.strip0_adc);
2244 tt->SetBranchAddress("strip17_adc", &e.strip17_adc);
2245 tt->SetBranchAddress("reac_mask", &e.reac_mask);
2246 tt->SetBranchAddress("beam_flat", &e.beam_flat);
2247 tt->SetBranchAddress("both_mult", &e.both_mult);
2248 e.seed_ts = 0;
2249 if (tt->GetBranch("seed_ts"))
2250 tt->SetBranchAddress("seed_ts", &e.seed_ts);
2251 Long64_t nt = tt->GetEntries();
2252 m_reservoir.reserve(nt);
2253 for (Long64_t j = 0; j < nt; j++) {
2254 tt->GetEntry(j);
2255 m_reservoir.push_back(e);
2256 }
2257 }
2258
2259 cf->Close();
2260 delete cf;
2261
2262 if (!ok) {
2263 std::cout << "strip-sum-scatter: cache partially corrupt; rebuilding."
2264 << std::endl;
2265 m_reservoir.clear();
2266 return kFALSE;
2267 }
2268 if (reproject) {
2269 std::cout << "strip-sum-scatter: cache tagging matches but the plane "
2270 "changed; re-projecting "
2271 << m_reservoir.size() << " reservoir events." << std::endl;
2272 AllocateScatters();
2273 ReprojectFromReservoir();
2274 WriteCache(cacheName, fingerprint);
2275 } else {
2276 std::cout << "strip-sum-scatter: loaded cached scatters + "
2277 << m_reservoir.size() << " reservoir events (fingerprint match)."
2278 << std::endl;
2279 }
2280 return kTRUE;
2281}
2282
2283void StripSumScatter::WriteCache(const TString &cacheName,
2284 const TString &fingerprint) {
2285 const Int_t kReacMin =
2287 const Int_t kReacMax =
2289
2290 TFile *out = IO::OpenForWriting(cacheName, "RECREATE");
2291 if (!out || out->IsZombie()) {
2292 if (out)
2293 delete out;
2294 return;
2295 }
2296 out->cd();
2297 TNamed fp("fingerprint", fingerprint.Data());
2298 fp.Write();
2299 // Normalization counts travel with the scatters they describe, so a cross
2300 // section never has to re-derive them from a different pass over the data.
2301 TParameter<Long64_t>("n_seen", m_nSeen).Write();
2302 TParameter<Long64_t>("n_normed", m_nNormed).Write();
2303 // The beam level and noise every sigma-unit cut was resolved against.
2304 for (Int_t s = 0; s < 18; s++) {
2305 TParameter<Double_t>(Form("strip_mean_s%d", s), s_stripMean[s]).Write();
2306 TParameter<Double_t>(Form("strip_sigma_s%d", s), s_stripSigma[s]).Write();
2307 }
2308 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2309 TParameter<Long64_t>(Form("n_normed_r%d", reac),
2310 m_normedAt[ReacIndex(reac)])
2311 .Write();
2312 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2313 TParameter<Long64_t>(Form("n_tagged_r%d", reac), m_tagged[ReacIndex(reac)])
2314 .Write();
2315 // The cut-variation counts: the variant names as one list, then per
2316 // variant and strip the tagged count and the denominator.
2317 {
2318 TString names;
2319 for (size_t v = 0; v < m_variantNames.size(); v++)
2320 names += (v ? "," : "") + m_variantNames[v];
2321 TNamed("cut_variants", names.Data()).Write();
2322 for (size_t v = 0; v < m_variantNames.size(); v++)
2323 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2324 TParameter<Long64_t>(
2325 Form("n_tagged_r%d_%s", reac, m_variantNames[v].Data()),
2326 m_taggedVar[v][ReacIndex(reac)])
2327 .Write();
2328 TParameter<Long64_t>(
2329 Form("n_normed_r%d_%s", reac, m_variantNames[v].Data()),
2330 m_normedVar[v][ReacIndex(reac)])
2331 .Write();
2332 }
2333 }
2334 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2335 m_scatter[reac]->Write(Form("scatter_r%d", reac));
2336
2337 TTree *tt = new TTree("traces", "strip-sum trace reservoir");
2338 TraceEvt e;
2339 tt->Branch("leftdE", e.leftdE, "leftdE[16]/F");
2340 tt->Branch("rightdE", e.rightdE, "rightdE[16]/F");
2341 tt->Branch("strip0dE", &e.strip0dE, "strip0dE/F");
2342 tt->Branch("strip17dE", &e.strip17dE, "strip17dE/F");
2343 tt->Branch("leftdE_adc", e.leftdE_adc, "leftdE_adc[16]/F");
2344 tt->Branch("rightdE_adc", e.rightdE_adc, "rightdE_adc[16]/F");
2345 tt->Branch("strip0_adc", &e.strip0_adc, "strip0_adc/F");
2346 tt->Branch("strip17_adc", &e.strip17_adc, "strip17_adc/F");
2347 tt->Branch("reac_mask", &e.reac_mask, "reac_mask/i");
2348 tt->Branch("beam_flat", &e.beam_flat, "beam_flat/O");
2349 tt->Branch("both_mult", &e.both_mult, "both_mult/I");
2350 tt->Branch("seed_ts", &e.seed_ts, "seed_ts/l");
2351 for (Int_t k = 0; k < Int_t(m_reservoir.size()); k++) {
2352 e = m_reservoir[k];
2353 tt->Fill();
2354 }
2355 tt->Write();
2356 out->Close();
2357 delete out;
2358 std::cout << "strip-sum-scatter: wrote cache " << cacheName << std::endl;
2359}
2360
2364SingleRunFitResult StripSumScatter::FitRunGates(Int_t key, const TString &label,
2365 TChain *chain) {
2366 (void)key;
2367 const TString run = label; // the group's name in every message
2368 SingleRunFitResult res;
2369 if (!chain || chain->GetEntries() == 0)
2370 return res;
2371
2372 // --- Beam classification ellipses (no gating) ---
2373 BeamEllipses be;
2374 be.ok = kFALSE;
2375 {
2376 const TString tag = label;
2377 const TString subdir = "strip_sum_scatter/" + label;
2378 Int_t ent_sx = 0, ent_sy = 1;
2379 EntranceAxes(ent_sx, ent_sy);
2380 const TString ent_tag = AxisTag(ent_sx) + "/" + AxisTag(ent_sy);
2381 const Double_t kPure =
2383 BeamFit2D ent_ell = FindBeamGate(chain, ent_sx, ent_sy, tag, subdir, kPure);
2384 if (ent_sy == GATE_AXIS_GRID)
2385 be.s1_grid = ent_ell;
2386 else if (ent_sx == 0)
2387 be.s0_s1 = ent_ell;
2388 else
2389 be.s1_s2 = ent_ell;
2390 if (ent_ell.ok) {
2391 std::lock_guard<std::mutex> lk(g_log_mutex);
2392 Constants::Detail() << " " << run << " beam ellipse " << ent_tag
2393 << ": mu=(" << ent_ell.mu_x << "," << ent_ell.mu_y
2394 << ")" << std::endl;
2395 } else {
2396 std::lock_guard<std::mutex> lk(g_log_mutex);
2397 std::cerr << " " << run << " beam ellipse " << ent_tag
2398 << " failed; skipping run" << std::endl;
2399 return res;
2400 }
2401 if (Constants::cfg.IGNORE_STRIP_17) {
2402 be.use_s15_s16 = kTRUE;
2403 be.s15_s16 = FindBeamGate(chain, 15, 16, tag, subdir, kPure);
2404 if (be.s15_s16.ok) {
2405 std::lock_guard<std::mutex> lk(g_log_mutex);
2407 << " " << run << " beam ellipse s15/s16: mu=(" << be.s15_s16.mu_x
2408 << "," << be.s15_s16.mu_y << ")" << std::endl;
2409 } else {
2410 std::lock_guard<std::mutex> lk(g_log_mutex);
2411 std::cerr << " " << run << " beam ellipse s15/s16 failed; skipping run"
2412 << std::endl;
2413 return res;
2414 }
2415 } else {
2416 be.use_s15_s16 = kFALSE;
2417 be.s16_s17 = FindBeamGate(chain, 16, 17, tag, subdir, kPure);
2418 if (be.s16_s17.ok) {
2419 std::lock_guard<std::mutex> lk(g_log_mutex);
2421 << " " << run << " beam ellipse s16/s17: mu=(" << be.s16_s17.mu_x
2422 << "," << be.s16_s17.mu_y << ")" << std::endl;
2423 } else {
2424 std::lock_guard<std::mutex> lk(g_log_mutex);
2425 std::cerr << " " << run << " beam ellipse s16/s17 failed; skipping run"
2426 << std::endl;
2427 return res;
2428 }
2429 }
2430 be.ok = kTRUE;
2431 }
2432 res.pure_beam = be;
2433
2434 // --- The strip gate ---
2435 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
2436 res.gate =
2437 FindStripGate(chain, C.GATE_STRIP, label, "strip_sum_scatter/" + label,
2439 if (!res.gate.ok) {
2440 std::lock_guard<std::mutex> lk(g_log_mutex);
2441 std::cerr << " " << run << " strip gate s" << C.GATE_STRIP
2442 << " failed; skipping run" << std::endl;
2443 }
2444 res.ok = res.gate.ok;
2445 return res;
2446}
2447
2448// Which variants move the beam selection: those re-evaluate the event
2449// level themselves; the rest share the nominal decision.
2450static void VariantMovesEventLevel(
2451 const std::vector<std::pair<TString, TagThresholds>> &variants,
2452 const TagThresholds &nominal, std::vector<Bool_t> &varies_event) {
2453 varies_event.assign(variants.size(), kFALSE);
2454 for (size_t v = 0; v < variants.size(); v++) {
2455 const TagThresholds &T = variants[v].second;
2456 varies_event[v] = T.gate_nsigma != nominal.gate_nsigma ||
2457 T.pileup_nsigma != nominal.pileup_nsigma ||
2458 T.noise_nsigma != nominal.noise_nsigma;
2459 }
2460}
2461
2462// The trace record: calibrated and raw ADC strips, the strip-0/-17 ends,
2463// the mirrored short side, the both-channel multiplicity, seed, mask, beam.
2464static void FillTraceEvt(TraceEvt &e, const EnergyView &ev, ULong64_t seed_ts,
2465 UInt_t mask, Bool_t beam) {
2466 for (Int_t k = 0; k < 16; k++) {
2467 e.leftdE[k] = Float_t(ev.left[k]);
2468 e.rightdE[k] = Float_t(ev.right[k]);
2469 e.leftdE_adc[k] = Float_t(ev.leftdE_adc[k]);
2470 e.rightdE_adc[k] = Float_t(ev.rightdE_adc[k]);
2471 }
2472 e.strip0dE = Float_t(ev.strip0);
2473 e.strip17dE = Float_t(ev.strip17);
2474 e.strip0_adc = Float_t(ev.strip0_adc);
2475 e.strip17_adc = Float_t(ev.strip17_adc);
2476 // Mirror IGNORE_SHORT_STRIPS on the raw record too: the decode zeroes the
2477 // short end, so the raw ADC trace drops the same side for comparability.
2479 for (Int_t s = 1; s <= 16; s++) {
2480 if ((s % 2) != 0)
2481 e.rightdE_adc[s - 1] = 0.0f;
2482 else
2483 e.leftdE_adc[s - 1] = 0.0f;
2484 }
2485 e.both_mult = CountBothEnds(ev, 16);
2486 e.seed_ts = seed_ts;
2487 e.reac_mask = mask;
2488 e.beam_flat = beam;
2489}
2490
2491StripSumScatter::EventLevelVerdict
2492StripSumScatter::JudgeEventLevel(const BeamGate1D &gate, const EnergyView &ev,
2493 Int_t gate_strip, Double_t step_z,
2494 const TagThresholds &T) {
2495 EventLevelVerdict v;
2496 v.gate = PassesGate(gate, ev, gate_strip, T.gate_nsigma);
2497 v.pileup = IsPileup(ev, T.pileup_nsigma);
2498 v.noise = IsNoise(ev, T.noise_nsigma);
2499 v.smooth_ok = !(T.smooth_nsigma > 0.0) || step_z <= T.smooth_nsigma;
2500 v.event = v.gate && !v.pileup && !v.noise;
2501 // Sequential rejection: the first cut that fails counts.
2502 if (!v.gate)
2503 v.pre_cut = kPRE_GATE;
2504 else if (v.pileup)
2505 v.pre_cut = kPRE_PILEUP;
2506 else if (v.noise)
2507 v.pre_cut = kPRE_NOISE;
2508 else if (!v.smooth_ok)
2509 v.pre_cut = kPRE_SMOOTH;
2510 return v;
2511}
2512
2513Bool_t StripSumScatter::AnyVariantKeeps(
2514 const BeamGate1D &gate, const EnergyView &ev, Int_t gate_strip,
2515 const std::vector<std::pair<TString, TagThresholds>> &variants,
2516 const std::vector<Bool_t> &varies_event, Bool_t nom_event,
2517 std::vector<Bool_t> &var_event) {
2518 // Every variant's event-level decision, before the smoothness it tests
2519 // itself.
2520 var_event.assign(variants.size(), nom_event);
2521 for (size_t v = 0; v < variants.size(); v++)
2522 if (varies_event[v]) {
2523 const TagThresholds &T = variants[v].second;
2524 var_event[v] = PassesGate(gate, ev, gate_strip, T.gate_nsigma) &&
2525 !IsPileup(ev, T.pileup_nsigma) &&
2526 !IsNoise(ev, T.noise_nsigma);
2527 }
2528 Bool_t any_event = nom_event;
2529 for (size_t v = 0; !any_event && v < variants.size(); v++)
2530 any_event = var_event[v];
2531 return any_event;
2532}
2533
2534void StripSumScatter::TagUnderVariants(
2535 const EnergyView &ev, Double_t step_z, Int_t kReacMin, Int_t kReacMax,
2536 const std::vector<std::pair<TString, TagThresholds>> &variants,
2537 const std::vector<Bool_t> &var_event, std::vector<Bool_t> &pass,
2538 SingleRunFillResult &res) {
2539 for (size_t v = 0; v < variants.size(); v++) {
2540 const TagThresholds &T = variants[v].second;
2541 if (!var_event[v] || (T.smooth_nsigma > 0.0 && step_z > T.smooth_nsigma))
2542 continue;
2543 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2544 pass[ReacIndex(reac)] = RejectReason(ev, reac, T) == kTAG_PASS;
2545 const Int_t won = ResolveTag(ev, pass);
2546 if (won >= 0)
2547 res.tagged_var[v][ReacIndex(won)]++;
2548 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2549 if ((won < 0 || reac <= won) && BeamUpstreamOf(ev, reac, T))
2550 res.normed_var[v][ReacIndex(reac)]++;
2551 }
2552}
2553
2554Int_t StripSumScatter::TagNominal(const EnergyView &ev,
2555 const Double_t totals[18], Int_t kReacMin,
2556 Int_t kReacMax, Int_t nReacStrips,
2557 std::vector<Bool_t> &pass,
2558 SingleRunFillResult &res, UInt_t &mask) {
2559 std::vector<TagCut> why(nReacStrips, kTAG_PASS);
2560 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2561 why[ReacIndex(reac)] = RejectReason(ev, reac);
2562 pass[ReacIndex(reac)] = why[ReacIndex(reac)] == kTAG_PASS;
2563 }
2564 const Int_t won = ResolveTag(ev, pass);
2565 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2566 TagCut w = why[ReacIndex(reac)];
2567 if (w == kTAG_PASS && reac != won)
2568 w = kCUT_OTHER_TAG;
2569 res.cut_counts[ReacIndex(reac) * kNTAGCUTS + w]++;
2570 }
2571 if (won >= 0) {
2572 mask |= (1u << ReacIndex(won));
2573 res.tagged[ReacIndex(won)]++;
2574 Double_t x = 0.0, y = 0.0;
2575 PlaneXY(totals, won, x, y);
2576 res.scatters[ReacIndex(won)]->Fill(x, y);
2577 }
2578 for (Int_t reac = kReacMin; reac <= kReacMax; reac++)
2579 if ((won < 0 || reac <= won) && BeamUpstreamOf(ev, reac))
2580 res.normed_at[ReacIndex(reac)]++;
2581 return won;
2582}
2583
2584void StripSumScatter::AllocateRunScatters(Int_t run, Int_t kReacMin,
2585 Int_t kReacMax, Int_t kXBins,
2586 Int_t kYBins,
2587 std::vector<TH2F *> &scatters) {
2588 // Private, directory-less clones over the same fixed build range as the
2589 // merged ones.
2590 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2591 TH2F *h =
2592 new TH2F(Form("scatter_r%d_run%d", reac, run), "", kXBins,
2595 h->SetDirectory(nullptr);
2596 scatters[ReacIndex(reac)] = h;
2597 }
2598}
2599
2604StripSumScatter::FillRunScatters(Int_t key, const TString &label, TChain *chain,
2605 const BeamGate1D &gate,
2606 const BeamEllipses &runBeam) {
2607 const Int_t run = key; // unique per task: names the private histograms
2608 const Int_t kReacMin =
2610 const Int_t kReacMax =
2612 const Int_t kXBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.XBINS;
2613 const Int_t kYBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.YBINS;
2614 const Int_t kGateStrip = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.GATE_STRIP;
2615 const Int_t kBeamReservoirCap =
2617 const Int_t nReacStrips = kReacMax - kReacMin + 1;
2618
2619 SingleRunFillResult res;
2620 res.scatters.assign(nReacStrips, nullptr);
2621 if (!chain)
2622 return res;
2623 AllocateRunScatters(run, kReacMin, kReacMax, kXBins, kYBins, res.scatters);
2624 Long64_t totalGated = 0, totalSeen = 0, totalNormed = 0;
2625 res.tagged.assign(nReacStrips, 0);
2626 res.normed_at.assign(nReacStrips, 0);
2627 res.cut_counts.assign(nReacStrips * kNTAGCUTS, 0);
2628 res.pre_counts.assign(kNPRECUTS, 0);
2629 // The cut-variation set, fixed for the run: each event past the event-level
2630 // cuts is tagged once per variant as well.
2631 const std::vector<std::pair<TString, TagThresholds>> variants =
2633 const TagThresholds nominal = NominalThresholds();
2634 res.tagged_var.assign(variants.size(), std::vector<Long64_t>(nReacStrips, 0));
2635 res.normed_var.assign(variants.size(), std::vector<Long64_t>(nReacStrips, 0));
2636 std::vector<Bool_t> variesEvent;
2637 VariantMovesEventLevel(variants, nominal, variesEvent);
2638 Int_t nBeamKept = 0;
2639 EnergyView ev;
2640 ev.Attach(chain);
2641 EnableEventBranches(chain);
2642 ULong64_t seed_ts_in = 0;
2643 if (chain->GetBranch("SeedTs"))
2644 chain->SetBranchAddress("SeedTs", &seed_ts_in);
2645 Long64_t n = chain->GetEntries();
2646 {
2647 std::lock_guard<std::mutex> lk(g_log_mutex);
2648 Constants::Detail() << " " << label << ": filling " << nReacStrips
2649 << " reaction-strip scatters over " << n << " events..."
2650 << std::endl;
2651 }
2652
2653 for (Long64_t j = 0; j < n; j++) {
2654 chain->GetEntry(j);
2655 ev.Decode();
2656 totalSeen++;
2657
2658 // An event with a strip that did not fire is incomplete whatever strip
2659 // is asked about, so it goes before anything per strip.
2660 if (!AllStripsFired(ev)) {
2662 continue;
2663 }
2664 // The beam selection, decided at the nominal level for the count and
2665 // at each variant's level for the cut variation; sequential rejection.
2666 const Double_t step_z = MaxStepNSigma(ev);
2667 const EventLevelVerdict nom =
2668 JudgeEventLevel(gate, ev, kGateStrip, step_z, nominal);
2669 if (nom.pre_cut >= 0)
2670 res.pre_counts[nom.pre_cut]++;
2671 std::vector<Bool_t> var_event;
2672 if (!AnyVariantKeeps(gate, ev, kGateStrip, variants, variesEvent, nom.event,
2673 var_event))
2674 continue;
2675 // Both-ends multiplicity: counted on raw ADC so it sees the short end
2676 // even when IGNORE_SHORT_STRIPS zeroes it in the decode.
2677 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BOTH_MULT_MAX >= 0) {
2678 const Int_t hi = TMath::Min(
2679 16, Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BOTH_MULT_COUNT_TO);
2680 if (CountBothEnds(ev, hi) >
2681 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.BOTH_MULT_MAX) {
2682 // Sequential: counted here only if the rest let it through. Not
2683 // varied: it drops the event for every variant too.
2684 if (nom.event && nom.smooth_ok)
2686 continue;
2687 }
2688 }
2689 // One tag per event: every strip's verdict, then the attributed strip
2690 // (ResolveTag); a passing strip that lost it counts as "other strip".
2691
2692 // Per-strip denominator: what met a reaction's conditions at `reac`
2693 // and had not reacted before; N(reac+1) = N(reac) - reactions at reac.
2694 UInt_t mask = 0;
2695 Double_t totals[18];
2696 ev.Totals(totals);
2697 std::vector<Bool_t> pass(nReacStrips, kFALSE);
2698 TagUnderVariants(ev, step_z, kReacMin, kReacMax, variants, var_event, pass,
2699 res);
2700 if (nom.event && nom.smooth_ok) {
2701 res.pre_counts[kPRE_PASS]++;
2702 // The last pre-reaction count: here, not at `seen`, is what makes the
2703 // tag-count ratio a cross section: same gate + quality efficiencies.
2704 totalNormed++;
2705 TagNominal(ev, totals, kReacMin, kReacMax, nReacStrips, pass, res, mask);
2706 }
2707
2708 // Keep reaction-passing events for traces; cap pure-beam (mutually
2709 // exclusive, no reaction jump); a per-task bound: the merge re-caps it.
2710 if (!nom.event || !nom.smooth_ok)
2711 continue;
2712 Bool_t beam = (mask == 0) && IsPureBeam(ev, runBeam);
2713 if (mask == 0 && !(beam && nBeamKept < kBeamReservoirCap))
2714 continue;
2715 if (beam)
2716 nBeamKept++;
2717
2718 TraceEvt e;
2719 FillTraceEvt(e, ev, seed_ts_in, mask, beam);
2720 res.reservoir.push_back(e);
2721 if (mask != 0)
2722 totalGated++;
2723 }
2724 res.gated = totalGated;
2725 res.seen = totalSeen;
2726 res.normed = totalNormed;
2727 return res;
2728}
2729
2730// Run `n` indexed tasks on `workers` threads, pulling from a shared queue.
2731void StripSumScatter::RunIndexedParallel(
2732 Int_t n, Int_t workers, const std::function<void(Int_t)> &task) {
2733 std::queue<Int_t> work;
2734 for (Int_t i = 0; i < n; i++)
2735 work.push(i);
2736 std::mutex work_mutex;
2737 std::vector<std::thread> pool;
2738 for (Int_t w = 0; w < workers; w++) {
2739 pool.emplace_back([&]() {
2740 while (true) {
2741 Int_t i;
2742 {
2743 std::lock_guard<std::mutex> lk(work_mutex);
2744 if (work.empty())
2745 return;
2746 i = work.front();
2747 work.pop();
2748 }
2749 task(i);
2750 }
2751 });
2752 }
2753 for (Int_t w = 0; w < Int_t(pool.size()); w++)
2754 pool[w].join();
2755}
2756
2757void StripSumScatter::AllocateScatters() {
2758 const Int_t kReacMin =
2760 const Int_t kReacMax =
2762 const Int_t kXLo = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO;
2763 const Int_t kXHi = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI;
2764 const Int_t kXBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.XBINS;
2765 const Int_t kYBins = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.YBINS;
2766 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2767 TH2F *h =
2768 new TH2F(Form("scatter_r%d", reac),
2769 Form(";norm. #DeltaE strips %d#rightarrow%d [a.u.];norm. "
2770 "#DeltaE strips %d#rightarrow%d [a.u.]",
2771 kXLo, kXHi, YLoOf(reac), YHiOf(reac)),
2774 h->SetDirectory(nullptr);
2775 h->SetStats(0);
2776 m_scatter[reac] = h;
2777 }
2778}
2779
2780void StripSumScatter::ReprojectFromReservoir() {
2781 const Int_t kReacMin =
2783 const Int_t kReacMax =
2785 Long64_t nFilled = 0;
2786 for (Int_t k = 0; k < Int_t(m_reservoir.size()); k++) {
2787 const TraceEvt &e = m_reservoir[k];
2788 if (e.reac_mask == 0)
2789 continue;
2790 Double_t total[18];
2791 e.Totals(total);
2792 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2793 if (!(e.reac_mask & (1u << ReacIndex(reac))))
2794 continue;
2795 Double_t x = 0.0, y = 0.0;
2796 PlaneXY(total, reac, x, y);
2797 m_scatter[reac]->Fill(x, y);
2798 nFilled++;
2799 }
2800 }
2801 std::cout << "strip-sum-scatter: re-projected " << nFilled
2802 << " tagged entries." << std::endl;
2803}
2804
2805// Phase 1: one run's beam ellipses and strip gate. The first groups of the
2806// epoch draw their gate figures; the rest are silent.
2807void StripSumScatter::FitGateRun(Int_t i, std::vector<SingleRunFitResult> &fits,
2808 const std::vector<Int_t> &runOrder,
2809 const std::vector<TString> &labelVec,
2810 const std::vector<TChain *> &chainVec) {
2812 fits[i] = FitRunGates(runOrder[i], labelVec[i], chainVec[i]);
2813}
2814
2815// Merge one file's fill into the totals; the pure-beam events are capped
2816// GLOBALLY, not per task, so the kept beam does not scale with file count.
2817void StripSumScatter::MergeRunFill(Int_t t,
2818 std::vector<SingleRunFillResult> &fills,
2819 Int_t &nBeamKept, Long64_t &totalGated,
2820 Long64_t &totalSeen) {
2821 const Int_t kReacMin =
2823 const Int_t kReacMax =
2825 const Int_t kBeamReservoirCap =
2827 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
2828 Int_t ri = ReacIndex(reac);
2829 if (ri < Int_t(fills[t].scatters.size()) && fills[t].scatters[ri])
2830 m_scatter[reac]->Add(fills[t].scatters[ri]);
2831 }
2832 for (Int_t k = 0; k < Int_t(fills[t].reservoir.size()); k++) {
2833 const TraceEvt &e = fills[t].reservoir[k];
2834 if (e.beam_flat) {
2835 if (nBeamKept >= kBeamReservoirCap)
2836 continue;
2837 nBeamKept++;
2838 }
2839 m_reservoir.push_back(e);
2840 }
2841 totalGated += fills[t].gated;
2842 totalSeen += fills[t].seen;
2843 m_nNormed += fills[t].normed;
2844 for (Int_t k = 0; k < Int_t(fills[t].normed_at.size()); k++)
2845 m_normedAt[k] += fills[t].normed_at[k];
2846 for (Int_t k = 0; k < Int_t(fills[t].tagged.size()); k++)
2847 m_tagged[k] += fills[t].tagged[k];
2848 if (m_taggedVar.size() == fills[t].tagged_var.size())
2849 for (size_t v = 0; v < m_taggedVar.size(); v++)
2850 for (Int_t k = 0; k < Int_t(fills[t].tagged_var[v].size()); k++) {
2851 m_taggedVar[v][k] += fills[t].tagged_var[v][k];
2852 m_normedVar[v][k] += fills[t].normed_var[v][k];
2853 }
2854 for (Int_t k = 0; k < Int_t(fills[t].cut_counts.size()); k++)
2855 m_cutCounts[k] += fills[t].cut_counts[k];
2856 for (Int_t k = 0; k < Int_t(fills[t].pre_counts.size()); k++)
2857 m_preCounts[k] += fills[t].pre_counts[k];
2858 for (Int_t k = 0; k < Int_t(fills[t].scatters.size()); k++)
2859 delete fills[t].scatters[k];
2860 fills[t].scatters.clear();
2861 std::vector<TraceEvt>().swap(fills[t].reservoir);
2862}
2863
2864// Phase 2: one file of the fill; finished prefixes merge in task order,
2865// freeing on the spot; the budget stays identical to sequential.
2866void StripSumScatter::FillScatterFile(
2867 Int_t t, const std::vector<FillTask> &tasks,
2868 const std::vector<Int_t> &runOrder, const std::vector<TString> &labelVec,
2869 const std::vector<SingleRunFitResult> &fits, FillPhase &phase) {
2870 const Int_t i = tasks[t].run_idx;
2871 // The first files of the epoch log their fill; the rest are silent.
2873 {
2874 TChain ch("events");
2875 ch.Add(tasks[t].path);
2876 if (ch.GetEntries() > 0)
2877 phase.fills[t] = FillRunScatters(runOrder[i], labelVec[i], &ch,
2878 fits[i].gate, fits[i].pure_beam);
2879 }
2880 std::lock_guard<std::mutex> lk(phase.merge_mutex);
2881 phase.filled[t] = kTRUE;
2882 const Int_t nTasks = Int_t(tasks.size());
2883 while (phase.next_merge < nTasks && phase.filled[phase.next_merge]) {
2884 MergeRunFill(phase.next_merge, phase.fills, phase.nBeamKept,
2885 phase.totalGated, phase.totalSeen);
2886 phase.next_merge++;
2887 }
2888}
2889
2890void StripSumScatter::FillScatters(const FileSet::GateGroups &groups) {
2891 const std::vector<Int_t> &runOrder = groups.order;
2892
2893 AllocateScatters();
2894 // The variant counters, in the order every per-run fill uses.
2895 {
2896 const std::vector<std::pair<TString, TagThresholds>> variants =
2898 m_variantNames.clear();
2899 m_taggedVar.assign(variants.size(),
2900 std::vector<Long64_t>(m_tagged.size(), 0));
2901 m_normedVar.assign(variants.size(),
2902 std::vector<Long64_t>(m_tagged.size(), 0));
2903 for (size_t v = 0; v < variants.size(); v++)
2904 m_variantNames.push_back(variants[v].first);
2905 }
2906
2907 // Pre-index the chains so worker threads only touch their own group: a
2908 // run's chunks on SOLARIS, one subfile on CoMPASS.
2909 const Int_t nRuns = Int_t(runOrder.size());
2910 std::vector<TChain *> chainVec(nRuns);
2911 std::vector<TString> labelVec(nRuns);
2912 for (Int_t i = 0; i < nRuns; i++) {
2913 chainVec[i] = groups.chain.find(runOrder[i])->second;
2914 labelVec[i] = groups.label.find(runOrder[i])->second;
2915 }
2916
2917 Int_t n_workers =
2918 TMath::Min(Int_t(std::thread::hardware_concurrency()), nRuns);
2919 n_workers = TMath::Min(
2920 n_workers, Constants::cfg.STRIP_SUM_SCATTER_CONFIG.MAX_STRIP_SUM_WORKERS);
2921 if (n_workers < 1)
2922 n_workers = 1;
2923 std::cout << "strip-sum-scatter: " << nRuns << " gate groups on " << n_workers
2924 << " workers" << std::endl;
2925
2926 // Phase 1: beam ellipses and the strip gate, one task per run.
2927 std::vector<SingleRunFitResult> fits(nRuns);
2928 RunIndexedParallel(nRuns, n_workers, [&](Int_t i) {
2929 FitGateRun(i, fits, runOrder, labelVec, chainVec);
2930 });
2931
2932 // Phase 2: fill, split per file, not per run: a CoMPASS run is hundreds of
2933 // subfiles in one chain (SOLARIS: one per run); tasks carry their gates.
2934 std::vector<FillTask> tasks;
2935 {
2936 std::map<Int_t, Int_t> idx_of_run;
2937 for (Int_t i = 0; i < nRuns; i++)
2938 idx_of_run[runOrder[i]] = i;
2939 std::vector<FileSpec> specs = FileSet::BuildProcessedFileSpecs();
2940 for (Int_t k = 0; k < Int_t(specs.size()); k++) {
2941 // The file's gate group: its run on SOLARIS, itself on CoMPASS.
2942 std::map<TString, Int_t>::const_iterator kit =
2943 groups.key_of.find(FileSet::EventsName(specs[k]));
2944 if (kit == groups.key_of.end())
2945 continue;
2946 std::map<Int_t, Int_t>::const_iterator it = idx_of_run.find(kit->second);
2947 if (it == idx_of_run.end() || !fits[it->second].ok)
2948 continue;
2949 TString full = IO::GetRootFilesBaseDir() + "/" +
2950 FileSet::EventsName(specs[k]) + ".root";
2951 if (gSystem->AccessPathName(full))
2952 continue;
2953 FillTask t;
2954 t.run_idx = it->second;
2955 t.path = full;
2956 tasks.push_back(t);
2957 }
2958 }
2959 Int_t nTasks = Int_t(tasks.size());
2960 Int_t fill_workers =
2961 TMath::Min(Int_t(std::thread::hardware_concurrency()), nTasks);
2962 fill_workers =
2963 TMath::Min(fill_workers,
2964 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.MAX_STRIP_SUM_WORKERS);
2965 if (fill_workers < 1)
2966 fill_workers = 1;
2967 std::cout << "strip-sum-scatter: filling " << nTasks << " files on "
2968 << fill_workers << " workers" << std::endl;
2969
2970 // The workers fill their file and merge the finished prefixes in task
2971 // order, so the pure-beam budget stays identical to sequential.
2972 FillPhase phase;
2973 phase.fills.resize(nTasks);
2974 phase.filled.assign(nTasks, kFALSE);
2975 RunIndexedParallel(nTasks, fill_workers, [&](Int_t t) {
2976 FillScatterFile(t, tasks, runOrder, labelVec, fits, phase);
2977 });
2978 m_nSeen = phase.totalSeen;
2979 std::cout << "strip-sum-scatter: " << phase.totalGated
2980 << " reaction-tagged of " << phase.totalSeen << " events ("
2981 << m_nNormed << " past every pre-tag cut); reservoir "
2982 << m_reservoir.size() << std::endl;
2983 WriteCutReport();
2984}
2985
2991void StripSumScatter::WriteCutReport() const {
2992 const StripSumScatterConfig &C = Constants::cfg.STRIP_SUM_SCATTER_CONFIG;
2993 const Int_t kReacMin = C.REACTION_STRIP_MIN;
2994 const Int_t kReacMax = C.REACTION_STRIP_MAX;
2995 // Which conditions are on, from the same list the diagram draws.
2996 Bool_t active[kNTAGCUTS], pre_active[kNPRECUTS];
2997 for (Int_t c = 0; c < kNTAGCUTS; c++)
2998 active[c] = kTRUE;
2999 for (Int_t c = 0; c < kNPRECUTS; c++)
3000 pre_active[c] = kTRUE;
3001 const std::vector<SelectionStep> steps = DescribeSelection();
3002 for (size_t i = 0; i < steps.size(); i++) {
3003 if (steps[i].tag_cut >= 0)
3004 active[steps[i].tag_cut] = steps[i].on;
3005 if (steps[i].pre_cut >= 0)
3006 pre_active[steps[i].pre_cut] = steps[i].on;
3007 }
3008
3009 std::ostringstream out;
3010 out << Form("strip-sum-scatter tag cuts: %s, %lld events seen",
3011 Paths::DatasetName().Data(), m_nSeen)
3012 << std::endl;
3013 out << "Sequential counts: each condition counts the events that passed "
3014 "everything before it."
3015 << std::endl;
3016 out << std::endl;
3017 out << "Event-level cuts (before any reaction is asked about):" << std::endl;
3018 for (Int_t c = 1; c < kNPRECUTS; c++) {
3019 if (!pre_active[c])
3020 out << Form(" %-14s off", kPreCutName[c]) << std::endl;
3021 else
3022 out << Form(" %-14s %12lld (%6.2f%% of seen)", kPreCutName[c],
3023 m_preCounts[c],
3024 m_nSeen > 0 ? 100.0 * m_preCounts[c] / m_nSeen : 0.0)
3025 << std::endl;
3026 }
3027 out << Form(" %-14s %12lld (%6.2f%% of seen)", kPreCutName[kPRE_PASS],
3028 m_preCounts[kPRE_PASS],
3029 m_nSeen > 0 ? 100.0 * m_preCounts[kPRE_PASS] / m_nSeen : 0.0)
3030 << std::endl;
3031 out << std::endl;
3032
3033 out << "Tag conditions per reaction strip (events past the event-level "
3034 "cuts; % of those):"
3035 << std::endl;
3036 out << "reac";
3037 for (Int_t c = 1; c < kNTAGCUTS; c++)
3038 out << Form(" | %13s", kTagCutName[c]);
3039 out << Form(" | %13s", kTagCutName[kTAG_PASS]) << std::endl;
3040 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
3041 const Long64_t *row = &m_cutCounts[ReacIndex(reac) * kNTAGCUTS];
3042 Long64_t total = 0;
3043 for (Int_t c = 0; c < kNTAGCUTS; c++)
3044 total += row[c];
3045 out << Form("%4d", reac);
3046 for (Int_t c = 1; c < kNTAGCUTS; c++) {
3047 if (!active[c])
3048 out << Form(" | %13s", "off");
3049 else
3050 out << Form(" | %13s", Form("%lld (%.2f%%)", row[c],
3051 total > 0 ? 100.0 * row[c] / total : 0.0));
3052 }
3053 out << Form(" | %13s",
3054 Form("%lld (%.3f%%)", row[kTAG_PASS],
3055 total > 0 ? 100.0 * row[kTAG_PASS] / total : 0.0))
3056 << std::endl;
3057 }
3058 std::cout << out.str();
3059
3060 // The cut variation: tagged counts per strip under each shifted
3061 // threshold, beside the nominal, so the sensitivity is readable.
3062 if (!m_variantNames.empty()) {
3063 out << std::endl;
3064 out << "Cut variation (tagged events per strip with one threshold "
3065 "shifted; nominal first):"
3066 << std::endl;
3067 out << "reac | nominal";
3068 for (size_t v = 0; v < m_variantNames.size(); v++)
3069 out << Form(" | %9s", m_variantNames[v].Data());
3070 out << std::endl;
3071 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
3072 out << Form("%4d | %13lld", reac, m_tagged[ReacIndex(reac)]);
3073 for (size_t v = 0; v < m_variantNames.size(); v++)
3074 out << Form(" | %9lld", m_taggedVar[v][ReacIndex(reac)]);
3075 out << std::endl;
3076 }
3077 }
3078
3079 const TString dir = Paths::ResultsDir() + "/plots/strip_sum_scatter";
3080 gSystem->mkdir(dir, kTRUE);
3081 const TString path = dir + "/tag_cuts.txt";
3082 std::ofstream f(path.Data());
3083 if (!f) {
3084 std::cerr << "strip-sum-scatter: cannot write " << path << std::endl;
3085 return;
3086 }
3087 f << out.str();
3088 f.close();
3089 std::cout << "strip-sum-scatter: tag-cut report written to " << path
3090 << std::endl;
3091}
3092
3093void StripSumScatter::PlotScatters() {
3094 // Display windows recomputed here, not in FillScatters: a cached run skips
3095 // the fill; constructor zeros made SetRangeUser(0, 0) throw the zoom away.
3096 YBounds(m_yLo, m_yHi);
3097 const Int_t kXLo = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_LO;
3098 const Int_t kXHi = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_HI;
3099 const Int_t kReacMin =
3101 const Int_t kReacMax =
3103
3104 std::lock_guard<std::mutex> lock(g_plot_mutex);
3105 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
3106 TCanvas *c = PlottingUtils::GetConfiguredCanvas(kFALSE);
3107 // Display-only zoom: the histogram is built over the fixed build range, so
3108 // retuning these windows never forces a refill.
3109 Int_t ri = ReacIndex(reac);
3110 m_scatter[reac]->GetXaxis()->SetRangeUser(
3111 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.X_DISPLAY_MIN,
3113 m_scatter[reac]->GetYaxis()->SetRangeUser(m_yLo[ri], m_yHi[ri]);
3114 PlottingUtils::ConfigureAndDraw2DHistogram(m_scatter[reac], c);
3115 m_scatter[reac]->GetYaxis()->SetTitleOffset(1.3);
3116 c->SetLeftMargin(0.18);
3117 PlottingUtils::SaveFigure(c,
3118 Form("normsumE_reac%d_s%d_%d_vs_s%d_%d", reac,
3119 YLoOf(reac), YHiOf(reac), kXLo, kXHi),
3120 "strip_sum_scatter", PlotSaveOptions::kLINEAR);
3121 delete c;
3122 }
3123}
3124
3125void StripSumScatter::InteractiveOverlay(Int_t reac) {
3126 const Int_t kReacMin =
3128 const Int_t kReacMax =
3130 const Int_t kTracesPerRegion =
3132
3133 if (reac < kReacMin || reac > kReacMax) {
3134 std::cerr << "strip-sum-scatter: candidate reaction strip " << reac
3135 << " outside [" << kReacMin << "," << kReacMax
3136 << "]; skipping interactive overlay." << std::endl;
3137 return;
3138 }
3139 // A cut saved from a previous pass wins over prompting, so a decided run
3140 // repeats without a person in the loop or DISPLAY: nothing interactive opens.
3141 TCutG *cutAn = nullptr;
3142 TCutG *cutAa = nullptr;
3143 // Saved cuts -- drawn here earlier, or fitted by compute-regions -- win over
3144 // prompting, so a decided run repeats without a person in the loop.
3145 if (!Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REGION_CUT_REDRAW) {
3146 cutAn = LoadRegionCut("region_an", reac);
3147 cutAa = LoadRegionCut("region_aa", reac);
3148 }
3149 TCanvas *cutCanvas = nullptr;
3150
3151 // TApplication + argv must outlive all GUI canvases below, incl. trace ones:
3152 // tearing it down early faults ROOT's paint path; so leak it, argv static.
3153 static Int_t app_argc = 1;
3154 static char app_arg0[] = "strip-sum-scatter";
3155 static char *app_argv[] = {app_arg0};
3156
3157 if (cutAn && cutAa) {
3158 std::cout << " [region] loaded saved cuts for reac " << reac << std::endl;
3159 } else {
3160 delete cutAn;
3161 cutAn = nullptr;
3162 delete cutAa;
3163 cutAa = nullptr;
3164 if (!gSystem->Getenv("DISPLAY")) {
3165 std::cerr << "strip-sum-scatter: no saved region cuts for reac " << reac
3166 << " and no DISPLAY to draw them; skipping interactive "
3167 "region-trace overlay (scatters already saved)."
3168 << std::endl;
3169 return;
3170 }
3171 // Intentionally not stored and never deleted -- see the note above.
3172 new TApplication("strip-sum-scatter", &app_argc, app_argv);
3173 gROOT->SetBatch(kFALSE);
3174
3175 cutCanvas = new TCanvas("c_strip_sum_regions",
3176 "Draw (a,n) then (a,a') regions", 900, 700);
3177 cutCanvas->SetLogz(kTRUE); // match the saved scatter's z-scale
3178 m_scatter[reac]->Draw("COLZ");
3179 cutCanvas->Update();
3180 cutAn = PromptCut(cutCanvas, "region_an", "(a,n)");
3181 cutAa = PromptCut(cutCanvas, "region_aa", "(a,a')");
3182 SaveRegionCuts(reac, cutAn, cutAa);
3183
3184 cutCanvas->GetListOfPrimitives()->Remove(cutAn);
3185 cutCanvas->GetListOfPrimitives()->Remove(cutAa);
3186 gROOT->SetEditorMode();
3187 gSystem->ProcessEvents();
3188 // Re-entering batch with TApplication + GUI canvas faults in TPad::PaintBox
3189 // (TCanvas::Build border paint); deleting canvas or nulling gPad: no help.
3190 cutCanvas->Clear();
3191 gSystem->ProcessEvents();
3192 }
3193
3194 std::vector<TGraph *> tr_an, tr_aa, tr_beam;
3195 // Same selected events, raw (un-normalized) ADC -- one entry per normed
3196 // trace, kept in lock-step so the two overlays show the identical events.
3197 std::vector<TGraph *> tr_an_adc, tr_aa_adc, tr_beam_adc;
3198 // Same selected events, Savitzky-Golay smoothed (normed a.u. space), for
3199 // the smoothing overlay; in lock-step with raw normed traces, same events.
3200 const Bool_t kSkipSg =
3202 std::vector<TGraph *> tr_an_sg, tr_aa_sg, tr_beam_sg;
3203 UInt_t bit = (1u << ReacIndex(reac));
3204
3205 for (Int_t k = 0; k < Int_t(m_reservoir.size()); k++) {
3206 if (Int_t(tr_an.size()) >= kTracesPerRegion &&
3207 Int_t(tr_aa.size()) >= kTracesPerRegion &&
3208 Int_t(tr_beam.size()) >= kTracesPerRegion)
3209 break;
3210 const TraceEvt &e = m_reservoir[k];
3211 Double_t td[18], td_adc[18];
3212 e.Totals(td);
3213 e.TotalsAdc(td_adc);
3214 if (e.beam_flat && Int_t(tr_beam.size()) < kTracesPerRegion) {
3215 tr_beam.push_back(TraceFromTotal(td));
3216 tr_beam_adc.push_back(TraceFromTotal(td_adc));
3217 if (!kSkipSg)
3218 tr_beam_sg.push_back(SmoothedTraceFromTotal(td));
3219 continue;
3220 }
3221 if (!(e.reac_mask & bit))
3222 continue;
3223 Double_t x = 0.0, y = 0.0;
3224 PlaneXY(td, reac, x, y);
3225 if (cutAn && Int_t(tr_an.size()) < kTracesPerRegion &&
3226 cutAn->IsInside(x, y)) {
3227 tr_an.push_back(TraceFromTotal(td));
3228 tr_an_adc.push_back(TraceFromTotal(td_adc));
3229 if (!kSkipSg)
3230 tr_an_sg.push_back(SmoothedTraceFromTotal(td));
3231 } else if (cutAa && Int_t(tr_aa.size()) < kTracesPerRegion &&
3232 cutAa->IsInside(x, y)) {
3233 tr_aa.push_back(TraceFromTotal(td));
3234 tr_aa_adc.push_back(TraceFromTotal(td_adc));
3235 if (!kSkipSg)
3236 tr_aa_sg.push_back(SmoothedTraceFromTotal(td));
3237 }
3238 }
3239
3240 std::cout << "Sampled traces: beam=" << tr_beam.size()
3241 << " (a,a')=" << tr_aa.size() << " (a,n)=" << tr_an.size()
3242 << std::endl;
3243 DrawRegionTraces(Form("region_traces_reac%d", reac), "strip_sum_scatter",
3244 tr_beam, tr_aa, tr_an, 0.6, 1.6, "#DeltaE [a.u.]");
3245 const Bool_t kMeanTraces =
3247 if (kMeanTraces)
3248 DrawRegionMeanTraces(Form("region_mean_traces_reac%d", reac),
3249 "strip_sum_scatter", tr_beam, tr_aa, tr_an, 0.6, 1.6,
3250 "#DeltaE [a.u.]");
3251 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PLOT_ADC_TRACES) {
3252 Double_t adc_y_lo = 0.0, adc_y_hi = 0.0;
3253 TraceYRange(tr_beam_adc, tr_aa_adc, tr_an_adc, adc_y_lo, adc_y_hi);
3254 DrawRegionTraces(Form("region_traces_reac%d_adc", reac),
3255 "strip_sum_scatter", tr_beam_adc, tr_aa_adc, tr_an_adc,
3256 adc_y_lo, adc_y_hi, "#DeltaE [ADC]");
3257 if (kMeanTraces)
3258 DrawRegionMeanTraces(Form("region_mean_traces_reac%d_adc", reac),
3259 "strip_sum_scatter", tr_beam_adc, tr_aa_adc,
3260 tr_an_adc, adc_y_lo, adc_y_hi, "#DeltaE [ADC]");
3261 }
3262 if (!kSkipSg) {
3263 DrawRegionTraces(Form("region_traces_reac%d_sg", reac), "strip_sum_scatter",
3264 tr_beam_sg, tr_aa_sg, tr_an_sg, 0.6, 1.6, "#DeltaE [a.u.]",
3265 kFALSE);
3266 if (kMeanTraces)
3267 DrawRegionMeanTraces(Form("region_mean_traces_reac%d_sg", reac),
3268 "strip_sum_scatter", tr_beam_sg, tr_aa_sg, tr_an_sg,
3269 0.7, 1.3, "#DeltaE [a.u.]");
3270 }
3271
3272 for (Int_t i = 0; i < Int_t(tr_an.size()); i++)
3273 delete tr_an[i];
3274 for (Int_t i = 0; i < Int_t(tr_aa.size()); i++)
3275 delete tr_aa[i];
3276 for (Int_t i = 0; i < Int_t(tr_beam.size()); i++)
3277 delete tr_beam[i];
3278 for (Int_t i = 0; i < Int_t(tr_an_adc.size()); i++)
3279 delete tr_an_adc[i];
3280 for (Int_t i = 0; i < Int_t(tr_aa_adc.size()); i++)
3281 delete tr_aa_adc[i];
3282 for (Int_t i = 0; i < Int_t(tr_beam_adc.size()); i++)
3283 delete tr_beam_adc[i];
3284 for (Int_t i = 0; i < Int_t(tr_an_sg.size()); i++)
3285 delete tr_an_sg[i];
3286 for (Int_t i = 0; i < Int_t(tr_aa_sg.size()); i++)
3287 delete tr_aa_sg[i];
3288 for (Int_t i = 0; i < Int_t(tr_beam_sg.size()); i++)
3289 delete tr_beam_sg[i];
3290
3291 delete cutAn;
3292 delete cutAa;
3293}
3294
3296 // Required before the threaded fill touches TChains from worker threads.
3297 ROOT::EnableThreadSafety();
3298 InitUtils::SetROOTPreferences(PlotSaveFormat::kPNG,
3299 Paths::ResultsDir() + "/plots",
3300 Paths::ResultsDir() + "/root_files");
3301 gROOT->SetBatch(kTRUE);
3302
3303 // The selection as configured, as a block diagram: needs no data, so it
3304 // is on disk whatever happens below.
3306 DescribeSelection(), Paths::ResultsDir() + "/plots/strip_sum_scatter",
3309 : TString("")) +
3310 " event selection");
3311
3312 // The gate groups: a run's chunks on SOLARIS, one subfile on CoMPASS. Every
3313 // per-group step below runs one task per group.
3315 const std::vector<Int_t> &run_order = groups.order;
3316 std::map<Int_t, TChain *> &chain_by_run = groups.chain;
3317 if (run_order.empty()) {
3318 std::cerr << "strip-sum-scatter: no runs found" << std::endl;
3319 return kFALSE;
3320 }
3321 const TString first_label = groups.label[run_order[0]];
3322
3323 // Every level and step cut is in sigma of the beam, so the beam comes
3324 // first: the first group's ellipses select its beam events, the reference.
3325 Double_t strip_mean[18], strip_sigma[18];
3326 {
3328 SingleRunFitResult first =
3329 FitRunGates(run_order[0], first_label, chain_by_run[run_order[0]]);
3330 if (!first.pure_beam.ok ||
3331 !MeasureBeamNoise(chain_by_run[run_order[0]], first.pure_beam,
3332 strip_mean, strip_sigma)) {
3333 std::cerr << "strip-sum-scatter: cannot measure the beam on "
3334 << first_label << std::endl;
3335 for (Int_t i = 0; i < Int_t(run_order.size()); i++)
3336 delete chain_by_run[run_order[i]];
3337 return kFALSE;
3338 }
3339 }
3340 SetStripNoise(strip_mean, strip_sigma);
3341 {
3342 const Int_t kReacMin =
3343 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN;
3344 const Int_t kReacMax =
3345 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MAX;
3346 TString line = Form("strip-sum-scatter: beam mean (sigma) from the gated "
3347 "sample of %s:",
3348 first_label.Data());
3349 for (Int_t strip = 1; strip <= 17; strip++)
3350 line += Form(" s%d %.4f (%.4f)", strip, strip_mean[strip],
3351 strip_sigma[strip]);
3352 std::cout << line << std::endl;
3353 std::cout << Form("strip-sum-scatter: jump gate %.2f sigma -> %.4f at "
3354 "strip %d, %.4f at strip %d",
3355 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REAC_JUMP_NSIGMA,
3356 JumpMin(kReacMin), kReacMin, JumpMin(kReacMax), kReacMax)
3357 << std::endl;
3358 }
3359
3360 // Build fingerprint and try cache.
3361 TString fingerprint = BuildFingerprint(groups);
3362 TString cache_name = CacheName();
3363
3364 Bool_t loaded = TryLoadCache(cache_name, fingerprint);
3365
3366 if (!loaded) {
3367 FillScatters(groups);
3368 WriteCache(cache_name, fingerprint);
3369 }
3370
3371 // Batch plotting (always done).
3372 PlotScatters();
3373
3374 // The chains served the beam measurement and the fill; the overlays work
3375 // from the scatters and the reservoir.
3376 for (Int_t i = 0; i < Int_t(run_order.size()); i++)
3377 delete chain_by_run[run_order[i]];
3378 return kTRUE;
3379}
3380
3382 if (!Prepare())
3383 return;
3384
3385 // Optional sim overlays, and the selection run over the sim.
3386 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.RERUN_SIM) {
3387 SimOverlay();
3388 SimTraceOverlay();
3389 SimTagReport();
3390 }
3391
3392 // Interactive region-trace overlay (requires DISPLAY).
3393 Int_t reac = Constants::cfg.STRIP_SUM_SCATTER_CONFIG.CANDIDATE_REAC_STRIP;
3394 InteractiveOverlay(reac);
3395}
3396
3397void StripSumScatter::DrawAllTaggedTraces(const TString &subdir) {
3398 const Int_t kReacMin =
3399 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MIN;
3400 const Int_t kReacMax =
3401 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.REACTION_STRIP_MAX;
3402 const Int_t kTracesPerRegion =
3403 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.TRACES_PER_CLASS;
3404 const Bool_t kMeanTraces =
3405 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PLOT_REGION_MEAN_TRACES;
3406 // Same events Savitzky-Golay smoothed, as the strip-sum-scatter overlay
3407 // draws them, under the same option.
3408 const Bool_t kSkipSg =
3409 Constants::cfg.STRIP_SUM_SCATTER_CONFIG.SKIP_SAVGOL_PLOTS;
3410 const std::vector<TGraph *> none;
3411
3412 // The beam sample once: it is the same band under every strip's figure.
3413 std::vector<TGraph *> tr_beam, tr_beam_adc, tr_beam_sg;
3414 for (Int_t k = 0; k < Int_t(m_reservoir.size()) &&
3415 Int_t(tr_beam.size()) < kTracesPerRegion;
3416 k++) {
3417 const TraceEvt &e = m_reservoir[k];
3418 if (!e.beam_flat)
3419 continue;
3420 Double_t td[18], td_adc[18];
3421 e.Totals(td);
3422 e.TotalsAdc(td_adc);
3423 tr_beam.push_back(TraceFromTotal(td));
3424 tr_beam_adc.push_back(TraceFromTotal(td_adc));
3425 if (!kSkipSg)
3426 tr_beam_sg.push_back(SmoothedTraceFromTotal(td));
3427 }
3428
3429 for (Int_t reac = kReacMin; reac <= kReacMax; reac++) {
3430 const UInt_t bit = (1u << ReacIndex(reac));
3431 std::vector<TGraph *> tr_tag, tr_tag_adc, tr_tag_sg;
3432 // Every tagged event at this strip, uncapped: in the all-tagged mode
3433 // the count IS these traces, so the figure shows all of them.
3434 for (Int_t k = 0; k < Int_t(m_reservoir.size()); k++) {
3435 const TraceEvt &e = m_reservoir[k];
3436 if (e.beam_flat || !(e.reac_mask & bit))
3437 continue;
3438 Double_t td[18], td_adc[18];
3439 e.Totals(td);
3440 e.TotalsAdc(td_adc);
3441 tr_tag.push_back(TraceFromTotal(td));
3442 tr_tag_adc.push_back(TraceFromTotal(td_adc));
3443 if (!kSkipSg)
3444 tr_tag_sg.push_back(SmoothedTraceFromTotal(td));
3445 }
3446 std::cout << " reac " << reac << ": " << tr_tag.size()
3447 << " tagged traces drawn against " << tr_beam.size()
3448 << " beam traces" << std::endl;
3449 DrawRegionTraces(Form("all_tagged_traces_reac%d", reac), subdir, tr_beam,
3450 none, tr_tag, 0.6, 1.6, "#DeltaE [a.u.]", kTRUE, "Tagged");
3451 if (kMeanTraces)
3452 DrawRegionMeanTraces(Form("all_tagged_mean_traces_reac%d", reac), subdir,
3453 tr_beam, none, tr_tag, 0.6, 1.6, "#DeltaE [a.u.]");
3454 if (Constants::cfg.STRIP_SUM_SCATTER_CONFIG.PLOT_ADC_TRACES) {
3455 Double_t adc_y_lo = 0.0, adc_y_hi = 0.0;
3456 TraceYRange(tr_beam_adc, none, tr_tag_adc, adc_y_lo, adc_y_hi);
3457 DrawRegionTraces(Form("all_tagged_traces_reac%d_adc", reac), subdir,
3458 tr_beam_adc, none, tr_tag_adc, adc_y_lo, adc_y_hi,
3459 "#DeltaE [ADC]", kTRUE, "Tagged");
3460 if (kMeanTraces)
3461 DrawRegionMeanTraces(Form("all_tagged_mean_traces_reac%d_adc", reac),
3462 subdir, tr_beam_adc, none, tr_tag_adc, adc_y_lo,
3463 adc_y_hi, "#DeltaE [ADC]");
3464 }
3465 if (!kSkipSg) {
3466 DrawRegionTraces(Form("all_tagged_traces_reac%d_sg", reac), subdir,
3467 tr_beam_sg, none, tr_tag_sg, 0.6, 1.6, "#DeltaE [a.u.]",
3468 kFALSE, "Tagged");
3469 if (kMeanTraces)
3470 DrawRegionMeanTraces(Form("all_tagged_mean_traces_reac%d_sg", reac),
3471 subdir, tr_beam_sg, none, tr_tag_sg, 0.7, 1.3,
3472 "#DeltaE [a.u.]");
3473 }
3474 for (Int_t i = 0; i < Int_t(tr_tag.size()); i++)
3475 delete tr_tag[i];
3476 for (Int_t i = 0; i < Int_t(tr_tag_adc.size()); i++)
3477 delete tr_tag_adc[i];
3478 for (Int_t i = 0; i < Int_t(tr_tag_sg.size()); i++)
3479 delete tr_tag_sg[i];
3480 }
3481 for (Int_t i = 0; i < Int_t(tr_beam.size()); i++)
3482 delete tr_beam[i];
3483 for (Int_t i = 0; i < Int_t(tr_beam_adc.size()); i++)
3484 delete tr_beam_adc[i];
3485 for (Int_t i = 0; i < Int_t(tr_beam_sg.size()); i++)
3486 delete tr_beam_sg[i];
3487}
const Int_t GATE_AXIS_GRID
Options which govern the reaction search in the 2D histograms (strip-sum-scatter).
Definition Constants.hpp:56
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
Definition FileSet.cpp:3
std::mutex g_log_mutex
Serialises multi-line progress logging from worker threads.
Definition FileSet.cpp:4
Block diagram of the event selection, generated from the configuration.
The reaction search: strip-sum scatters, beam gating and tagging.
TagCut
The conditions of the reaction tag, in the order they are applied.
@ kCUT_CLIFF
The fall happened in the last step (TAIL_CLIFF_*).
@ kCUT_UPSTREAM
A strip before the reaction was not beam-like.
@ kCUT_TAIL_RISE
A rise in the tail (TAIL_RISE_NSIGMA).
@ kCUT_CROSS
Read below the beam before POST_CROSS_MIN_STRIP.
@ kCUT_RERISE
Back at the beam, then above it again (TAIL_RERISE_*).
@ kCUT_POST_ABOVE
The excess did not persist (POST_ABOVE_*).
@ kCUT_END_STRIP
The end strip not END_STRIP_NSIGMA below the beam.
@ kTAG_PASS
@ kCUT_JUMP
Jump at the reaction strip below the gate.
@ kCUT_OTHER_TAG
Passed, but the event's one tag went to another strip.
@ kCUT_REAC_LEVEL
Reaction-strip deposit below 1 + the gate.
@ kNTAGCUTS
@ kPRE_ALL_STRIPS
A strip did not fire.
@ kPRE_BOTH_MULT
Both-ends multiplicity (BOTH_MULT_MAX).
@ kPRE_PASS
@ kPRE_NOISE
Noise.
@ kNPRECUTS
@ kPRE_PILEUP
Pileup.
@ kPRE_SMOOTH
A strip-to-strip step above SMOOTHNESS_NSIGMA.
@ kPRE_GATE
Failed a beam gate.
const char *const kPreCutName[kNPRECUTS]
Short labels for PreCut, indexed by it.
const char *const kTagCutName[kNTAGCUTS]
Short labels for TagCut, indexed by it.
static SpotFit FitSpotFromPoints(TH2F *h, std::vector< std::pair< Float_t, Float_t > > &pts, const Moments2D &seed)
A beam spot from a coarse histogram and the same events unbinned: the sample's sigma-clipped moments ...
static Bool_t FitPeak(TH1F *h, Double_t seed_mu, Double_t seed_sigma, Double_t window_nsigma, Bool_t fix_mu, Double_t &mu, Double_t &sigma, Double_t *chi2_ndf=nullptr)
The 1-D counterpart: a Gaussian on a flat pedestal, fitted over window_nsigma of the seed and judged ...
static void DrawEllipse(const BeamFit2D &b, Double_t n, Color_t color)
Draw the n-sigma contour of a fitted spot on the current pad.
Definition BeamFit2D.cpp:24
static Bool_t InEllipseXY(const BeamFit2D &b, Double_t x, Double_t y, Double_t n)
Whether a point lies inside the fitted ellipse at n sigma: the correlation-aware Mahalanobis chi2 bel...
Definition BeamFit2D.cpp:9
static Moments2D SeedSpotMoments(TH2F *h, Int_t seed_half_bins)
Second moments of a coarse spot histogram's seed window: the maximum bin, ± seed_half_bins around it,...
StripSumScatterConfig STRIP_SUM_SCATTER_CONFIG
Bool_t IGNORE_STRIP_0
Drop strip 0 / strip 17 from the analysis altogether: not required for a complete event,...
TString SIM_BEAM_FILE
Bool_t IGNORE_STRIP_17
static TGraph * BuildTraceFromTotals(const Double_t *total)
Build a trace graph from one event's per-strip totals.
static TString EventsName(const FileSpec &s)
Filename of the built-events ROOT file for a subfile.
Definition FileSet.cpp:292
static GateGroups GroupEventsForGating()
Build the gate groups; see GateGroups.
Definition FileSet.cpp:306
static std::vector< FileSpec > BuildProcessedFileSpecs()
Every subfile that already has processed output.
Definition FileSet.cpp:270
static Long64_t SampleStride(Long64_t n_total, Long64_t max_points)
Stride that visits at most max_points of n_total entries.
Definition FileSet.cpp:333
static TString DatasetName()
The dataset's isotope name, e.g.
Definition Paths.cpp:22
static TString DatasetDir()
Absolute path to the active dataset directory, analysis/<iso>.
Definition Paths.cpp:63
static TString ResultsDir()
Absolute path to the directory receiving generated output.
Definition Paths.cpp:24
static TString SimRootPath(const SimFileSpec &s)
Absolute path to a simulation's ROOT file.
Definition RemixSim.cpp:113
static Int_t ReactionStripOf(const TString &tag)
Which strip a simulated reaction occurs on.
Definition RemixSim.cpp:117
static TString TagWithoutStrip(const TString &tag)
The tag with any trailing _s<N> reaction-strip token removed.
Definition RemixSim.cpp:130
static std::vector< SimFileSpec > BuildFileSpecs()
Every simulation this dataset defines.
Definition RemixSim.cpp:70
static TString CacheName()
Filename of the scatter cache for this configuration.
static Bool_t IsNoise(const EnergyView &ev)
static Int_t YLoOf(Int_t reac)
First strip of the post-trigger window summed onto y.
static Int_t YHiOf(Int_t reac)
Last strip of the post-trigger window, inclusive.
static TagThresholds NominalThresholds()
The thresholds the configuration sets.
static void SetStripNoise(const Double_t *mean, const Double_t *sigma)
Install the per-strip beam means and sigmas.
static Bool_t IsPileup(const EnergyView &ev)
The event-level pileup and noise cuts (PILEUP_* / NOISE_*), in sigma of the measured beam spread.
static Bool_t IsSmooth(const EnergyView &ev, Double_t nsigma)
The event-level smoothness cut: MaxStepNSigma() at or under nsigma, or the cut is off (nsigma <= 0).
static Double_t JumpMin(Int_t reac)
Minimum jump for a tag: REAC_JUMP_NSIGMA * StripSigma(reac).
static Double_t StripSigma(Int_t strip)
Width of the beam's deposit on a strip, from the gated beam sample of the first run (MeasureBeamNoise...
void Run()
Build or load the scatters, tag reactions, and draw everything.
static std::vector< SelectionStep > DescribeSelection()
The whole selection as the configuration sets it, in order.
static TagCut TailReason(const EnergyView &ev, Int_t reac, const TagThresholds &T)
The tail-shape part of RejectReason: kTAG_PASS or the first failing tail condition,...
void DrawAllTaggedTraces(const TString &subdir)
Region-trace overlays of every tagged event, one figure per reaction strip, against the beam band.
StripSumScatter()
Construct with empty scatters and no cache loaded.
static Int_t ResolveTag(const EnergyView &ev, const std::vector< Bool_t > &pass)
The one strip an event is tagged at, among those whose tag passed (pass[ReacIndex]),...
~StripSumScatter()
Frees the scatters and the reservoir.
static Bool_t Condition(TagCut c, const EnergyView &ev, Int_t reac, const TagThresholds &T)
One tag condition on its own: whether c passes for reac under T, true where it does not apply at that...
static Bool_t ConditionActive(TagCut c, const TagThresholds &T)
Whether the configuration has condition c in force.
static TagCut RejectReason(const EnergyView &ev, Int_t reac)
Which condition of the tag an event fails first at a strip.
Bool_t Prepare()
Measure the beam, then load the scatter cache or build it.
static Double_t MaxStepNSigma(const EnergyView &ev)
The largest strip-to-strip step of the trace, |E(s) - E(s-1)| over strips 2 to the last in sigma of s...
static Double_t StripMean(Int_t strip)
Mean of the beam's deposit on a strip over the same sample; the level every cut measures from.
static void PlaneXY(const Double_t *total, Int_t reac, Double_t &x, Double_t &y)
Where an event sits in the scatter plane for a given reaction strip.
void SimTagReport()
Run the data selection over every simulated population: the event-level cuts (all but the beam gate,...
static std::vector< std::pair< TString, TagThresholds > > ThresholdVariants()
The cut-variation set: each active threshold shifted up and down by its configured step (CUT_VARIATIO...
const RunEpoch * GetActiveEpoch()
The currently active epoch, or null when none is set.
Bool_t ActiveIgnoreShortStrips()
Whether the decode keeps the long end only: the active epoch's setting; with none active,...
Bool_t SavePlots()
FileInSample(), by the name the plot call sites use.
Bool_t InPlotSample(Int_t k)
Whether the k-th file (0-based, in file order) of an epoch's list is in the plot sample.
std::ostream & Detail()
std::cout for a file in the sample, a stream that discards otherwise: the per-file detail lines (per ...
const TString & ActiveFileTag()
Output-name prefix of the active epoch.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
void SetPlotsThisFile(Bool_t on)
Mark the file this thread is about to process as in (or out of) the plot sample.
Bool_t ActiveUseSolarisData()
Whether this era's data is SOLARIS rather than CoMPASS.
void Save(Int_t reac, TCutG *cut_an, TCutG *cut_aa, Double_t n_an_assigned=-1.0)
Write a strip's two region cuts.
TCutG * Load(const char *name, Int_t reac)
Load a cut from its per-cut file.
const Double_t kXMin
Lower x bound of the build window.
const Double_t kXMax
Upper x bound; x sums 16 strips.
const Double_t kYMin
Lower y bound.
const Double_t kYMax
Upper y bound; y sums the post-trigger strips only.
void Write(const std::vector< SelectionStep > &steps, const TString &dir, const TString &title)
Write selection.dot and selection.mmd under dir, and render the DOT to selection.png and selection....
The classification ellipses defining a pure-beam event.
BeamFit2D s15_s16
Alternative exit ellipse.
BeamFit2D s1_grid
Alternative entrance ellipse on strip 1 and the grid.
BeamFit2D s0_s1
Entrance ellipse on strips 0 and 1.
Bool_t ok
Whether the fits succeeded.
Bool_t use_s15_s16
Which exit ellipse is in force.
BeamFit2D s16_s17
Exit ellipse on strips 16 and 17.
BeamFit2D s1_s2
Alternative entrance ellipse, per PURE_BEAM_GATE.
A fitted 2-D Gaussian beam spot.
Definition BeamFit2D.hpp:19
Double_t rho
Correlation coefficient, in [-1, 1].
Definition BeamFit2D.hpp:23
Double_t sigma_y
Widths along each axis.
Definition BeamFit2D.hpp:22
Double_t mu_x
Definition BeamFit2D.hpp:21
Double_t mu_y
Centroid.
Definition BeamFit2D.hpp:21
Bool_t ok
Whether the fit converged.
Definition BeamFit2D.hpp:24
Double_t sigma_x
Definition BeamFit2D.hpp:22
Double_t chi2_ndf
The fit's reduced chi2; -1 when it did not run.
Bool_t fit_used
kTRUE when fit is the fit, not the fallback.
BeamFit2D fit
The spot to use; ok when usable.
The event-level gate: one strip's fitted beam peak and width.
A view over one event's energies, decoding raw ADC into calibrated units.
UShort_t rightdE_adc[16]
Right end of strips 1-16, index strip - 1.
Double_t strip17
Strip 17.
void Decode()
Decode the currently loaded entry into the value members.
Bool_t Attach(TTree *t)
Bind to an events tree and set up the branch addresses.
Double_t Total(Int_t strip) const
A strip's deposit: the sum of its ends, or the unsegmented value.
Double_t left[16]
Left end of strips 1-16, index strip - 1.
Double_t Axis(Int_t axis) const
The value on a beam-gate axis: a strip's deposit, or the grid (GATE_AXIS_GRID) over its anchor.
void Totals(Double_t *out) const
Every strip's deposit at once, for code that wants an array.
UShort_t leftdE_adc[16]
Left end of strips 1-16, index strip - 1.
UShort_t strip17_adc
Strip 17, unsegmented.
Double_t strip0
Strip 0.
UShort_t strip0_adc
Strip 0, unsegmented.
Double_t right[16]
Right end of strips 1-16, index strip - 1.
The events files grouped into the units the beam gates are fitted on.
Definition FileSet.hpp:125
std::map< Int_t, TChain * > chain
Chain per key. Caller owns..
Definition FileSet.hpp:127
std::map< TString, Int_t > key_of
Group key per EventsName().
Definition FileSet.hpp:129
std::map< Int_t, TString > label
Label per key, for logs and plots.
Definition FileSet.hpp:128
std::vector< Int_t > order
Group keys in processing order.
Definition FileSet.hpp:126
Double_t weight
Total weight included; zero means the range held nothing above threshold.
Definition BeamFit2D.hpp:37
One entry of a simulation's events_MeV tree.
Definition RemixSim.hpp:91
Double_t Left(Int_t s) const
Left end of strip s (1-16), or the whole of strip 0 or 17.
Definition RemixSim.cpp:24
Float_t strip17
Strip 17, unsegmented.
Definition RemixSim.hpp:95
Double_t Total(Int_t s) const
The strip's deposit, Left(s) + Right(s).
Definition RemixSim.hpp:111
Bool_t Attach(TTree *t)
Bind to an events_MeV tree and set up the branch addresses.
Definition RemixSim.cpp:10
Float_t strip0
Strip 0, unsegmented.
Definition RemixSim.hpp:94
Double_t Right(Int_t s) const
Right end of strip s (1-16); zero for strips 0 and 17.
Definition RemixSim.cpp:32
One step of the event selection, as StripSumScatter::DescribeSelection lists them: in the order the c...
Int_t tag_cut
The TagCut it counts under, or -1.
TString id
Node identifier, [a-z0-9_].
Bool_t on
False when the configuration switches the step off.
Stage
Where in the selection the step sits.
Int_t pre_cut
The PreCut it counts under, or -1.
TString detail
One line: the condition to pass, with the numbers in.
TString name
Short name, as in the cut report.
TString label
Legend label.
TString file
Simulation ROOT file.
One run's filled scatters, reservoir and normalisation counts.
std::vector< Long64_t > normed_at
Per-strip denominator: beam particles incident on that strip.
std::vector< std::vector< Long64_t > > tagged_var
Events tagged under each threshold variant, [variant][ReacIndex], in the order of StripSumScatter::Th...
Long64_t seen
Events examined.
Long64_t normed
Events surviving every cut applied before reaction tagging: the beam gates, the pileup,...
std::vector< Long64_t > cut_counts
Sequential per-condition counts, [ReacIndex(reac) * kNTAGCUTS + cut], for the tag-cut report.
Long64_t gated
Events passing the beam gates.
std::vector< Long64_t > tagged
Events tagged at each reaction strip, indexed the same way.
std::vector< Long64_t > pre_counts
Sequential event-level counts, indexed by PreCut.
std::vector< std::vector< Long64_t > > normed_var
The per-strip denominator under each variant, indexed the same way: a variant of the beam selection m...
std::vector< TH2F * > scatters
Private clones, one per reaction strip.
std::vector< TraceEvt > reservoir
Tagged events from this run.
One run's fitted beam gates.
Bool_t ok
Whether the fits succeeded.
BeamGate1D gate
The strip gate.
BeamEllipses pure_beam
Entrance and exit ellipses.
Double_t CUT_VARIATION_CLIFF_STEP
Double_t SMOOTHNESS_NSIGMA
Event-level smoothness: every strip-to-strip step of the trace, from strips 1-2 to the last pair,...
Double_t PURE_BEAM_NSIGMA
Mahalanobis level of the pure-beam entrance and exit ellipses; separate from GATE_NSIGMA so the beam ...
Definition Constants.hpp:77
Double_t TAIL_RETURN_NSIGMA
After the post-window peak, once the trace has come back down to within TAIL_RETURN_NSIGMA x StripSig...
AnRegionMode AN_REGION_MODE
Int_t TAIL_FALL_FROM_STRIP
From TAIL_FALL_FROM_STRIP to the last strip, allow no strip-to-strip rise above TAIL_RISE_NSIGMA x St...
Int_t POST_CROSS_MIN_STRIP
The trace may not read below the beam mean before this strip: strips reac+1 .
Int_t POST_TRIGGER_SUM_STRIPS
Number of strips summed onto the scatter y-axis after the trigger strip: y spans reac+1 .
Definition Constants.hpp:82
Bool_t REQUIRE_STRIP_16_BELOW_BEAM
Bool_t SKIP_SAVGOL_PLOTS
Leave out the Savitzky-Golay-smoothed copies of the trace overlays (the _sg figures and cluster-varia...
Double_t REAC_JUMP_NSIGMA
Minimum jump at the reaction strip for a tag, in sigma of the measured beam spread of that strip (Str...
Bool_t PLOT_REGION_MEAN_TRACES
Also draw the per-region mean traces with RMS bands (the region_mean_traces_* figures) next to the tr...
Bool_t REQUIRE_BEAM_UPSTREAM_OF_REAC
How close to require strips to be to beam prior to the reaction, in sigma of each strip's measured be...
Double_t TAIL_CLIFF_MAX_FRACTION
A residue stops gradually; the beam nucleus after a large-angle elastic scatter keeps its excess to t...
Double_t END_STRIP_NSIGMA
The end strip (17, or 16 with REQUIRE_STRIP_16_BELOW_BEAM or IGNORE_STRIP_17) must read below the bea...
Double_t POST_ABOVE_NSIGMA
Every strip from reac+1 to reac+ POST_ABOVE_STRIPS (capped at the last strip) must sit more than POST...
Double_t GATE_CENTER
The peak to fit, in beam units; zero lets the fit find it.
Double_t AN_REGION_NSIGMA
compute-regions: the (a,n) region is the reaction component's AN_REGION_NSIGMA Mahalanobis ellipse fr...
Int_t GATE_STRIP
The event-level beam gate: one strip, within GATE_NSIGMA of its fitted beam peak per gate group.
Bool_t CUT_VARIATION
Cut-variation systematic, after the published 87Rb analysis: every condition that selects the beam or...
Double_t BEAM_UPSTREAM_NSIGMA
Double_t CUT_VARIATION_NSIGMA_STEP
Bool_t Y_RATIO_TO_UPSTREAM
Normalize the partial dE sum on y axis by the event's own mean deposit over strips 1 .
std::map< Int_t, std::pair< Double_t, Double_t > > Y_DISPLAY_RANGE
Per-reaction-strip y-axis display windows, overriding Y_DISPLAY_MIN/MAX for individual strips (displa...
Int_t BOTH_MULT_MAX
Use the segmentation of strips to reject an event when more than MAX strips in 1.....
Double_t PILEUP_NSIGMA
Event-level cuts before any reaction is asked about, in sigma of each strip's measured beam spread (S...
Every threshold the tag applies, as one value set.
Double_t noise_nsigma
NOISE_NSIGMA; 0 = off.
Double_t smooth_nsigma
Event level; 0 = off.
Double_t post_above_nsigma
Double_t end_strip_nsigma
Double_t tail_rise_nsigma
Double_t gate_nsigma
GATE_NSIGMA; 0 = off.
Double_t pileup_nsigma
PILEUP_NSIGMA; 0 = off.
Double_t upstream_nsigma
Double_t tail_return_nsigma
Double_t tail_rerise_nsigma
Double_t cross_min_strip
The strip the trace must reach without reading below the beam; an absolute strip index held as a doub...
One tagged event, kept in the reservoir.
Float_t strip17_adc
Float_t leftdE[16]
Left end of strips 1-16, index strip - 1.
Float_t rightdE[16]
Right end of strips 1-16, index strip - 1.
Int_t both_mult
Split strips (1-16) with both ends above threshold.
Float_t strip0dE
Strip 0, unsegmented.
Float_t strip0_adc
void TotalsAdc(Double_t *out) const
Every strip's raw ADC deposit, for code that wants an array of 18.
ULong64_t seed_ts
Timestamp of the grid hit that seeded the event, from the events tree.
Bool_t beam_flat
Whether the trace looked flat, i.e. beam-like.
Float_t leftdE_adc[16]
Float_t strip17dE
Strip 17, unsegmented.
UInt_t reac_mask
Bit per reaction strip this event was tagged at.
void Totals(Double_t *out) const
Every strip's deposit, for code that wants an array of 18.
Float_t rightdE_adc[16]