MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
Timing.cpp
Go to the documentation of this file.
1#include "Timing.hpp"
2#include <algorithm>
3#include <cmath>
4
6 Double_t best_shift = 0.0;
7 Double_t best_inv_nsd2 = 0.0;
8 Int_t best_npts = 0;
9 Bool_t found = kFALSE;
10 std::vector<Double_t> shifts;
11 std::vector<Double_t> inv_nsd2_values;
12};
13
14ShiftScanResult ScanShiftRange(const std::vector<Double_t> &ref_x,
15 const std::vector<Double_t> &ref_y,
16 const std::vector<Double_t> &gr_x,
17 const std::vector<Double_t> &gr_y,
18 Double_t shift_min_s, Double_t shift_max_s,
19 Double_t shift_step_s, Double_t thresh_dt_us,
20 const TString &label) {
21
22 ShiftScanResult result;
23
24 if (shift_step_s <= 0) {
25 std::cerr << label << ": shift_step_s <= 0 (" << shift_step_s
26 << "), skipping scan." << std::endl;
27 return result;
28 }
29
30 if (shift_min_s > shift_max_s) {
31 std::cerr << label << ": shift_min_s > shift_max_s (" << shift_min_s
32 << " > " << shift_max_s << "), skipping scan." << std::endl;
33 return result;
34 }
35
36 Int_t candidate_count = static_cast<Int_t>(std::floor(
37 (shift_max_s - shift_min_s) / shift_step_s)) +
38 1;
39
40 if (candidate_count > Constants::cfg.TIMING_SHIFT_MAX_SCAN_CANDIDATES) {
41 std::cerr << label << ": candidate count " << candidate_count
42 << " exceeds limit "
43 << Constants::cfg.TIMING_SHIFT_MAX_SCAN_CANDIDATES
44 << ", skipping scan." << std::endl;
45 return result;
46 }
47
48 std::cout << " " << label << " range: [" << shift_min_s << ", "
49 << shift_max_s << "] s ([" << shift_min_s * 1e6 << ", "
50 << shift_max_s * 1e6 << "] us)" << std::endl;
51 std::cout << " " << label << " step: " << shift_step_s << " s ("
52 << shift_step_s * 1e6 << " us)" << std::endl;
53 std::cout << " " << label << " candidates: " << candidate_count << std::endl;
54
55 result.shifts.reserve(candidate_count);
56 result.inv_nsd2_values.reserve(candidate_count);
57
58 for (Int_t i = 0; i < candidate_count; i++) {
59 Double_t shift = shift_min_s + i * shift_step_s;
60 Int_t npts = 0;
61 Double_t nsd2 = 0;
62
63 Timing::ComputeNSD2(ref_x, ref_y, gr_x, gr_y, shift, thresh_dt_us, npts,
64 nsd2);
65
66 if (npts > Constants::cfg.TIMING_SHIFT_MIN_NPTS) {
67 Double_t inv_nsd2 = 1.0 / nsd2;
68 result.shifts.push_back(shift);
69 result.inv_nsd2_values.push_back(inv_nsd2);
70
71 if (!result.found || inv_nsd2 > result.best_inv_nsd2) {
72 result.best_shift = shift;
73 result.best_inv_nsd2 = inv_nsd2;
74 result.best_npts = npts;
75 result.found = kTRUE;
76 }
77 }
78
79#if MUSIC_HOT_PATH_LOGGING
80 if (i % 50000 == 0 && i > 0)
81 std::cout << " Progress: " << i << "/" << candidate_count << std::endl;
82#endif
83 }
84
85 return result;
86}
87
88void Timing::ComputeNSD2(const std::vector<Double_t> &ref_x,
89 const std::vector<Double_t> &ref_y,
90 const std::vector<Double_t> &gr_x,
91 const std::vector<Double_t> &gr_y, Double_t shift,
92 Double_t thresh_dt_us, Int_t &npts, Double_t &nsd2) {
93 nsd2 = 0;
94 npts = 0;
95
96 Double_t tmin_gr = gr_x.empty() ? 0 : gr_x.front() - shift;
97 Double_t tmax_gr = gr_x.empty() ? 0 : gr_x.back() - shift;
98 Double_t tmin_ref = ref_x.empty() ? 0 : ref_x.front();
99 Double_t tmax_ref = ref_x.empty() ? 0 : ref_x.back();
100
101 Double_t tmin = TMath::Max(tmin_ref, tmin_gr);
102 Double_t tmax = TMath::Min(tmax_ref, tmax_gr);
103
104 Int_t gr_idx = 0;
105
106 for (Int_t p = 0; p < Int_t(ref_x.size()); p++) {
107 Double_t tref = ref_x[p];
108 Double_t dtref = ref_y[p];
109
110 if (tref < tmin || tref > tmax || dtref <= thresh_dt_us)
111 continue;
112
113 Double_t tref_shifted = tref + shift;
114
115 while (gr_idx < Int_t(gr_x.size()) - 1 && gr_x[gr_idx + 1] < tref_shifted)
116 gr_idx++;
117
118 if (gr_idx >= Int_t(gr_x.size()) - 1)
119 continue;
120
121 Double_t x0 = gr_x[gr_idx];
122 Double_t x1 = gr_x[gr_idx + 1];
123 Double_t y0 = gr_y[gr_idx];
124 Double_t y1 = gr_y[gr_idx + 1];
125
126 Double_t dt = y0 + (y1 - y0) * (tref_shifted - x0) / (x1 - x0);
127 nsd2 += pow(dt - dtref, 2);
128 npts++;
129 }
130
131 if (nsd2 > 0 && npts > 0) {
132 nsd2 = sqrt(nsd2) / npts;
133 } else {
134 nsd2 = 1e12;
135 }
136}
137
138Bool_t Timing::IsLongChannel(const TString &name) {
139 if (name == "Strip0" || name == "Strip17")
140 return kTRUE;
141 if (name.Length() < 2)
142 return kFALSE;
143 Char_t side = name[0];
144 if (side != 'L' && side != 'R')
145 return kFALSE;
146 TString num = name;
147 num.Remove(0, 1);
148 if (!num.IsDigit())
149 return kFALSE;
150 Int_t n = num.Atoi();
151 if (side == 'L' && (n % 2 == 1))
152 return kTRUE;
153 if (side == 'R' && (n % 2 == 0))
154 return kTRUE;
155 return kFALSE;
156}
157
158Bool_t Timing::LongChanOrder(const LongChan &a, const LongChan &b) {
159 if (a.board != b.board)
160 return a.board < b.board;
161 return a.channel < b.channel;
162}
163
164std::vector<LongChan> Timing::BuildLongChannelList() {
165 std::vector<LongChan> list;
166 std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it;
167 for (it = Constants::ActiveChannelMap().begin();
168 it != Constants::ActiveChannelMap().end(); ++it) {
169 if (!IsLongChannel(it->second))
170 continue;
171 LongChan lc;
172 lc.board = static_cast<UShort_t>(it->first.first);
173 lc.channel = static_cast<UShort_t>(it->first.second);
174 lc.name = it->second;
175 list.push_back(lc);
176 }
177 std::sort(list.begin(), list.end(), LongChanOrder);
178 return list;
179}
180
181void Timing::PlotExtremeEvents2D(TH2F *h_before, TH2F *h_after,
182 TH2F *h_before_zoom, TH2F *h_after_zoom,
183 const TString &file_label) {
184 std::lock_guard<std::mutex> lock(g_plot_mutex);
185 TString subdir = "timing/" + file_label;
186
187 TCanvas *c_before = PlottingUtils::GetConfiguredCanvas(kFALSE);
188 PlottingUtils::ConfigureAndDraw2DHistogram(h_before, c_before);
189 c_before->SetLogz(kFALSE);
191 PlottingUtils::SaveFigure(c_before, "extreme_events_before", subdir,
192 PlotSaveOptions::kLINEAR);
193 delete c_before;
194
195 TCanvas *c_after = PlottingUtils::GetConfiguredCanvas(kFALSE);
196 PlottingUtils::ConfigureAndDraw2DHistogram(h_after, c_after);
197 c_after->SetLogz(kFALSE);
199 PlottingUtils::SaveFigure(c_after, "extreme_events_after", subdir,
200 PlotSaveOptions::kLINEAR);
201 delete c_after;
202
203 Int_t n = gStyle->GetNumberOfColors();
204 std::vector<Int_t> old_palette;
205 old_palette.reserve(n);
206
207 for (Int_t i = 0; i < n; i++) {
208 old_palette.push_back(gStyle->GetColorPalette(i));
209 }
210
211 Int_t palette[2];
212 palette[0] = TColor::GetColor("#FFFFFF"); // background / zero
213 palette[1] = TColor::GetColor("#D62728"); // occupied bin / event
214
215 if (h_before_zoom) {
216 TCanvas *c_before_zoom = PlottingUtils::GetConfiguredCanvas(kFALSE);
217 PlottingUtils::Configure2DHistogram(h_before_zoom, c_before_zoom);
218
219 gStyle->SetPalette(2, palette);
220 h_before_zoom->SetMinimum(0);
221 h_before_zoom->SetMaximum(1);
222 c_before_zoom->SetLogz(kFALSE);
223 h_before_zoom->Draw();
224 c_before_zoom->SetRightMargin(0.07);
226 PlottingUtils::SaveFigure(c_before_zoom, "extreme_events_before_zoom_us",
227 subdir, PlotSaveOptions::kLINEAR);
228 delete c_before_zoom;
229 }
230
231 if (h_after_zoom) {
232 TCanvas *c_after_zoom = PlottingUtils::GetConfiguredCanvas(kFALSE);
233 PlottingUtils::Configure2DHistogram(h_after_zoom, c_after_zoom);
234
235 gStyle->SetPalette(2, palette);
236 h_after_zoom->SetMinimum(0);
237 h_after_zoom->SetMaximum(1);
238 c_after_zoom->SetLogz(kFALSE);
239 h_after_zoom->Draw();
240 c_after_zoom->SetRightMargin(0.07);
242 PlottingUtils::SaveFigure(c_after_zoom, "extreme_events_after_zoom_us",
243 subdir, PlotSaveOptions::kLINEAR);
244 delete c_after_zoom;
245 }
246 if (!old_palette.empty()) {
247 gStyle->SetPalette(static_cast<Int_t>(old_palette.size()),
248 old_palette.data());
249 }
250}
251
252void Timing::PlotCostLandscape(const std::vector<Double_t> &shifts,
253 const std::vector<Double_t> &inv_nsd2_values,
254 Double_t best_shift, UShort_t ref_board,
255 UShort_t board, const TString &file_label,
256 const TString &tag) {
257 std::lock_guard<std::mutex> lock(g_plot_mutex);
258
259 std::vector<std::pair<Double_t, Double_t>> points;
260 points.reserve(shifts.size());
261 for (Int_t i = 0; i < Int_t(shifts.size()); i++) {
262 points.push_back(std::make_pair(shifts[i], inv_nsd2_values[i]));
263 }
264 std::sort(points.begin(), points.end());
265
266 TGraph *g = new TGraph();
267 Double_t y_max = 0;
268 for (Int_t i = 0; i < Int_t(points.size()); i++) {
269 g->SetPoint(g->GetN(), points[i].first, points[i].second);
270 if (points[i].second > y_max)
271 y_max = points[i].second;
272 }
273
274 TCanvas *canvas = PlottingUtils::GetConfiguredCanvas(kFALSE);
275 PlottingUtils::ConfigureGraph(
276 g, kBlue + 1,
277 Form("Cost landscape Board %d-%d;Candidate shift [s];1/NSD^{2}",
278 ref_board, board));
279 g->SetLineWidth(PlottingUtils::GetLineWidth());
280 g->Draw("AL");
281
282 TLine *best = new TLine(best_shift, 0, best_shift, 1.05 * y_max);
283 best->SetLineColor(kRed + 1);
284 best->SetLineStyle(2);
285 best->SetLineWidth(PlottingUtils::GetLineWidth());
286 best->Draw();
287
288 PlottingUtils::AddText(Form("best shift = %.6f s", best_shift), 0.85, 0.85);
289
291 PlottingUtils::SaveFigure(
292 canvas, Form("cost_landscape_board_%d%s", board, tag.Data()),
293 "timing/" + file_label, PlotSaveOptions::kLINEAR);
294 delete canvas;
295}
296
297Double_t Timing::FindShiftBeam(TGraph *ref, TGraph *gr, Double_t overlap_tmin_s,
298 Double_t overlap_tmax_s, Double_t thresh_dt_us,
299 UShort_t ref_board, UShort_t board,
300 const TString &file_label) {
301
302 std::vector<Double_t> ref_x(ref->GetN()), ref_y(ref->GetN());
303 std::vector<Double_t> gr_x(gr->GetN()), gr_y(gr->GetN());
304
305 std::cout << "Caching graph data..." << std::endl;
306 for (Int_t i = 0; i < ref->GetN(); i++) {
307 ref->GetPoint(i, ref_x[i], ref_y[i]);
308 }
309 for (Int_t i = 0; i < gr->GetN(); i++) {
310 gr->GetPoint(i, gr_x[i], gr_y[i]);
311 }
312
313 const Double_t coarse_step_s =
314 Constants::cfg.TIMING_SHIFT_COARSE_STEP_US * 1e-6;
315 const Double_t fine_step_s = Constants::cfg.TIMING_SHIFT_FINE_STEP_US * 1e-6;
316 const Double_t fine_half_width_s =
317 Constants::cfg.TIMING_SHIFT_FINE_HALF_WIDTH_US * 1e-6;
318
319 const Double_t scan_min_s = -Constants::cfg.TIMING_MAX_ABS_SHIFT_S;
320 const Double_t scan_max_s = Constants::cfg.TIMING_MAX_ABS_SHIFT_S;
321
322 std::cout << "Looking for timeshift between " << overlap_tmin_s << " - "
323 << overlap_tmax_s << " sec" << std::endl;
324 std::cout << "Max absolute shift range: [" << scan_min_s << ", " << scan_max_s
325 << "] s ([" << scan_min_s * 1e6 << ", " << scan_max_s * 1e6
326 << "] us)" << std::endl;
327 std::cout << "Coarse step: " << coarse_step_s << " s ("
328 << Constants::cfg.TIMING_SHIFT_COARSE_STEP_US << " us)"
329 << std::endl;
330 std::cout << "Fine step: " << fine_step_s << " s ("
331 << Constants::cfg.TIMING_SHIFT_FINE_STEP_US << " us)" << std::endl;
332 std::cout << "Fine half-width: " << fine_half_width_s << " s ("
333 << Constants::cfg.TIMING_SHIFT_FINE_HALF_WIDTH_US << " us)"
334 << std::endl;
335
336 ShiftScanResult coarse =
337 ScanShiftRange(ref_x, ref_y, gr_x, gr_y, scan_min_s, scan_max_s,
338 coarse_step_s, thresh_dt_us, "Coarse");
339
340 Double_t final_shift = 0.0;
341 Double_t final_inv_nsd2 = 0.0;
342
343 ShiftScanResult fine;
344
345 if (coarse.found) {
346 std::cout << "Coarse best: shift = " << coarse.best_shift << " s ("
347 << coarse.best_shift * 1e6
348 << " us), 1/NSD2 = " << coarse.best_inv_nsd2
349 << ", npts = " << coarse.best_npts << std::endl;
350
351 Double_t fine_min_s = coarse.best_shift - fine_half_width_s;
352 Double_t fine_max_s = coarse.best_shift + fine_half_width_s;
353
354 if (fine_min_s < scan_min_s)
355 fine_min_s = scan_min_s;
356 if (fine_max_s > scan_max_s)
357 fine_max_s = scan_max_s;
358
359 fine = ScanShiftRange(ref_x, ref_y, gr_x, gr_y, fine_min_s, fine_max_s,
360 fine_step_s, thresh_dt_us, "Fine");
361
362 if (fine.found) {
363 final_shift = fine.best_shift;
364 final_inv_nsd2 = fine.best_inv_nsd2;
365 std::cout << "Fine best: shift = " << fine.best_shift << " s ("
366 << fine.best_shift * 1e6
367 << " us), 1/NSD2 = " << fine.best_inv_nsd2
368 << ", npts = " << fine.best_npts << std::endl;
369 } else {
370 final_shift = coarse.best_shift;
371 final_inv_nsd2 = coarse.best_inv_nsd2;
372 std::cout << "Fine scan found no valid candidate; falling back to coarse "
373 "best."
374 << std::endl;
375 }
376 } else {
377 std::cerr << "WARNING: Coarse scan found no valid candidate. Returning 0.0 "
378 "shift."
379 << std::endl;
380 }
381
382 Int_t npts0 = 0;
383 Double_t nsd2_0 = 0;
384 ComputeNSD2(ref_x, ref_y, gr_x, gr_y, 0.0, thresh_dt_us, npts0, nsd2_0);
385 Double_t inv_nsd2_zero =
386 (npts0 > Constants::cfg.TIMING_SHIFT_MIN_NPTS) ? 1.0 / nsd2_0 : 0.0;
387
388 std::cout << "No-shift : 0 s (1/NSD2 = " << inv_nsd2_zero
389 << ", npts=" << npts0 << ")" << std::endl;
390 if (inv_nsd2_zero > 0 && final_inv_nsd2 > 0)
391 std::cout << " Improvement vs no-shift: " << final_inv_nsd2 / inv_nsd2_zero
392 << "x" << std::endl;
393
394 std::cout << "Final selected shift: " << final_shift << " s ("
395 << final_shift * 1e6 << " us)" << std::endl;
396
397 if (fine.found) {
398 PlotCostLandscape(fine.shifts, fine.inv_nsd2_values, final_shift, ref_board,
399 board, file_label, "_fine");
400 if (!coarse.shifts.empty())
402 coarse.best_shift, ref_board, board, file_label,
403 "_coarse");
404 } else if (!coarse.shifts.empty()) {
405 PlotCostLandscape(coarse.shifts, coarse.inv_nsd2_values, final_shift,
406 ref_board, board, file_label, "_coarse");
407 }
408
409 return final_shift;
410}
411
413 const std::vector<RawHit> &hits, const std::vector<LongChan> &channels,
414 Double_t min_energy, Double_t max_energy, Double_t tmin_s, Double_t tmax_s,
415 Double_t thresh_dt_us) {
416
417 std::map<std::pair<Int_t, Int_t>, Int_t> chan_to_idx;
418 for (Int_t i = 0; i < Int_t(channels.size()); i++) {
419 chan_to_idx[std::pair<Int_t, Int_t>(channels[i].board,
420 channels[i].channel)] = i;
421 }
422
423 std::vector<std::vector<ULong64_t>> per_chan_ts(channels.size());
424 for (Int_t i = 0; i < Int_t(channels.size()); i++) {
425 per_chan_ts[i].reserve(10000);
426 }
427
428 Long64_t n_entries = Long64_t(hits.size());
429 std::cout << "Single-pass extraction across " << channels.size()
430 << " long channels (in-memory, " << n_entries << " hits)..."
431 << std::endl;
432
433 for (Long64_t i = 0; i < n_entries; i++) {
434 const RawHit &h = hits[i];
435 if (h.energy < min_energy || h.energy > max_energy)
436 continue;
437 Double_t time_s = h.timestamp / 1e12;
438 if (time_s < tmin_s || time_s > tmax_s)
439 continue;
440
441 std::map<std::pair<Int_t, Int_t>, Int_t>::const_iterator it =
442 chan_to_idx.find(std::pair<Int_t, Int_t>(h.board, h.channel));
443 if (it == chan_to_idx.end())
444 continue;
445
446 per_chan_ts[it->second].push_back(h.timestamp);
447
448#if MUSIC_HOT_PATH_LOGGING
449 if (i % 10000000 == 0)
450 std::cout << " Progress: " << i << "/" << n_entries << std::endl;
451#endif
452 }
453
454 std::vector<TGraph *> graphs;
455 graphs.reserve(channels.size());
456 for (Int_t c = 0; c < Int_t(channels.size()); c++) {
457 TGraph *g = new TGraph();
458 const std::vector<ULong64_t> &ts = per_chan_ts[c];
459 for (Int_t i = 0; i + 1 < Int_t(ts.size()); i++) {
460 Double_t dt_us = (ts[i + 1] - ts[i]) / 1e6;
461 if (dt_us > thresh_dt_us) {
462 Double_t time_s = ts[i] / 1e12;
463 g->SetPoint(g->GetN(), time_s, dt_us);
464 }
465 }
466 std::cout << " " << channels[c].name << " (B" << channels[c].board << "C"
467 << channels[c].channel << "): " << ts.size() << " hits, "
468 << g->GetN() << " extreme events" << std::endl;
469 graphs.push_back(g);
470 }
471
472 return graphs;
473}
474
475// The file's time span in seconds and the overlap window inside it.
476struct FileSpan {
477 Double_t tmin_s;
478 Double_t tmax_s;
481};
482
483// The file's time span from the hit timestamps, the margins trimmed.
484static FileSpan MeasureFileSpan(const std::vector<RawHit> &hits,
485 Double_t overlap_margin_s) {
486 ULong64_t ts_min = hits[0].timestamp;
487 ULong64_t ts_max = hits[0].timestamp;
488 for (Int_t i = 1; i < Int_t(hits.size()); i++) {
489 if (hits[i].timestamp < ts_min)
490 ts_min = hits[i].timestamp;
491 if (hits[i].timestamp > ts_max)
492 ts_max = hits[i].timestamp;
493 }
494 FileSpan span;
495 span.tmin_s = ts_min / 1e12;
496 span.tmax_s = ts_max / 1e12;
497 span.overlap_tmin_s = span.tmin_s + overlap_margin_s;
498 span.overlap_tmax_s = span.tmax_s - overlap_margin_s;
499 std::cout << "File span [" << span.tmin_s << ", " << span.tmax_s
500 << "] s, using overlap [" << span.overlap_tmin_s << ", "
501 << span.overlap_tmax_s << "] s" << std::endl;
502 return span;
503}
504
505// Each board's ref channel's index in the long-channel list, where it is
506// present.
507static std::map<UShort_t, Int_t>
508RefIndexPerBoard(const std::vector<LongChan> &long_channels,
509 const std::vector<UShort_t> &board_channels) {
510 std::map<UShort_t, Int_t> board_to_ref_idx;
511 for (Int_t i = 0; i < Int_t(long_channels.size()); i++) {
512 if (long_channels[i].channel == board_channels[long_channels[i].board])
513 board_to_ref_idx[long_channels[i].board] = i;
514 }
515 return board_to_ref_idx;
516}
517
518// One shift per board against the ref board's ref channel: the shift in
519// seconds and the ps result; boards with no data WARNING and continue.
520static void MeasureBoardShifts(
521 const std::map<UShort_t, Int_t> &board_to_ref_idx,
522 const std::vector<TGraph *> &long_graphs, TGraph *ref_graph,
523 UShort_t ref_board, const std::vector<UShort_t> &board_channels,
524 const FileSpan &span, Double_t thresh_dt_us, const TString &file_label,
525 std::vector<Double_t> &board_shifts_s, TimeShiftResult &result) {
526 for (UShort_t board = 0; board < Constants::ActiveNBoards(); board++) {
527 if (board == ref_board)
528 continue;
529
530 std::map<UShort_t, Int_t>::const_iterator it = board_to_ref_idx.find(board);
531 if (it == board_to_ref_idx.end()) {
532 std::cout << "WARNING: Board " << board
533 << " ref channel not in long-channel list" << std::endl;
534 continue;
535 }
536
537 TGraph *board_graph = long_graphs[it->second];
538 if (board_graph->GetN() < 10) {
539 std::cout << "WARNING: Not enough data points for Board " << board
540 << std::endl;
541 continue;
542 }
543
544 std::cout << "Processing Board " << board << " Channel "
545 << board_channels[board] << std::endl;
546
547 Double_t shift_s = Timing::FindShiftBeam(
548 ref_graph, board_graph,
549 span.overlap_tmin_s + 0.1 * (span.overlap_tmax_s - span.overlap_tmin_s),
550 span.overlap_tmax_s - 0.1 * (span.overlap_tmax_s - span.overlap_tmin_s),
551 thresh_dt_us, ref_board, board, file_label);
552
553 board_shifts_s[board] = shift_s;
554 Long64_t shift_ps = static_cast<Long64_t>(shift_s * 1e12);
555 result.board_shifts[board] = -shift_ps;
556
557 std::cout << "Board " << ref_board << "-" << board << " shift: " << shift_s
558 << " s (" << shift_ps << " ps)" << std::endl;
559 }
560}
561
562// One point of each channel into the full-range extreme hists, the after
563// times collected for the dense-window search.
564static void FillExtremeHistograms(TH2F *h_extreme_before, TH2F *h_extreme_after,
565 const std::vector<TGraph *> &long_graphs,
566 const std::vector<LongChan> &long_channels,
567 const std::vector<Double_t> &board_shifts_s,
568 std::vector<Double_t> &extreme_times_after) {
569 for (Int_t c = 0; c < Int_t(long_channels.size()); c++) {
570 TGraph *g = long_graphs[c];
571 Double_t shift_s = board_shifts_s[long_channels[c].board];
572
573 for (Int_t k = 0; k < g->GetN(); k++) {
574 Double_t x, y;
575 g->GetPoint(k, x, y);
576
577 Double_t x_before_s = x;
578 Double_t x_after_s = x - shift_s;
579
580 h_extreme_before->Fill(x_before_s, Double_t(c));
581 h_extreme_after->Fill(x_after_s, Double_t(c));
582
583 extreme_times_after.push_back(x_after_s);
584 }
585 }
586}
587
588// The "B<board> <name>" Y-axis label on all four extreme-event plots.
589static void LabelChannelAxes(TH2F *h_extreme_before, TH2F *h_extreme_after,
590 TH2F *h_extreme_before_zoom,
591 TH2F *h_extreme_after_zoom,
592 const std::vector<LongChan> &long_channels) {
593 for (Int_t b = 0; b < Int_t(long_channels.size()); b++) {
594 TString label =
595 Form("B%d %s", long_channels[b].board, long_channels[b].name.Data());
596
597 h_extreme_before->GetYaxis()->SetBinLabel(b + 1, label);
598 h_extreme_after->GetYaxis()->SetBinLabel(b + 1, label);
599
600 h_extreme_before_zoom->GetYaxis()->SetBinLabel(b + 1, label);
601 h_extreme_after_zoom->GetYaxis()->SetBinLabel(b + 1, label);
602 }
603}
604
605// The second pass: the zoom-window hists only, in microseconds relative to
606// the window's start, with the entry counts.
607static void FillZoomHistograms(TH2F *h_extreme_before_zoom,
608 TH2F *h_extreme_after_zoom,
609 const std::vector<TGraph *> &long_graphs,
610 const std::vector<LongChan> &long_channels,
611 const std::vector<Double_t> &board_shifts_s,
612 Double_t before_zoom_t0_s,
613 Double_t after_zoom_t0_s, Double_t zoom_width_us,
614 Int_t &n_zoom_before, Int_t &n_zoom_after) {
615 for (Int_t c = 0; c < Int_t(long_channels.size()); c++) {
616 TGraph *g = long_graphs[c];
617 Double_t shift_s = board_shifts_s[long_channels[c].board];
618
619 for (Int_t k = 0; k < g->GetN(); k++) {
620 Double_t x, y;
621 g->GetPoint(k, x, y);
622
623 Double_t x_before_s = x;
624 Double_t x_after_s = x - shift_s;
625
626 Double_t x_before_zoom_us = (x_before_s - before_zoom_t0_s) * 1e6;
627 Double_t x_after_zoom_us = (x_after_s - after_zoom_t0_s) * 1e6;
628
629 if (x_before_zoom_us >= 0.0 && x_before_zoom_us <= zoom_width_us) {
630 h_extreme_before_zoom->Fill(x_before_zoom_us, Double_t(c));
631 n_zoom_before++;
632 }
633
634 if (x_after_zoom_us >= 0.0 && x_after_zoom_us <= zoom_width_us) {
635 h_extreme_after_zoom->Fill(x_after_zoom_us, Double_t(c));
636 n_zoom_after++;
637 }
638 }
639 }
640}
641
642// The RESULTS block: each board's shift in seconds against the ref.
643static void PrintBoardShifts(const TimeShiftResult &result,
644 UShort_t ref_board) {
645 std::cout << "RESULTS (ref board = " << ref_board << ")" << std::endl;
646 for (UShort_t board = 0; board < Constants::ActiveNBoards(); board++) {
647 if (board == ref_board)
648 continue;
649 std::cout << " Board " << ref_board << "-" << board << ": "
650 << result.board_shifts[board] * 1e-12 << " s" << std::endl;
651 }
652}
653
655 const std::vector<RawHit> &hits, const TString &file_label,
656 UShort_t ref_board, const std::vector<UShort_t> &board_channels,
657 Double_t min_energy, Double_t max_energy, Double_t overlap_margin_s,
658 Double_t thresh_dt_us) {
659
660 TimeShiftResult result;
661 result.board_shifts.assign(Constants::ActiveNBoards(), 0);
662
663 // Board sync disabled (e.g. 87Rb): skip the extract/scan/extreme-events
664 // pipeline; ApplyShifts then adds a zero shift to every hit.
666 std::cout << "Board sync disabled for this dataset; skipping timeshift "
667 "calculation (all board shifts = 0)."
668 << std::endl;
669 return result;
670 }
671
672 if (hits.empty()) {
673 std::cerr << "CalcTimeShiftsBeamMethodFromHits: empty hits vector"
674 << std::endl;
675 return result;
676 }
677
678 const FileSpan span = MeasureFileSpan(hits, overlap_margin_s);
679
680 std::vector<LongChan> long_channels = BuildLongChannelList();
681 std::vector<TGraph *> long_graphs = ExtractAllChannelsTimingStructureFromHits(
682 hits, long_channels, min_energy, max_energy, span.overlap_tmin_s,
683 span.overlap_tmax_s, thresh_dt_us);
684
685 const std::map<UShort_t, Int_t> board_to_ref_idx =
686 RefIndexPerBoard(long_channels, board_channels);
687
688 std::map<UShort_t, Int_t>::const_iterator ref_it =
689 board_to_ref_idx.find(ref_board);
690 if (ref_it == board_to_ref_idx.end()) {
691 std::cerr << "Reference board " << ref_board
692 << " ref channel is not in long-channel list" << std::endl;
693 for (Int_t k = 0; k < Int_t(long_graphs.size()); k++)
694 delete long_graphs[k];
695 return result;
696 }
697 TGraph *ref_graph = long_graphs[ref_it->second];
698
699 std::vector<Double_t> board_shifts_s(Constants::ActiveNBoards(), 0.0);
700 MeasureBoardShifts(board_to_ref_idx, long_graphs, ref_graph, ref_board,
701 board_channels, span, thresh_dt_us, file_label,
702 board_shifts_s, result);
703
704 Int_t time_bins = TMath::Min(
705 500,
706 TMath::Max(100, static_cast<Int_t>((span.tmax_s - span.tmin_s) * 50.0)));
707 Int_t n_y = static_cast<Int_t>(long_channels.size());
708
709 TH2F *h_extreme_before =
710 new TH2F("hExtremeBefore", ";Time [s];", time_bins, span.tmin_s,
711 span.tmax_s, n_y, -0.5, n_y - 0.5);
712
713 TH2F *h_extreme_after =
714 new TH2F("hExtremeAfter", ";Time [s];", time_bins, span.tmin_s,
715 span.tmax_s, n_y, -0.5, n_y - 0.5);
716
717 std::vector<Double_t> extreme_times_after;
718
719 extreme_times_after.reserve(10000);
720 FillExtremeHistograms(h_extreme_before, h_extreme_after, long_graphs,
721 long_channels, board_shifts_s, extreme_times_after);
722
723 const Double_t zoom_width_us = 1000.0;
724 const Double_t zoom_width_s = zoom_width_us * 1e-6;
725
726 // About 1 us/bin. Increase this if you want sub-us binning.
727 Int_t zoom_bins =
728 TMath::Min(2000, TMath::Max(100, static_cast<Int_t>(zoom_width_us)));
729
730 // Choose a dense after-correction window, then use the same absolute window
731 // for before and after. This makes before/after directly comparable.
732 Double_t after_zoom_t0_s = FindDensestTimeWindowStartS(
733 extreme_times_after, zoom_width_s, span.tmin_s);
734
735 Double_t before_zoom_t0_s = after_zoom_t0_s;
736
737 std::cout << "Zoom window before: [" << before_zoom_t0_s << ", "
738 << before_zoom_t0_s + zoom_width_s << "] s" << std::endl;
739
740 std::cout << "Zoom window after : [" << after_zoom_t0_s << ", "
741 << after_zoom_t0_s + zoom_width_s << "] s" << std::endl;
742
743 TH2F *h_extreme_before_zoom =
744 new TH2F("hExtremeBeforeZoom", ";Time [#mus];", zoom_bins, 0.0,
745 zoom_width_us, n_y, -0.5, n_y - 0.5);
746
747 TH2F *h_extreme_after_zoom =
748 new TH2F("hExtremeAfterZoom", ";Time [#mus];", zoom_bins, 0.0,
749 zoom_width_us, n_y, -0.5, n_y - 0.5);
750
751 // Restore Y-axis labels for all four plots.
752 LabelChannelAxes(h_extreme_before, h_extreme_after, h_extreme_before_zoom,
753 h_extreme_after_zoom, long_channels);
754
755 // Second pass:
756 // Fill only the zoom-window histograms, in microseconds relative to t0.
757 Int_t n_zoom_before = 0;
758 Int_t n_zoom_after = 0;
759 FillZoomHistograms(h_extreme_before_zoom, h_extreme_after_zoom, long_graphs,
760 long_channels, board_shifts_s, before_zoom_t0_s,
761 after_zoom_t0_s, zoom_width_us, n_zoom_before,
762 n_zoom_after);
763
764 std::cout << "Zoom plot entries before correction: " << n_zoom_before
765 << std::endl;
766 std::cout << "Zoom plot entries after correction : " << n_zoom_after
767 << std::endl;
768
769 PlotExtremeEvents2D(h_extreme_before, h_extreme_after, h_extreme_before_zoom,
770 h_extreme_after_zoom, file_label);
771 PrintBoardShifts(result, ref_board);
772
773 delete h_extreme_before;
774 delete h_extreme_after;
775 delete h_extreme_before_zoom;
776 delete h_extreme_after_zoom;
777
778 for (Int_t k = 0; k < Int_t(long_graphs.size()); k++)
779 delete long_graphs[k];
780
781 return result;
782}
783
784void Timing::ApplyShiftsInPlace(std::vector<RawHit> &hits,
785 const std::vector<Long64_t> &board_shifts) {
786 for (Int_t i = 0; i < Int_t(hits.size()); i++) {
787 Long64_t board_shift = (hits[i].board < UShort_t(board_shifts.size()))
788 ? board_shifts[hits[i].board]
789 : 0;
790 hits[i].timestamp = hits[i].timestamp + board_shift;
791 }
792}
793
794void Timing::SortHitsByTimestamp(std::vector<RawHit> &hits) {
796 Int_t rc = GpuAccel::GetSort()(hits.data(), Long64_t(hits.size()));
798 if (rc == 0)
799 return;
800 std::cerr << "[GPU] Sort failed (rc=" << rc << "), falling back to CPU."
801 << std::endl;
802 }
803 std::sort(hits.begin(), hits.end(), [](const RawHit &a, const RawHit &b) {
804 return a.timestamp < b.timestamp;
805 });
806}
807
808Double_t Timing::FindDensestTimeWindowStartS(std::vector<Double_t> times,
809 Double_t window_width_s,
810 Double_t fallback_start_s) {
811 if (times.empty() || window_width_s <= 0)
812 return fallback_start_s;
813
814 std::sort(times.begin(), times.end());
815
816 Long64_t best_i = 0;
817 Long64_t best_count = 0;
818 Long64_t j = 0;
819
820 for (Long64_t i = 0; i < Long64_t(times.size()); i++) {
821 if (j < i)
822 j = i;
823
824 while (j < Long64_t(times.size()) && times[j] <= times[i] + window_width_s)
825 j++;
826
827 Long64_t count = j - i;
828 if (count > best_count) {
829 best_count = count;
830 best_i = i;
831 }
832 }
833
834 // Add a small left padding so the first event is not exactly on the axis
835 // edge.
836 return times[best_i] - 0.05 * window_width_s;
837}
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
Definition FileSet.cpp:3
ShiftScanResult ScanShiftRange(const std::vector< Double_t > &ref_x, const std::vector< Double_t > &ref_y, const std::vector< Double_t > &gr_x, const std::vector< Double_t > &gr_y, Double_t shift_min_s, Double_t shift_max_s, Double_t shift_step_s, Double_t thresh_dt_us, const TString &label)
Definition Timing.cpp:14
Aligning the clocks of independent digitiser boards.
static Bool_t TryAcquireSortSlot()
Claim one of the concurrent GPU sort slots.
Definition GpuAccel.cpp:45
static void ReleaseSortSlot()
Give back a slot claimed by TryAcquireSortSlot().
Definition GpuAccel.cpp:53
static SortFunc GetSort()
The resolved sort entry point.
Definition GpuAccel.cpp:43
static Bool_t Available()
Whether the kernel loaded successfully.
Definition GpuAccel.cpp:41
static Double_t FindDensestTimeWindowStartS(std::vector< Double_t > times, Double_t window_width_s, Double_t fallback_start_s)
Start of the busiest window of a given width.
Definition Timing.cpp:808
static TimeShiftResult CalcTimeShiftsBeamMethodFromHits(const std::vector< RawHit > &hits, const TString &file_label, UShort_t ref_board, const std::vector< UShort_t > &board_channels, Double_t min_energy, Double_t max_energy, Double_t overlap_margin_s, Double_t thresh_dt_us)
Measure every board's offset against a reference board.
Definition Timing.cpp:654
static void SortHitsByTimestamp(std::vector< RawHit > &hits)
Sort hits into ascending timestamp order, in place.
Definition Timing.cpp:794
static void ComputeNSD2(const std::vector< Double_t > &ref_x, const std::vector< Double_t > &ref_y, const std::vector< Double_t > &gr_x, const std::vector< Double_t > &gr_y, Double_t shift, Double_t thresh_dt_us, Int_t &npts, Double_t &nsd2)
Normalised squared deviation between two timing structures at a shift.
Definition Timing.cpp:88
static void ApplyShiftsInPlace(std::vector< RawHit > &hits, const std::vector< Long64_t > &board_shifts)
Add the measured offsets to the hits, in place.
Definition Timing.cpp:784
static void PlotExtremeEvents2D(TH2F *h_before, TH2F *h_after, TH2F *h_before_zoom, TH2F *h_after_zoom, const TString &file_label)
Save the before-and-after alignment diagnostic figures.
Definition Timing.cpp:181
static std::vector< LongChan > BuildLongChannelList()
Every long-end channel in the active map, in stable order.
Definition Timing.cpp:164
static Bool_t LongChanOrder(const LongChan &a, const LongChan &b)
Ordering predicate giving a stable channel sequence.
Definition Timing.cpp:158
static std::vector< TGraph * > ExtractAllChannelsTimingStructureFromHits(const std::vector< RawHit > &hits, const std::vector< LongChan > &channels, Double_t min_energy, Double_t max_energy, Double_t tmin_s, Double_t tmax_s, Double_t thresh_dt_us)
Build the beam timing structure for each channel.
Definition Timing.cpp:412
static void PlotCostLandscape(const std::vector< Double_t > &shifts, const std::vector< Double_t > &inv_nsd2_values, Double_t best_shift, UShort_t ref_board, UShort_t board, const TString &file_label, const TString &tag)
Save the shift-search cost landscape for one board pair.
Definition Timing.cpp:252
static Bool_t IsLongChannel(const TString &name)
Whether a channel name denotes a long end.
Definition Timing.cpp:138
static Double_t FindShiftBeam(TGraph *ref, TGraph *gr, Double_t overlap_tmin_s, Double_t overlap_tmax_s, Double_t thresh_dt_us, UShort_t ref_board, UShort_t board, const TString &file_label)
Best shift aligning one board's beam structure to the reference's.
Definition Timing.cpp:297
Bool_t ActiveDoBoardSync()
Whether to run the multi-board timing alignment.
Bool_t SavePlots()
FileInSample(), by the name the plot call sites use.
const std::map< std::pair< Int_t, Int_t >, TString > & ActiveChannelMap()
The channel map in force.
Int_t ActiveNBoards()
Boards in the active epoch's setup.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
Double_t tmax_s
Definition Timing.cpp:478
Double_t overlap_tmin_s
Definition Timing.cpp:479
Double_t overlap_tmax_s
Definition Timing.cpp:480
Double_t tmin_s
Definition Timing.cpp:477
One long-end channel, used as a timing reference.
Definition Timing.hpp:44
UShort_t channel
Channel on that board.
Definition Timing.hpp:46
TString name
Channel name from the active map.
Definition Timing.hpp:47
UShort_t board
Board id.
Definition Timing.hpp:45
std::vector< Double_t > inv_nsd2_values
Definition Timing.cpp:11
Double_t best_shift
Definition Timing.cpp:6
Int_t best_npts
Definition Timing.cpp:8
Bool_t found
Definition Timing.cpp:9
Double_t best_inv_nsd2
Definition Timing.cpp:7
std::vector< Double_t > shifts
Definition Timing.cpp:10
Per-board timing offsets, indexed by board number.
Definition Timing.hpp:37
std::vector< Long64_t > board_shifts
Offset to add to each board's timestamps, in picoseconds.
Definition Timing.hpp:40