MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
Pipeline.cpp
Go to the documentation of this file.
1#include "Pipeline.hpp"
2#include "PulseHistory.hpp"
3
4std::mutex fused_log_mutex;
5
6Bool_t FusedExists(const TString &subpath) {
7 TString full = IO::GetRootFilesBaseDir() + "/" + subpath;
8 return !gSystem->AccessPathName(full);
9}
10
11Double_t FusedSecSince(const std::chrono::steady_clock::time_point &t0) {
12 return std::chrono::duration<Double_t>(std::chrono::steady_clock::now() - t0)
13 .count();
14}
15
16// /proc/self/statm reports VmRSS in pages (field 2); 4 KiB/page on Linux.
17// Worker-local label so concurrent log lines stay attributable.
18void PrintMemUsage(const char *label) {
19 Long64_t rss = 0;
20 std::ifstream statm("/proc/self/statm");
21 Long64_t dummy;
22 statm >> dummy >> rss;
23 Double_t rss_gb = rss * 4096.0 / (1024.0 * 1024.0 * 1024.0);
24 std::lock_guard<std::mutex> lock(fused_log_mutex);
25 std::cout << "[MEM] " << label << ": " << rss_gb << " GB RSS" << std::endl;
26}
27
28Bool_t EnsureRunHeaderFused(Int_t run, UShort_t &header) {
29 if (BinaryToRoot::ReadHeaderSidecar(run, header))
30 return kTRUE;
31
33 // SOLARIS: find first available file (chunk or original) for header
34 std::vector<TString> suffixes = FileSet::DiscoverSolRunSuffixes(run);
35 Bool_t found = kFALSE;
36 for (Int_t k = 0; k < Int_t(suffixes.size()); k++) {
37 FileSpec s0;
38 s0.run = run;
39 s0.suffix = suffixes[k];
40 TString sol_path = FileSet::SolBinPath(s0);
41 if (gSystem->AccessPathName(sol_path))
42 continue;
43
44 SOLReader reader;
45 if (!reader.Open(sol_path.Data()))
46 continue;
47 if (!reader.ReadEvent()) {
48 reader.Close();
49 continue;
50 }
51 header = reader.GetCurrentEvent().block_header;
52 reader.Close();
53 found = kTRUE;
54 break;
55 }
56 if (!found) {
57 std::cerr << "SOL header gather FAILED for run " << run
58 << " (no accessible files)" << std::endl;
59 return kFALSE;
60 }
61 } else {
62 // CoMPASS: read global header from first .BIN file
63 FileSpec s0;
64 s0.run = run;
65 s0.suffix = "";
66 TString bin_path = FileSet::CompassBinPath(s0);
67 std::pair<std::vector<RawHit>, UShort_t> p =
68 InitUtils::ConvertCoMPASSBinToHits(bin_path, 0);
69 if (p.second == 0)
70 return kFALSE;
71 header = p.second;
72 }
73
75 return kTRUE;
76}
77
78Bool_t RunFusedPipelineForFile(FileSpec spec, UShort_t run_header,
79 const EventBuilder::SlotMap &slot_map,
80 const std::vector<ChannelCal> &chans) {
81 TString file_label = FileSet::FileLabel(spec);
82 std::chrono::steady_clock::time_point t_total =
83 std::chrono::steady_clock::now();
84 std::chrono::steady_clock::time_point t0;
85 Double_t t_parse = 0, t_timing = 0, t_apply = 0, t_events = 0, t_cal = 0,
86 t_history = 0;
88 Bool_t history_done = kFALSE;
89
90 // SKIP_EXISTING skips the expensive data processing (binary read, timing,
91 // event build, calibration) when the events file already exists -- but the
92 // plots below are still (re)made from that existing file.
93 const Bool_t skip_processing =
94 Constants::cfg.SKIP_EXISTING &&
95 FusedExists(FileSet::EventsName(spec) + ".root");
96
97 if (skip_processing) {
98 std::lock_guard<std::mutex> lock(fused_log_mutex);
99 std::cout << "[skip-build] " << file_label
100 << " events exist; re-making plots only" << std::endl;
101 } else {
102 TString bin_path;
104 bin_path = FileSet::SolBinPath(spec);
105 } else {
106 bin_path = FileSet::CompassBinPath(spec);
107 }
108
109 if (gSystem->AccessPathName(bin_path)) {
110 std::lock_guard<std::mutex> lock(fused_log_mutex);
111 std::cerr << "[fail] " << file_label
112 << (Constants::ActiveUseSolarisData() ? " SOL" : " BIN")
113 << " missing: " << bin_path << std::endl;
114 return kFALSE;
115 }
116
117 PrintMemUsage((TString("before binary read ") + file_label).Data());
118
119 t0 = std::chrono::steady_clock::now();
120 std::vector<RawHit> hits;
121
123 // SOLARIS: stream blocks directly to RawHit (no intermediate SOLHit
124 // vector)
125 SOLReader sol_reader;
126 sol_reader.SetSkipTraces(kTRUE);
127 if (!sol_reader.Open(bin_path.Data())) {
128 std::lock_guard<std::mutex> lock(fused_log_mutex);
129 std::cerr << "[fail] " << file_label << " cannot open SOL file"
130 << std::endl;
131 return kFALSE;
132 }
133 // Reserve an estimate to avoid repeated reallocation (~1M blocks typical)
134 hits.reserve(1048576);
135 while (sol_reader.ReadEvent()) {
136 const SOLData &sol = sol_reader.GetCurrentEvent();
137 RawHit raw;
138 raw.board = 0;
139 raw.channel = sol.channel;
140 raw.energy = sol.energy;
141 raw.timestamp = sol.timestamp * 1000;
142 raw.flags = MapSOLFlagsToCoMPASS(sol.flags_high, sol.flags_low);
143 hits.push_back(raw);
144 }
145 sol_reader.Close();
146 } else {
147 // CoMPASS: direct conversion to RawHit
148 UShort_t use_header = (spec.suffix == "") ? 0 : run_header;
149 std::pair<std::vector<RawHit>, UShort_t> parsed =
150 InitUtils::ConvertCoMPASSBinToHits(bin_path, use_header);
151 hits = parsed.first;
152 if (spec.suffix == "")
153 BinaryToRoot::WriteHeaderSidecar(spec.run, parsed.second);
154 }
155 t_parse = FusedSecSince(t0);
156
157 PrintMemUsage((TString("after binary read ") + file_label).Data());
158
159 if (hits.empty()) {
160 std::lock_guard<std::mutex> lock(fused_log_mutex);
161 std::cerr << "[fail] " << file_label << " parse produced no hits"
162 << std::endl;
163 return kFALSE;
164 }
165
167 t0 = std::chrono::steady_clock::now();
169 hits, file_label, Constants::ActiveTimingRefBoard(),
171 Constants::cfg.TIMING_MIN_ENERGY, Constants::cfg.TIMING_MAX_ENERGY,
172 Constants::cfg.TIMING_OVERLAP_MARGIN_S,
173 Constants::cfg.TIMING_THRESH_DT_US);
174 t_timing = FusedSecSince(t0);
175
176 PrintMemUsage((TString("after timing ") + file_label).Data());
177
178 t0 = std::chrono::steady_clock::now();
179 Timing::ApplyShiftsInPlace(hits, shift_result.board_shifts);
182 t_apply = FusedSecSince(t0);
183
184 PrintMemUsage((TString("after apply+sort ") + file_label).Data());
185 } else {
186 std::lock_guard<std::mutex> lock(fused_log_mutex);
187 std::cout << "[skip-timing] " << file_label
188 << " board sync and sort both disabled; skipping to event "
189 "build"
190 << std::endl;
191 }
192
193 // Pole-zero pulse-history correction on the long ends, measured on this
194 // subfile's own beam-like events. Before the build, since it changes the
195 // energies the builder dedups on.
196 if (Constants::cfg.PULSE_HISTORY_CORRECTION) {
197 t0 = std::chrono::steady_clock::now();
198 std::vector<Int_t> groups = PulseHistory::BuildGroupMap();
199 if (PulseHistory::Measure(hits, groups, history, file_label))
200 PulseHistory::Apply(hits, groups, history);
201 PulseHistory::SavePlots(history, file_label);
202 history_done = kTRUE;
203 t_history = FusedSecSince(t0);
204 std::lock_guard<std::mutex> lock(fused_log_mutex);
205 std::cout << PulseHistory::Report(history, file_label);
206 }
207
208 t0 = std::chrono::steady_clock::now();
210 hits, slot_map, FileSet::EventsName(spec), file_label);
211 t_events = FusedSecSince(t0);
212
213 PrintMemUsage((TString("after event build ") + file_label).Data());
214
215 std::vector<RawHit>().swap(hits);
216
217 PrintMemUsage((TString("after hits free ") + file_label).Data());
218
219 if (!build_ok) {
220 std::lock_guard<std::mutex> lock(fused_log_mutex);
221 std::cerr << "[fail] " << file_label << " event build failed"
222 << std::endl;
223 return kFALSE;
224 }
225 if (history_done)
227 history);
228 }
229
230 // Calibration reads the events file (freshly built or pre-existing) and
231 // fits beam peaks to derive gains, so it runs in plot-only mode too,
232 // regenerating the calibration diagnostic histograms.
233 if (!chans.empty()) {
234 t0 = std::chrono::steady_clock::now();
236 t_cal = FusedSecSince(t0);
237 PrintMemUsage((TString("after calibration ") + file_label).Data());
238 }
239
240 // Normed summary histograms (requires calibration from above).
241 {
242 t0 = std::chrono::steady_clock::now();
244 file_label);
245 Double_t t_normed = FusedSecSince(t0);
246 std::lock_guard<std::mutex> lock(fused_log_mutex);
247 std::cout << " normed summary: " << t_normed << "s" << std::endl;
248 }
249
250 Double_t total = FusedSecSince(t_total);
251 {
252 std::lock_guard<std::mutex> lock(fused_log_mutex);
253 std::cout << std::fixed << std::setprecision(1) << "[done] " << file_label
254 << " total=" << total << "s parse=" << t_parse
255 << " timing=" << t_timing << " apply=" << t_apply
256 << " history=" << t_history << " events=" << t_events
257 << " cal=" << t_cal << std::endl;
258 }
259 return kTRUE;
260}
261
262// One epoch's worth of work (or the whole flat run list when the dataset
263// declares no epochs). The caller owns the log redirection so a multi-epoch
264// run writes one log rather than truncating it per epoch, and owns the active
265// epoch so every Active*() below reads this epoch's hardware settings.
266static void RunActiveSelection() {
267 std::vector<FileSpec> specs = FileSet::BuildRawOrProcessedFileSpecs();
268 Int_t n_specs = Int_t(specs.size());
269
270 std::set<Int_t> unique_runs;
271 for (Int_t k = 0; k < n_specs; k++)
272 unique_runs.insert(specs[k].run);
273
274 // The global header is only consumed when a subfile is (re)built from its
275 // BIN; a run whose subfiles are all already processed runs plot-only and
276 // never touches the raw dir, so skip its header gather entirely.
277 std::set<Int_t> runs_needing_header;
278 for (Int_t k = 0; k < n_specs; k++) {
279 Bool_t will_build = !(Constants::cfg.SKIP_EXISTING &&
280 FusedExists(FileSet::EventsName(specs[k]) + ".root"));
281 if (will_build)
282 runs_needing_header.insert(specs[k].run);
283 }
284
285 std::cout << "Phase A: gathering global headers for "
286 << runs_needing_header.size() << " run(s)..." << std::endl;
287 std::map<Int_t, UShort_t> run_headers;
288 for (std::set<Int_t>::const_iterator it = runs_needing_header.begin();
289 it != runs_needing_header.end(); ++it) {
290 UShort_t h;
291 if (!EnsureRunHeaderFused(*it, h)) {
292 std::cerr << "Header gather FAILED for run " << *it << std::endl;
293 continue;
294 }
295 run_headers[*it] = h;
296 std::cout << " Run " << *it << " header 0x" << std::hex << h << std::dec
297 << std::endl;
298 }
299
301
302 std::vector<ChannelCal> chans;
303 if (Constants::cfg.SKIP_CALIBRATION) {
304 std::cout << "SKIP_CALIBRATION=true; skipping beam calibration and eres "
305 "aggregation (events kept in raw ADC)."
306 << std::endl;
307 } else {
309 }
310
311 Int_t n_workers =
312 TMath::Min(Int_t(std::thread::hardware_concurrency()), n_specs);
313 n_workers = TMath::Min(n_workers, Constants::cfg.MAX_FUSED_WORKERS);
314 std::cout << "Phase B: fused pipeline on " << n_specs << " files with "
315 << n_workers << " workers." << std::endl;
316
317 std::queue<Int_t> work;
318 for (Int_t k = 0; k < n_specs; k++)
319 work.push(k);
320 std::mutex work_mutex;
321
322 std::vector<std::thread> workers;
323 for (Int_t w = 0; w < n_workers; w++) {
324 workers.emplace_back([&]() {
325 while (true) {
326 Int_t k;
327 {
328 std::lock_guard<std::mutex> lk(work_mutex);
329 if (work.empty())
330 return;
331 k = work.front();
332 work.pop();
333 }
334 FileSpec spec = specs[k];
335 UShort_t header =
336 run_headers.count(spec.run) ? run_headers[spec.run] : UShort_t(0);
337 Bool_t ok = RunFusedPipelineForFile(spec, header, slot_map, chans);
338 if (!ok) {
339 std::lock_guard<std::mutex> lk(fused_log_mutex);
340 std::cerr << "FAILED: " << FileSet::FileLabel(spec) << std::endl;
341 }
342 }
343 });
344 }
345 for (Int_t w = 0; w < Int_t(workers.size()); w++)
346 workers[w].join();
347
348 std::cout << "All fused pipelines complete." << std::endl;
349
350 if (!chans.empty()) {
351 std::cout << "Phase C: per-run ridge-ratio aggregation" << std::endl;
352 for (std::set<Int_t>::const_iterator it = unique_runs.begin();
353 it != unique_runs.end(); ++it) {
354 std::vector<FileSpec> run_specs;
355 for (Int_t k = 0; k < n_specs; k++)
356 if (specs[k].run == *it)
357 run_specs.push_back(specs[k]);
359 }
360 std::cout << "Phase C: per-run eres TOML aggregation" << std::endl;
361 for (std::set<Int_t>::const_iterator it = unique_runs.begin();
362 it != unique_runs.end(); ++it) {
363 std::vector<FileSpec> run_specs;
364 for (Int_t k = 0; k < n_specs; k++)
365 if (specs[k].run == *it)
366 run_specs.push_back(specs[k]);
368 }
369 }
370}
371
374 ROOT::EnableThreadSafety();
376 const TString project_root = Paths::DatasetDir();
377 InitUtils::SetROOTPreferences(PlotSaveFormat::kPNG,
378 Paths::ResultsDir() + "/plots",
379 Paths::ResultsDir() + "/root_files");
380
381 TString log_path = project_root + "/pipeline_fused.log";
382 std::ofstream log_file(log_path.Data());
383 std::streambuf *saved_cout = std::cout.rdbuf(log_file.rdbuf());
384 std::streambuf *saved_cerr = std::cerr.rdbuf(log_file.rdbuf());
385 Int_t saved_error_level = gErrorIgnoreLevel;
386 gErrorIgnoreLevel = kError;
387
388 if (Constants::cfg.EPOCHS.empty()) {
389 RunActiveSelection();
390 } else {
391 for (Int_t e = 0; e < Int_t(Constants::cfg.EPOCHS.size()); e++) {
392 const RunEpoch &epoch = Constants::cfg.EPOCHS[e];
393 if (!epoch.enabled || epoch.runs.empty())
394 continue;
395 std::cout << std::endl;
396 std::cout << "=== epoch " << epoch.name << " ("
397 << (epoch.source == kSolaris ? "SOLARIS" : "CoMPASS") << ", "
398 << epoch.runs.size() << " run(s), " << epoch.n_boards << "x"
399 << epoch.n_channels << " ch) ===" << std::endl;
401 RunActiveSelection();
403 }
404 }
405
406 std::cout.rdbuf(saved_cout);
407 std::cerr.rdbuf(saved_cerr);
408 gErrorIgnoreLevel = saved_error_level;
409 log_file.close();
410 std::cout << "Fused pipeline finished. Output logged to " << log_path
411 << std::endl;
412}
UInt_t MapSOLFlagsToCoMPASS(UShort_t sol_flags_high, UShort_t sol_flags_low)
Pack SOLARIS flags into a CoMPASS-compatible word.
Bool_t FusedExists(const TString &subpath)
Definition Pipeline.cpp:6
Double_t FusedSecSince(const std::chrono::steady_clock::time_point &t0)
Definition Pipeline.cpp:11
void PrintMemUsage(const char *label)
Definition Pipeline.cpp:18
Bool_t RunFusedPipelineForFile(FileSpec spec, UShort_t run_header, const EventBuilder::SlotMap &slot_map, const std::vector< ChannelCal > &chans)
Definition Pipeline.cpp:78
std::mutex fused_log_mutex
Definition Pipeline.cpp:4
Bool_t EnsureRunHeaderFused(Int_t run, UShort_t &header)
Definition Pipeline.cpp:28
Pole-zero pulse-history correction on the raw hit stream.
@ kSolaris
SOLARIS DAQ.
Definition RunEpoch.hpp:15
static Bool_t ReadHeaderSidecar(Int_t run, UShort_t &header)
Read a run's global header back from its sidecar.
static void WriteHeaderSidecar(Int_t run, UShort_t header)
Write a run's global header to its sidecar.
static void CalibrateBeamOneSubfile(const FileSpec &spec, const std::vector< ChannelCal > &chans_template)
Calibrate one subfile end to end.
static void AggregateEresTomlForRun(Int_t run, const std::vector< FileSpec > &specs)
Aggregate the run's energy-resolution measurements into its TOML.
static std::vector< ChannelCal > BuildChannels()
One ChannelCal per readout channel in the active channel map.
static void AggregateRidgeRatiosForRun(Int_t run, const std::vector< FileSpec > &specs)
Replace each subfile's ridge ratio with the run-level median.
static SlotMap BuildSlotMap()
Build the board/channel to slot lookup for this dataset.
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 BuildNormedSummaryHistograms(const TString &input_filename, const TString &file_label)
Build the calibrated summary histograms for one events file.
static TString EventsName(const FileSpec &s)
Filename of the built-events ROOT file for a subfile.
Definition FileSet.cpp:289
static std::vector< TString > DiscoverSolRunSuffixes(Int_t run)
Subfile suffixes present on disk for a SOLARIS run.
Definition FileSet.cpp:117
static TString FileLabel(const FileSpec &s)
Human-readable label identifying a subfile.
Definition FileSet.cpp:296
static TString CompassBinPath(const FileSpec &s)
Path to a CoMPASS binary subfile.
Definition FileSet.cpp:6
static std::vector< FileSpec > BuildRawOrProcessedFileSpecs()
The union of raw and processed subfiles, without duplicates.
Definition FileSet.cpp:263
static TString SolBinPath(const FileSpec &s)
Path to a SOLARIS .sol subfile.
Definition FileSet.cpp:11
static Bool_t Init()
Load the GPU library and resolve the sort symbol.
Definition GpuAccel.cpp:9
static TString DatasetDir()
Absolute path to the active dataset directory, analysis/<iso>.
Definition Paths.cpp:60
static TString ResultsDir()
Absolute path to the directory receiving generated output.
Definition Paths.cpp:24
static void PrintLogo()
Print the project wordmark.
Definition Paths.cpp:31
static void Run()
Run the whole pipeline over the configured subfiles.
Definition Pipeline.cpp:372
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 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
UShort_t ActiveTimingRefBoard()
Board the others are timing-aligned against.
Bool_t ActiveDoBoardSync()
Whether to run the multi-board timing alignment.
Bool_t ActiveDoSort()
Whether to time-sort hits before event building.
const std::vector< UShort_t > & ActiveTimingRefBoardChannels()
Reference channel per board, for the alignment.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
void SetActiveEpoch(const RunEpoch *epoch)
Set the epoch the Active*() accessors read from.
Bool_t ActiveUseSolarisData()
Whether this era's data is SOLARIS rather than CoMPASS.
TString Report(const Result &res, const TString &file_label)
Format the pass as a human-readable report.
std::vector< Int_t > BuildGroupMap()
Group lookup for every (board, channel) under the active map.
void Apply(std::vector< RawHit > &hits, const std::vector< Int_t > &group_of, Result &res)
Apply the measured kernels to the hit stream, in place.
Bool_t Measure(std::vector< RawHit > &hits, const std::vector< Int_t > &group_of, Result &res, const TString &file_label)
Measure the kernels on this subfile's beam-like events.
void SavePlots(Result &res, const TString &file_label)
Draw and save the diagnostics, then free them.
void WriteToEventsFile(const TString &events_subpath, const Result &res)
Record the kernels and counters alongside a subfile's events.
One input file: a run number and the subfile suffix within it.
Definition FileSet.hpp:40
TString suffix
Subfile suffix, empty for the first subfile.
Definition FileSet.hpp:42
Int_t run
Run number.
Definition FileSet.hpp:41
Everything one subfile's pulse-history pass produced.
One acquisition period of a dataset.
Definition RunEpoch.hpp:37
std::vector< Int_t > runs
Run numbers belonging to this epoch.
Definition RunEpoch.hpp:51
TString name
Epoch name, used in logs and plot paths.
Definition RunEpoch.hpp:38
Bool_t enabled
Whether this epoch participates in the analysis.
Definition RunEpoch.hpp:40
Int_t n_boards
Boards in this era's setup.
Definition RunEpoch.hpp:57
RunSource source
Acquisition system for this era.
Definition RunEpoch.hpp:39
Int_t n_channels
Channels per board.
Definition RunEpoch.hpp:58
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