MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
EventBuilder.cpp
Go to the documentation of this file.
1#include "EventBuilder.hpp"
2#include "EventsSummary.hpp"
3
5 for (Int_t k = 0; k < 16; k++) {
6 e.leftdE[k] = 0;
7 e.rightdE[k] = 0;
8 }
9 e.strip0dE = 0;
10 e.strip17dE = 0;
11 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
12 e.hits[k] = 0;
13 e.cathode = -1;
14 e.grid = 0;
15 e.flags_or = 0;
16 e.had_cathode = kFALSE;
17 e.ref_ts = 0;
18}
19
21 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++) {
22 p.timestamps[k] = 0;
23 p.energies[k] = 0;
24 p.flags[k] = 0;
25 }
26}
27
28Bool_t EventBuilder::ShouldKeepHit(ULong64_t cand_ts, ULong64_t prev_ts,
29 UShort_t cand_energy, UShort_t prev_energy,
30 ULong64_t ref_ts, DedupStrategy strategy) {
31 if (strategy == kSMALLEST_ENERGY)
32 return cand_energy < prev_energy;
33 if (strategy == kLARGEST_ENERGY)
34 return cand_energy > prev_energy;
35 if (strategy == kLATEST_TIMESTAMP)
36 return cand_ts > prev_ts;
37
38 ULong64_t cand_dt = (cand_ts > ref_ts) ? cand_ts - ref_ts : ref_ts - cand_ts;
39 ULong64_t prev_dt = (prev_ts > ref_ts) ? prev_ts - ref_ts : ref_ts - prev_ts;
40 return cand_dt < prev_dt;
41}
42
44 SlotMap sm;
46 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
48 it != Constants::ActiveChannelMap().end(); ++it) {
49 Int_t board = it->first.first;
50 Int_t channel = it->first.second;
51 if (board < 0 || board >= Constants::ActiveNBoards())
52 continue;
53 if (channel < 0 || channel >= Constants::ActiveNChannels())
54 continue;
55 Int_t idx = board * Constants::ActiveNChannels() + channel;
56 const TString &name = it->second;
57 if (name == "Strip0") {
59 } else if (name == "Strip17") {
61 } else if (name == "Cathode") {
63 } else if (name == "Grid") {
65 } else if (name.Length() >= 2 && (name[0] == 'L' || name[0] == 'R')) {
66 TString numstr = name(1, name.Length() - 1);
67 if (numstr.IsDigit()) {
68 Int_t s = numstr.Atoi();
69 if (s >= 1 && s <= 16)
70 sm[idx] = (name[0] == 'L') ? s : (s + 16);
71 }
72 }
73 }
74 return sm;
75}
76
78 ULong64_t ref_ts, Int_t slot, UShort_t energy,
79 ULong64_t timestamp, UInt_t flags,
80 DedupStrategy strategy) {
81 // Slots are 0..ARR_SLOT_GRID by construction (BuildSlotMap); guard
82 // defensively so an out-of-range slot can never index the arrays below.
83 if (slot < 0 || slot >= Constants::N_ARR_SLOTS)
84 return;
85 e.flags_or |= flags;
86
87 Bool_t wins;
88 if (e.hits[slot] == 0) {
89 wins = kTRUE;
90 } else if (pc) {
91 wins = ShouldKeepHit(timestamp, pc->timestamps[slot], energy,
92 pc->energies[slot], ref_ts, strategy);
93 } else {
94 wins = kFALSE;
95 }
96
97 if (wins) {
98 // Slots 1-16 are the left ends of strips 1-16, 17-32 the right ends.
100 e.strip0dE = energy;
101 else if (slot <= 16)
102 e.leftdE[slot - 1] = energy;
103 else if (slot <= 32)
104 e.rightdE[slot - 17] = energy;
105 else if (slot == Constants::ARR_SLOT_STRIP_17)
106 e.strip17dE = energy;
107 else if (slot == Constants::ARR_SLOT_CATHODE)
108 e.cathode = energy;
109 else if (slot == Constants::ARR_SLOT_GRID)
110 e.grid = energy;
111 if (pc) {
112 pc->timestamps[slot] = timestamp;
113 pc->energies[slot] = energy;
114 pc->flags[slot] = flags;
115 }
116 }
117 if (slot == Constants::ARR_SLOT_CATHODE)
118 e.had_cathode = kTRUE;
119 e.hits[slot]++;
120}
121
122// The same first event-level cut as the analysis (AllStripsFired), so no
123// half-read event reaches the beam-gate fits.
125 if (!Constants::cfg.IGNORE_STRIP_0 && Constants::cfg.REQUIRE_STRIP_0 &&
126 e.strip0dE == 0)
127 return kFALSE;
128 if (!Constants::cfg.IGNORE_STRIP_17 && e.strip17dE == 0)
129 return kFALSE;
130 for (Int_t strip = 1; strip <= 16; strip++) {
131 const Int_t v =
132 (strip % 2) != 0 ? e.leftdE[strip - 1] : e.rightdE[strip - 1];
133 if (v == 0)
134 return kFALSE;
135 }
136 return kTRUE;
137}
138
139void EventBuilder::GetFlagSummary(const EventState &e, Bool_t &has_fake,
140 Bool_t &has_saturation, Bool_t &has_pileup) {
141 has_fake = (e.flags_or & CoMPASSData::FAKE_EVENT) != 0;
142 has_saturation = ((e.flags_or & CoMPASSData::SATURATION_IN_GATE) ||
143 (e.flags_or & CoMPASSData::INPUT_SATURATING)) != 0;
144 has_pileup = (e.flags_or & CoMPASSData::PILEUP) != 0;
145}
146
170
171void FinalizeEvent(EventState &e, TTree *output_tree, UShort_t *leftdE_branch,
172 UShort_t *rightdE_branch, UShort_t &strip0_branch,
173 UShort_t &strip17_branch, UShort_t *hits_branch,
174 Short_t &cathode_branch, Short_t &grid_branch,
175 UInt_t &flags_or_branch, ULong64_t &seed_ts_branch,
177 Long64_t event_idx = -1,
178 std::vector<TGraph *> *sample_traces = nullptr,
179 Long64_t sample_stride = 0, Int_t *n_sampled = nullptr) {
180 // Collect sample traces for overlay plot
181 if (sample_traces && event_idx >= 0 && sample_stride > 0 &&
182 event_idx % sample_stride == 0 &&
183 Int_t(sample_traces->size()) < Constants::cfg.SAVE_SAMPLE_TRACES) {
184 Double_t total[18];
185 for (Int_t s = 0; s < 18; s++)
186 total[s] = Double_t(e.Total(s));
187 sample_traces->push_back(EventsSummary::BuildTraceFromTotals(total));
188 if (n_sampled)
189 (*n_sampled)++;
190 }
191
192 if (e.had_cathode)
194 if (e.hits[34] > 1) {
196 c.dropped_cathode_hits_total += (e.hits[34] - 1);
197 }
198 Bool_t any_anode_multi = kFALSE;
199 for (Int_t k = 0; k < 34; k++) {
200 if (e.hits[k] > 1) {
201 any_anode_multi = kTRUE;
202 c.dropped_anode_hits_total += (e.hits[k] - 1);
203 }
204 }
205 if (any_anode_multi)
207
208 Bool_t is_complete = EventBuilder::CheckEventComplete(e);
209 c.total_events++;
210
211 // Per-required-channel miss tally (over all events): which completeness
212 // channel was zero. Long side = L for odd strips, R for even strips.
213 if (e.strip0dE == 0)
214 c.miss_strip0++;
215 if (e.strip17dE == 0)
216 c.miss_strip17++;
217 for (Int_t s = 1; s <= 16; s++) {
218 Int_t v = (s % 2 == 1) ? e.leftdE[s - 1] : e.rightdE[s - 1];
219 if (v == 0)
220 c.miss_long[s]++;
221 }
222
223 Bool_t has_fake = kFALSE, has_saturation = kFALSE, has_pileup = kFALSE;
224 EventBuilder::GetFlagSummary(e, has_fake, has_saturation, has_pileup);
225 Bool_t has_any_flag = has_fake || has_saturation || has_pileup;
226
227 if (is_complete) {
228 Bool_t reject = Constants::cfg.REJECT_FLAGGED_EVENTS && has_any_flag;
229 if (Constants::ActiveDedupStrategy() == kDISCARD && any_anode_multi)
230 reject = kTRUE;
231 if (!reject) {
232 for (Int_t k = 0; k < 16; k++) {
233 leftdE_branch[k] = UShort_t(e.leftdE[k]);
234 rightdE_branch[k] = UShort_t(e.rightdE[k]);
235 }
236 strip0_branch = UShort_t(e.strip0dE);
237 strip17_branch = UShort_t(e.strip17dE);
238 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
239 hits_branch[k] = UShort_t(e.hits[k]);
240 cathode_branch = Short_t(e.cathode);
241 grid_branch = Short_t(e.grid);
242 flags_or_branch = e.flags_or;
243 seed_ts_branch = e.ref_ts;
244 output_tree->Fill();
245
246 for (Int_t s = 0; s < 18; s++)
247 hSum.h_music->Fill(Double_t(s), Double_t(e.Total(s)));
248
249 for (Int_t s = 1; s <= 16; s++) {
250 const Bool_t lIsLong = (s % 2) != 0;
251 hSum.h2_long_vs_short[s - 1]->Fill(
252 Double_t(lIsLong ? e.rightdE[s - 1] : e.leftdE[s - 1]),
253 Double_t(lIsLong ? e.leftdE[s - 1] : e.rightdE[s - 1]));
254 }
255
256 if (hSum.h1_cathode && e.had_cathode)
257 hSum.h1_cathode->Fill(Double_t(e.cathode));
258
259 if (hSum.h1_strip17)
260 hSum.h1_strip17->Fill(Double_t(e.strip17dE));
261
262 if (hSum.h2_strip0_vs_grid)
263 hSum.h2_strip0_vs_grid->Fill(
264 Double_t(e.grid),
265 Double_t(SummaryGridPartnerIsStrip1() ? e.Total(1) : e.strip0dE));
266
267 if (hSum.h1_strip0)
268 hSum.h1_strip0->Fill(Double_t(e.strip0dE));
269
270 if (hSum.h1_grid)
271 hSum.h1_grid->Fill(Double_t(e.grid));
272
273 Int_t mult = 0;
274 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
275 mult += e.hits[k];
276 hSum.h_mult->Fill(Double_t(mult));
277 } else {
279 }
280 c.complete_events++;
281 if (has_fake)
283 if (has_saturation)
285 if (has_pileup)
287 } else {
289 if (has_fake)
291 if (has_saturation)
293 if (has_pileup)
295 }
296}
297
298// A hit a reference-channel event has not yet assigned, queued until the
299// next reference hit (or the stream end) decides its window.
301 Int_t slot;
302 UShort_t energy;
303 ULong64_t timestamp;
304 UInt_t flags;
305};
306
307// The events tree's branch variables. The tree holds their addresses, so
308// the instance must outlive the tree's Fills.
315 UShort_t leftdE[16];
316 UShort_t rightdE[16];
317 UShort_t strip0dE;
318 UShort_t strip17dE;
319 UShort_t hits_arr[36];
324 Short_t cathode;
325 Short_t grid;
326 UInt_t flags_or;
327 ULong64_t seed_ts;
328};
329
330// The build's configuration, resolved once from the dataset.
340
341// The event currently being built and the hits queued for it.
342struct OpenEvent {
346 ULong64_t ref_ts;
347 ULong64_t window_end;
348 UShort_t seed_energy;
349 Bool_t have;
350 std::vector<PendingHit> pending;
351};
352
353// What the build writes into: tree, branch variables, histograms, counters.
364
365// The run's summary counters: the reference hits and their first and last
366// stamps, the rejections, the cathode total, and the seed holdoff tallies.
380
381// A pre-trigger has no anodes pending: the strips fire after the real grid
382// trigger. Size alone cannot tell it from a first particle read low.
383static const Int_t kPreTriggerMaxPending = 2;
384
385// The events tree; the large baskets have auto-flush disabled so ZSTD
386// compresses in big chunks instead of many small basket flushes.
387static TTree *BookEventBranches(EventBranches &br) {
388 TTree *output_tree = new TTree("events", "MUSIC events");
389 output_tree->Branch("LeftdE", br.leftdE, "LeftdE[16]/s");
390 output_tree->Branch("RightdE", br.rightdE, "RightdE[16]/s");
391 output_tree->Branch("Strip0dE", &br.strip0dE, "Strip0dE/s");
392 output_tree->Branch("Strip17dE", &br.strip17dE, "Strip17dE/s");
393 output_tree->Branch("Hits", br.hits_arr, "Hits[36]/s");
394 output_tree->Branch("Cathode", &br.cathode, "Cathode/S");
395 output_tree->Branch("Grid", &br.grid, "Grid/S");
396 output_tree->Branch("FlagsOR", &br.flags_or, "FlagsOR/i");
397 output_tree->Branch("SeedTs", &br.seed_ts, "SeedTs/l");
398
399 output_tree->SetAutoFlush(0);
400 for (Int_t b = 0; b < output_tree->GetListOfBranches()->GetEntries(); b++) {
401 TBranch *branch =
402 static_cast<TBranch *>(output_tree->GetListOfBranches()->At(b));
403 branch->SetBasketSize(128 * 1024 * 1024);
404 }
405 return output_tree;
406}
407
408// The summary histograms, ranged for the active dataset.
409static void ConfigureSummaryHistograms(SummaryHistograms &hSum) {
411 cfg.unit_label = "ADC";
412 cfg.strip_e_min = Constants::ActiveStripEMinAdc();
413 cfg.strip_e_max = Constants::ActiveStripEMaxAdc();
414 cfg.odd_even_split = kTRUE;
415 cfg.left_odd_max = Constants::ActiveLeftOddMaxAdc();
416 cfg.left_even_max = Constants::ActiveLeftEvenMaxAdc();
417 cfg.right_odd_max = Constants::ActiveRightOddMaxAdc();
418 cfg.right_even_max = Constants::ActiveRightEvenMaxAdc();
419 cfg.cathode_max = Constants::ActiveCathodeMaxAdc();
420 cfg.grid_max = Constants::ActiveGridMaxAdc();
421 cfg.strip0_max = Constants::ActiveStrip0MaxAdc();
422 cfg.music_energy_bins = 2000;
423 CreateSummaryHistograms(hSum, cfg);
424}
425
426// The summary histograms' deletion, in the order the allocation made.
427static void DeleteSummaryHistograms(SummaryHistograms &hSum) {
428 for (Int_t s = 1; s <= 16; s++)
429 delete hSum.h2_long_vs_short[s - 1];
430 delete hSum.h_music;
431 delete hSum.h_mult;
432 delete hSum.h1_cathode;
433 delete hSum.h1_strip17;
434 delete hSum.h2_strip0_vs_grid;
435 delete hSum.h1_strip0;
436 delete hSum.h1_grid;
437}
438
439// The sample-trace stride: a rough event estimate, each complete event
440// having ~20 hits (18 strips + cathode + grid).
441static Long64_t SampleTraceStride(Long64_t n_hits) {
442 Long64_t sample_stride = 0;
443 if (Constants::cfg.SAVE_SAMPLE_TRACES > 0) {
444 Long64_t est_events = n_hits / 20;
445 sample_stride = est_events / Long64_t(Constants::cfg.SAVE_SAMPLE_TRACES);
446 if (sample_stride < 1)
447 sample_stride = 1;
448 }
449 return sample_stride;
450}
451
452// The reference channel's slot, the active channel map scanned for its
453// name; -1 when not found.
454static Int_t ResolveReferenceSlot(const EventBuilder::SlotMap &slot_map) {
455 Int_t ref_slot = -1;
456 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
458 it != Constants::ActiveChannelMap().end(); ++it) {
459 if (it->second == Constants::ActiveReferenceChannel()) {
460 Int_t board = it->first.first;
461 Int_t channel = it->first.second;
462 if (board >= 0 && board < Constants::ActiveNBoards() && channel >= 0 &&
463 channel < Constants::ActiveNChannels()) {
464 ref_slot = slot_map[board * Constants::ActiveNChannels() + channel];
465 break;
466 }
467 }
468 }
469 return ref_slot;
470}
471
472// The event counters, zeroed.
473static void ZeroEventCounters(EventCounters &cnt) {
474 cnt.total_events = 0;
475 cnt.complete_events = 0;
476 cnt.complete_with_fake = 0;
478 cnt.complete_with_pileup = 0;
479 cnt.complete_rejected = 0;
480 cnt.incomplete_events = 0;
481 cnt.incomplete_with_fake = 0;
484 cnt.events_with_cathode = 0;
489 for (Int_t s = 0; s < 17; s++)
490 cnt.miss_long[s] = 0;
491 cnt.miss_strip0 = 0;
492 cnt.miss_strip17 = 0;
493}
494
495// A new event, seeded by `h` on `slot`: the state reset, the reference
496// stamp, and in reference mode the window end and the seed's energy.
497static inline void OpenNewEvent(OpenEvent &oe, const RawHit &h, Int_t slot,
498 const BuildConfig &cfg, Bool_t is_ref) {
501 oe.ref_ts = h.timestamp;
502 oe.event.ref_ts = oe.ref_ts;
503 if (is_ref) {
504 oe.window_end = oe.ref_ts + cfg.window_ps;
505 oe.seed_energy = h.energy;
506 }
507 oe.have = kTRUE;
508 EventBuilder::AssignHit(oe.event, oe.pc, oe.ref_ts, slot, h.energy,
509 h.timestamp, h.flags, cfg.dedup_strat);
510}
511
512// The queued hits assigned to the open event, the out-of-window ones
513// counted.
514static inline void FlushPending(OpenEvent &oe, const BuildConfig &cfg,
515 Long64_t &dropped_outside_window) {
516 for (Int_t p = 0; p < Int_t(oe.pending.size()); p++) {
517 if (oe.pending[p].timestamp <= oe.window_end) {
518 EventBuilder::AssignHit(oe.event, oe.pc, oe.ref_ts, oe.pending[p].slot,
519 oe.pending[p].energy, oe.pending[p].timestamp,
520 oe.pending[p].flags, cfg.dedup_strat);
521 } else {
522 dropped_outside_window++;
523 }
524 }
525 oe.pending.clear();
526}
527
528// The open event finalized into the sink; the event index advances.
529static inline void CloseEvent(OpenEvent &oe, EventSink &sink) {
530 FinalizeEvent(oe.event, sink.tree, sink.br->leftdE, sink.br->rightdE,
531 sink.br->strip0dE, sink.br->strip17dE, sink.br->hits_arr,
532 sink.br->cathode, sink.br->grid, sink.br->flags_or,
533 sink.br->seed_ts, *sink.hSum, *sink.cnt, sink.event_idx,
534 sink.sample_traces, sink.sample_stride, &sink.n_sampled);
535 sink.event_idx++;
536}
537
538// The seed holdoff: the pulse behind a pre-trigger re-seeds the open event
539// rather than splitting the anodes. kTRUE when the hit was consumed.
540static inline Bool_t MergePreTriggerSeed(const RawHit &h, Int_t ref_slot,
541 OpenEvent &oe, const BuildConfig &cfg,
542 BuildTallies &t) {
543 if (oe.have && h.timestamp - oe.ref_ts <= cfg.holdoff_ps) {
544 const Bool_t empty = Int_t(oe.pending.size()) <= kPreTriggerMaxPending;
545 if (empty)
547 else
549 if (empty &&
550 Double_t(oe.seed_energy) < cfg.holdoff_ratio * Double_t(h.energy)) {
552 if (cfg.holdoff_on) {
553 t.seeds_merged++;
554 oe.ref_ts = h.timestamp;
555 oe.event.ref_ts = oe.ref_ts;
556 oe.window_end = oe.ref_ts + cfg.window_ps;
557 // The larger energy is the real pulse; the stamp is the later hit.
558 const UShort_t keep_e = std::max(oe.seed_energy, h.energy);
559 oe.seed_energy = keep_e;
560 oe.event.hits[ref_slot] = 0;
561 EventBuilder::AssignHit(oe.event, oe.pc, oe.ref_ts, ref_slot, keep_e,
562 h.timestamp, h.flags, cfg.dedup_strat);
563 return kTRUE;
564 }
565 } else {
567 }
568 }
569 return kFALSE;
570}
571
572// Reference-channel mode: a reference hit seeds a new event, the others
573// queue until the next one. kTRUE when the hit was skipped.
574static inline Bool_t ReferenceModeHit(const RawHit &h, Int_t slot,
575 const BuildConfig &cfg, OpenEvent &oe,
576 EventSink &sink, BuildTallies &t) {
577 if (slot == cfg.ref_slot) {
578 // Grid ADC window filter: skip reference hits outside the accepted
579 // range so they don't seed an event.
580 if (Double_t(h.energy) < Constants::ActiveReferenceChannelMinAdc() ||
581 Double_t(h.energy) > Constants::ActiveReferenceChannelMaxAdc()) {
582 return kTRUE;
583 }
584 if (t.n_ref == 0)
585 t.first_ref_ts = h.timestamp;
586 t.last_ref_ts = h.timestamp;
587 t.n_ref++;
588
589 if (MergePreTriggerSeed(h, cfg.ref_slot, oe, cfg, t))
590 return kTRUE;
591
592 if (oe.have) {
593 // Flush pending hits that fall within the current window.
594 FlushPending(oe, cfg, t.dropped_outside_window);
595
596 // Finalize the completed event.
597 CloseEvent(oe, sink);
598 }
599
600 // This hit seeds the new event, keeping its energy; the outgoing event
601 // gets a different ion's amplitude, uncorrelated with its anodes.
602 OpenNewEvent(oe, h, cfg.ref_slot, cfg, kTRUE);
603 } else {
604 if (oe.have && h.timestamp <= oe.window_end) {
605 PendingHit ph;
606 ph.slot = slot;
607 ph.energy = h.energy;
608 ph.timestamp = h.timestamp;
609 ph.flags = h.flags;
610 oe.pending.push_back(ph);
611 } else if (oe.have) {
613 }
614 }
615 return kFALSE;
616}
617
618// Time-window mode (REFERENCE_CHANNEL == "NONE"): the first hit opens a
619// window; its passing closes the event and opens the next.
620static inline void WindowModeHit(const RawHit &h, Int_t slot,
621 const BuildConfig &cfg, OpenEvent &oe,
622 EventSink &sink) {
623 if (!oe.have) {
624 OpenNewEvent(oe, h, slot, cfg, kFALSE);
625 } else if (h.timestamp - oe.ref_ts <= cfg.window_ps) {
626 EventBuilder::AssignHit(oe.event, oe.pc, oe.ref_ts, slot, h.energy,
627 h.timestamp, h.flags, cfg.dedup_strat);
628 } else {
629 // Window exceeded — finalize and start new window.
630 CloseEvent(oe, sink);
631 OpenNewEvent(oe, h, slot, cfg, kFALSE);
632 }
633}
634
635// The one-line summary outside the per-file sample: what the build did.
636static void PrintBuildOneLiner(const TString &file_label,
637 const EventCounters &cnt, Int_t n_ref,
638 Double_t ref_rate_hz,
639 Long64_t dropped_outside_window) {
640 // One string, one write: workers share the stream and a line built
641 // piecewise interleaves with another worker's.
642 const TString line = Form(
643 "[events] %s: %lld events, %lld complete (%.1f%%), %lld %s hits at "
644 "%.0f Hz, %lld outside window, dedup dropped %lld anode",
645 file_label.Data(), Long64_t(cnt.total_events),
646 Long64_t(cnt.complete_events),
647 cnt.total_events > 0 ? 100.0 * cnt.complete_events / cnt.total_events
648 : 0.0,
649 Long64_t(n_ref), Constants::ActiveReferenceChannel().Data(), ref_rate_hz,
650 Long64_t(dropped_outside_window), Long64_t(cnt.dropped_anode_hits_total));
651 std::cout << line << std::endl;
652}
653
654// The full summary block, for the sample files.
655static void PrintBuildSummary(const BuildConfig &cfg, const BuildTallies &t,
656 const EventCounters &cnt, Double_t span_s,
657 Double_t ref_rate_hz, Double_t holdoff_us,
658 const TString &output_filepath) {
659 if (t.empty_channel_map_events != 0)
660 std::cout << "Observed " << t.empty_channel_map_events
661 << " hits with empty entry in channel map." << std::endl;
662
663 if (t.dropped_outside_window > 0)
664 std::cout << "Dropped " << t.dropped_outside_window
665 << " hits outside coincidence window." << std::endl;
666 if (cfg.ref_mode && t.n_ref > 0) {
667 // What the seed holdoff did, or would do.
668 const Double_t pct = 100.0 / Double_t(t.n_ref);
669 std::cout << "Seed holdoff " << (cfg.holdoff_on ? "ON" : "off") << " ("
670 << (cfg.holdoff_on ? holdoff_us : 10.0) << " us"
671 << (cfg.holdoff_on ? "" : " diagnostic") << ", guard ratio "
672 << cfg.holdoff_ratio << "x): close seed pairs "
674 << Form("%.2f", pct * Double_t(t.seeds_close_empty +
676 << "% of seeds): " << t.seeds_close_empty
677 << " with no anodes pending ("
678 << Form("%.2f", pct * Double_t(t.seeds_close_empty)) << "%), "
679 << t.seeds_close_anodes << " with anodes pending ("
680 << Form("%.2f", pct * Double_t(t.seeds_close_anodes))
681 << "%). Pre-triggers (no anodes, current seed under the guard): "
682 << t.seeds_pretrigger << " ("
683 << Form("%.2f", pct * Double_t(t.seeds_pretrigger)) << "%), "
684 << t.seeds_merged << " merged; " << t.seeds_close_pair
685 << " kept apart." << std::endl;
686 }
687
688 std::cout << Constants::ActiveReferenceChannel() << " hits total: " << t.n_ref
689 << std::endl;
691 << " trigger rate: " << ref_rate_hz << " Hz over " << span_s << " s"
692 << std::endl;
693 std::cout << "Cathode hits total: " << t.cathode_hits_total << " (cathode/"
695 << (t.n_ref > 0 ? Double_t(t.cathode_hits_total) / t.n_ref : 0.0)
696 << ")" << std::endl;
697 std::cout << "Total events: " << cnt.total_events << std::endl;
698 if (cnt.total_events > 0) {
699 std::cout << "Events with cathode hit: " << cnt.events_with_cathode << " ("
700 << (100.0 * cnt.events_with_cathode / cnt.total_events) << "%)"
701 << std::endl;
702 std::cout << "Events with multiple cathode hits: "
703 << cnt.events_with_multi_cathode << " ("
704 << (100.0 * cnt.events_with_multi_cathode / cnt.total_events)
705 << "% of all, "
706 << (cnt.events_with_cathode > 0
707 ? 100.0 * cnt.events_with_multi_cathode /
709 : 0.0)
710 << "% of cathode events)" << std::endl;
711 std::cout << "Events with multi-hit on any anode: "
712 << cnt.events_with_multi_anode_hit << " ("
713 << (100.0 * cnt.events_with_multi_anode_hit / cnt.total_events)
714 << "%)" << std::endl;
715 std::cout << "Dropped hits (dedup strategy): anode="
717 << ", cathode=" << cnt.dropped_cathode_hits_total << std::endl;
718 }
719 std::cout << "Complete events: " << cnt.complete_events << " ("
720 << (100.0 * cnt.complete_events / cnt.total_events) << "%)"
721 << std::endl;
722 std::cout << "Incomplete events: " << cnt.incomplete_events << " ("
723 << (100.0 * cnt.incomplete_events / cnt.total_events) << "%)"
724 << std::endl;
725 if (cnt.total_events > 0) {
726 std::cout << "Per-channel miss rate (% of all events where the "
727 "completeness-required channel was zero):"
728 << std::endl;
729 std::cout << " Strip0=" << (100.0 * cnt.miss_strip0 / cnt.total_events)
730 << "% Strip17=" << (100.0 * cnt.miss_strip17 / cnt.total_events)
731 << "%" << std::endl;
732 for (Int_t s = 1; s <= 16; s++) {
733 const Char_t *side = (s % 2 == 1) ? "L" : "R";
734 std::cout << " " << side << s << "="
735 << (100.0 * cnt.miss_long[s] / cnt.total_events) << "%";
736 if (s % 4 == 0)
737 std::cout << std::endl;
738 }
739 }
740 if (Constants::cfg.REJECT_FLAGGED_EVENTS) {
741 Int_t stored = cnt.complete_events - cnt.complete_rejected;
742 std::cout << "Stored events (REJECT_FLAGGED_EVENTS=true): " << stored
743 << " ("
744 << (cnt.complete_events > 0 ? 100.0 * stored / cnt.complete_events
745 : 0.0)
746 << "% of complete; " << cnt.complete_rejected << " rejected)"
747 << std::endl;
748 }
749 if (cnt.complete_events > 0) {
750 std::cout << "Complete events with rejection-quality flags:" << std::endl;
751 std::cout << " Fake events: " << cnt.complete_with_fake << " ("
752 << (100.0 * cnt.complete_with_fake / cnt.complete_events) << "%)"
753 << std::endl;
754 std::cout << " Saturated: " << cnt.complete_with_saturation << " ("
755 << (100.0 * cnt.complete_with_saturation / cnt.complete_events)
756 << "%)" << std::endl;
757 std::cout << " Pileup: " << cnt.complete_with_pileup << " ("
758 << (100.0 * cnt.complete_with_pileup / cnt.complete_events)
759 << "%)" << std::endl;
760 }
761 if (cnt.incomplete_events > 0) {
762 std::cout << "Incomplete events with rejection-quality flags:" << std::endl;
763 std::cout << " Fake events: " << cnt.incomplete_with_fake << " ("
764 << (100.0 * cnt.incomplete_with_fake / cnt.incomplete_events)
765 << "%)" << std::endl;
766 std::cout << " Saturated: " << cnt.incomplete_with_saturation << " ("
767 << (100.0 * cnt.incomplete_with_saturation /
769 << "%)" << std::endl;
770 std::cout << " Pileup: " << cnt.incomplete_with_pileup << " ("
771 << (100.0 * cnt.incomplete_with_pileup / cnt.incomplete_events)
772 << "%)" << std::endl;
773 }
774
775 std::cout << "Events saved to: " << output_filepath << std::endl;
776}
777
778Bool_t EventBuilder::BuildEventsFromSortedHits(const std::vector<RawHit> &hits,
779 const SlotMap &slot_map,
780 const TString &output_name,
781 const TString &file_label) {
782 TString output_filepath = output_name + ".root";
783 TFile *output_file = IO::OpenForWriting(output_filepath);
784 if (!output_file || output_file->IsZombie()) {
785 std::cerr << "Error opening output: " << output_filepath << std::endl;
786 return kFALSE;
787 }
788
789 EventBranches br;
790 TTree *output_tree = BookEventBranches(br);
791
793 ConfigureSummaryHistograms(hSum);
794
795 // Sample traces for overlay plot
796 std::vector<TGraph *> sample_traces;
797 Long64_t sample_stride = SampleTraceStride(Long64_t(hits.size()));
798 Int_t n_sampled = 0;
799
800 BuildConfig cfg;
801 // Determine operating mode: reference-channel or blind time-window.
802 cfg.ref_mode = Constants::ActiveReferenceChannel() != "NONE";
803 // Resolve reference channel name to slot ID by scanning active channel map.
804 cfg.ref_slot = cfg.ref_mode ? ResolveReferenceSlot(slot_map) : -1;
805 if (cfg.ref_mode && cfg.ref_slot < 0) {
806 std::cerr << "FATAL: reference channel '"
808 << "' not found in channel map. Cannot build events."
809 << std::endl;
810 output_file->Close();
811 delete output_file;
812 DeleteSummaryHistograms(hSum);
813 for (Int_t i = 0; i < Int_t(sample_traces.size()); i++)
814 delete sample_traces[i];
815 return kFALSE;
816 }
817 cfg.window_ps = ULong64_t(Constants::ActiveEventTimeWindowUs() * 1.0e6);
818 cfg.dedup_strat = Constants::ActiveDedupStrategy();
819 // Seed holdoff (SEED_HOLDOFF_US); off, the candidates are still counted in
820 // a 10 us window so the summary says what turning it on would do.
821 const Double_t holdoff_us = Constants::ActiveSeedHoldoffUs();
822 cfg.holdoff_ratio = Constants::ActiveSeedHoldoffMaxRatio();
823 cfg.holdoff_on = holdoff_us > 0.0;
824 cfg.holdoff_ps = ULong64_t((cfg.holdoff_on ? holdoff_us : 10.0) * 1.0e6);
825
826 Long64_t n_entries = Long64_t(hits.size());
827
828 OpenEvent oe;
829 oe.pc = &oe.per_channel;
830 oe.ref_ts = 0;
831 oe.window_end = 0;
832 oe.seed_energy = 0;
833 oe.have = kFALSE;
834 oe.pending.reserve(4096);
835
836 EventCounters cnt;
837 ZeroEventCounters(cnt);
838
839 BuildTallies t;
840 t.n_ref = 0;
841 t.first_ref_ts = 0;
842 t.last_ref_ts = 0;
844 t.cathode_hits_total = 0;
846 t.seeds_pretrigger = 0;
847 t.seeds_merged = 0;
848 t.seeds_close_pair = 0;
849 t.seeds_close_empty = 0;
850 t.seeds_close_anodes = 0;
851
852 EventSink sink;
853 sink.tree = output_tree;
854 sink.br = &br;
855 sink.hSum = &hSum;
856 sink.cnt = &cnt;
857 sink.sample_traces = &sample_traces;
858 sink.sample_stride = sample_stride;
859 sink.n_sampled = n_sampled;
860 sink.event_idx = 0;
861
863 std::cout << "[" << file_label << "] Streaming pass over " << n_entries
864 << " sorted hits (reference: "
866 << ", window: " << Constants::ActiveEventTimeWindowUs() << " us)"
867 << std::endl;
868
869 for (Long64_t i = 0; i < n_entries; i++) {
870 const RawHit &h = hits[i];
871
872 if (h.board >= Constants::ActiveNBoards() ||
873 h.channel >= Constants::ActiveNChannels()) {
875 continue;
876 }
877 Int_t slot = slot_map[h.board * Constants::ActiveNChannels() + h.channel];
878 if (slot < 0) {
880 continue;
881 }
882
883 if (slot == Constants::ARR_SLOT_CATHODE)
885
886 if (cfg.ref_mode) {
887 if (ReferenceModeHit(h, slot, cfg, oe, sink, t))
888 continue;
889 } else {
890 WindowModeHit(h, slot, cfg, oe, sink);
891 }
892
893#if MUSIC_HOT_PATH_LOGGING
894 if (i % 10000000 == 0)
895 std::cout << " Stream progress: " << i << "/" << n_entries << std::endl;
896#endif
897 }
898
899 // Finalize the last event.
900 if (oe.have) {
901 if (cfg.ref_mode)
902 FlushPending(oe, cfg, t.dropped_outside_window);
903 CloseEvent(oe, sink);
904 }
905
907 std::cout << "Found " << t.n_ref << " "
908 << Constants::ActiveReferenceChannel() << " hits." << std::endl;
909
910 if (cfg.ref_mode && t.n_ref == 0) {
911 std::cerr << "No " << Constants::ActiveReferenceChannel()
912 << " hits in file, skipping." << std::endl;
913 output_file->Close();
914 delete output_file;
915 DeleteSummaryHistograms(hSum);
916 return kFALSE;
917 }
918
919 Double_t span_s = (t.last_ref_ts > t.first_ref_ts)
920 ? Double_t(t.last_ref_ts - t.first_ref_ts) / 1e12
921 : 0.0;
922 Double_t ref_rate_hz = (span_s > 0.0) ? Double_t(t.n_ref) / span_s : 0.0;
923
924 output_file->cd();
925 TParameter<Double_t>("grid_rate_hz", ref_rate_hz).Write();
926 output_tree->Write("events", TObject::kOverwrite);
927 hSum.h_mult->Write("", TObject::kOverwrite);
928
929 {
930 std::lock_guard<std::mutex> lock(g_plot_mutex);
931 TString subdir = "events_summary/" + file_label;
932 SaveAndDeleteSummaryHistograms(hSum, output_file, subdir, "");
933
934 // Sample traces overlay
935 if (!sample_traces.empty()) {
936 EventsSummary::SaveSampleTraces(sample_traces, "sample_traces", subdir,
938 "Energy [ADC]");
939 }
940 }
941
942 output_file->Close();
943 delete output_file;
944
945 // Outside the per-file sample one line says what the build did; the full
946 // block below is for the sample files.
948 PrintBuildOneLiner(file_label, cnt, t.n_ref, ref_rate_hz,
950 return kTRUE;
951 }
952 PrintBuildSummary(cfg, t, cnt, span_s, ref_rate_hz, holdoff_us,
953 output_filepath);
954 return kTRUE;
955}
DedupStrategy
How to resolve more than one hit on the same channel within an event.
@ kDISCARD
Discard the entire event if any anode channel has a repeated hit.
@ kLATEST_TIMESTAMP
Keep the last hit to arrive.
@ kLARGEST_ENERGY
Keep the highest-energy hit.
@ kSMALLEST_ENERGY
Keep the lowest-energy hit.
void FinalizeEvent(EventState &e, TTree *output_tree, UShort_t *leftdE_branch, UShort_t *rightdE_branch, UShort_t &strip0_branch, UShort_t &strip17_branch, UShort_t *hits_branch, Short_t &cathode_branch, Short_t &grid_branch, UInt_t &flags_or_branch, ULong64_t &seed_ts_branch, SummaryHistograms &hSum, EventCounters &c, Long64_t event_idx=-1, std::vector< TGraph * > *sample_traces=nullptr, Long64_t sample_stride=0, Int_t *n_sampled=nullptr)
Bool_t SummaryGridPartnerIsStrip1()
Whether the grid's partner in the summary is strip 1 rather than strip 0: when strip 0 is ignored or ...
void CreateSummaryHistograms(SummaryHistograms &h, const SummaryHistConfig &cfg)
Allocate every histogram in the set.
void SaveAndDeleteSummaryHistograms(SummaryHistograms &h, TFile *out_file, const TString &subdir, const TString &plot_suffix)
Write the set to file, save the plots, then free every histogram.
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
Definition FileSet.cpp:3
static Bool_t CheckEventComplete(const EventState &e)
Whether an event has everything required to be written out: the long end of every split strip,...
static SlotMap BuildSlotMap()
Build the board/channel to slot lookup for this dataset.
static void ResetEventState(EventState &e)
Clear an event back to its empty state, ready to be seeded.
static Bool_t ShouldKeepHit(ULong64_t cand_ts, ULong64_t prev_ts, UShort_t cand_energy, UShort_t prev_energy, ULong64_t ref_ts, DedupStrategy strategy)
Decide which of two hits on the same slot survives.
static void GetFlagSummary(const EventState &e, Bool_t &has_fake, Bool_t &has_saturation, Bool_t &has_pileup)
Unpack the OR-ed status flags into the conditions that matter.
static void ResetPerChannelData(PerChannelData &p)
Clear the per-slot detail alongside a reset event.
static Bool_t BuildEventsFromSortedHits(const std::vector< RawHit > &hits, const SlotMap &slot_map, const TString &output_name, const TString &file_label)
Build every event in a subfile and write them to a ROOT tree.
std::vector< Int_t > SlotMap
Board/channel to array slot lookup, indexed as BuildSlotMap() defines.
static void AssignHit(EventState &e, PerChannelData *pc, ULong64_t ref_ts, Int_t slot, UShort_t energy, ULong64_t timestamp, UInt_t flags, DedupStrategy strategy)
Place one hit into an event, resolving any collision.
static void SaveSampleTraces(const std::vector< TGraph * > &traces, const TString &save_name, const TString &subdir, Double_t y_min, Double_t y_max, const char *y_title)
Draw a set of traces overlaid on one frame and save it.
static TGraph * BuildTraceFromTotals(const Double_t *total)
Build a trace graph from one event's per-strip totals.
const Int_t ARR_SLOT_CATHODE
Cathode signal.
DedupStrategy ActiveDedupStrategy()
How repeated hits on one slot are resolved.
Double_t ActiveReferenceChannelMaxAdc()
Upper energy gate on the seed channel, in ADC.
Double_t ActiveSeedHoldoffMaxRatio()
The size, as a fraction of the new hit, under which the current seed counts as a pre-trigger.
const TString & ActiveReferenceChannel()
Channel whose hits seed events.
Double_t ActiveLeftEvenMaxAdc()
Left-side ceiling for even strips, in ADC.
Double_t ActiveStripEMaxAdc()
Upper bound of the per-strip energy range, in ADC.
const Int_t ARR_SLOT_GRID
Frisch grid signal.
Double_t ActiveEventTimeWindowUs()
Coincidence window for event building, in microseconds.
Double_t ActiveStripEMinAdc()
Lower bound of the per-strip energy range, in ADC.
const Int_t N_ARR_SLOTS
Length of the per-event channel arrays.
const Int_t ARR_SLOT_STRIP_17
Last anode strip. Strips run contiguously from ARR_SLOT_STRIP_0 to here.
Double_t ActiveRightEvenMaxAdc()
Right-side ceiling for even strips, in ADC.
const std::map< std::pair< Int_t, Int_t >, TString > & ActiveChannelMap()
The channel map in force.
Double_t ActiveLeftOddMaxAdc()
Left-side ceiling for odd strips, in ADC.
Double_t ActiveStrip0MaxAdc()
Strip 0 full scale, in ADC.
Double_t ActiveRightOddMaxAdc()
Right-side ceiling for odd strips, in ADC.
Double_t ActiveCathodeMaxAdc()
Cathode full scale, in ADC.
Double_t ActiveReferenceChannelMinAdc()
Lower energy gate on the seed channel, in ADC.
Int_t ActiveNBoards()
Boards in the active epoch's setup.
const Int_t ARR_SLOT_STRIP_0
First anode strip, occupying the low end of the array.
Double_t ActiveGridMaxAdc()
Grid full scale, in ADC.
Bool_t FileInSample()
Whether the file this thread is processing is in the per-file sample: every file with SAVE_FULL_PLOTS...
const DatasetConfig & cfg
The active dataset's configuration, flat block.
Int_t ActiveNChannels()
Channels per board.
Double_t ActiveSeedHoldoffUs()
Seed holdoff in microseconds; 0 = off. See SEED_HOLDOFF_US.
ULong64_t holdoff_ps
DedupStrategy dedup_strat
ULong64_t window_ps
Double_t holdoff_ratio
ULong64_t last_ref_ts
Long64_t seeds_merged
Long64_t seeds_close_anodes
Long64_t seeds_close_pair
Long64_t seeds_pretrigger
Long64_t cathode_hits_total
Long64_t dropped_outside_window
Long64_t seeds_close_empty
Int_t empty_channel_map_events
ULong64_t first_ref_ts
UShort_t rightdE[16]
UShort_t hits_arr[36]
ULong64_t seed_ts
UShort_t leftdE[16]
ADC energies are 14-bit unsigned at the source; store them as UShort_t, not Int_t.
Short_t cathode
14-bit ADC (<=16383), so Short_t holds every value with room to spare while preserving Cathode's -1 "...
Long64_t dropped_cathode_hits_total
Int_t incomplete_with_pileup
Long64_t events_with_multi_cathode
Long64_t miss_long[17]
Per-required-channel miss counts over all events: how often each completeness-required long end or un...
Int_t complete_with_pileup
Long64_t events_with_multi_anode_hit
Int_t complete_with_saturation
Int_t incomplete_with_saturation
Int_t incomplete_with_fake
Long64_t dropped_anode_hits_total
Long64_t miss_strip0
Long64_t events_with_cathode
Long64_t miss_strip17
EventBranches * br
std::vector< TGraph * > * sample_traces
SummaryHistograms * hSum
EventCounters * cnt
Long64_t event_idx
Long64_t sample_stride
One built event: the per-slot energies and the flags seen while building it.
Int_t leftdE[16]
Left end of strips 1-16, in ADC, index strip - 1.
UInt_t flags_or
Bitwise OR of the CoMPASS status flags of every hit in the event, so a single test covers pileup or s...
Int_t hits[Constants::N_ARR_SLOTS]
Hit count per slot; see SlotLayout.hpp.
Int_t strip17dE
Strip 17, unsegmented, in ADC.
Bool_t had_cathode
Whether a cathode hit was seen at all.
ULong64_t ref_ts
Timestamp of the reference grid hit that seeded this event.
Int_t cathode
Cathode energy, in ADC.
Int_t strip0dE
Strip 0, unsegmented, in ADC.
Int_t grid
Frisch grid energy, in ADC.
Int_t Total(Int_t strip) const
A strip's deposit: the sum of its ends, or the unsegmented value.
Int_t rightdE[16]
Right end of strips 1-16, in ADC, index strip - 1.
ULong64_t window_end
PerChannelData * pc
PerChannelData per_channel
std::vector< PendingHit > pending
UShort_t seed_energy
EventState event
ULong64_t ref_ts
ULong64_t timestamp
UShort_t energy
Raw per-slot detail for one event, kept alongside EventState.
ULong64_t timestamps[Constants::N_ARR_SLOTS]
Hit timestamp per slot.
UInt_t flags[Constants::N_ARR_SLOTS]
Status flags per slot.
UShort_t energies[Constants::N_ARR_SLOTS]
Hit energy per slot, ADC.
Axis ranges and labels for building a SummaryHistograms set.
The standard diagnostic histogram set for a subfile.
TH2F * h_music
Energy against strip index: the MUSIC plot.
TH1F * h1_strip0
Strip 0 energy.
TH1F * h1_cathode
Cathode energy.
TH2F * h2_strip0_vs_grid
Strip 0 against grid energy; strip 1 against the grid when strip 0 is not required of an event (Summa...
TH1F * h_mult
Hit multiplicity per event.
TH1F * h1_strip17
Strip 17 energy.
TH2F * h2_long_vs_short[16]
Long end against short end of the segmented strips 1-16, index strip - 1.
TH1F * h1_grid
Grid energy.