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