Belle II Software development
cdcdedx_validation.py
1
8
9'''
10Validation plots for CDC dedx calibration.
11'''
12
13import sys
14import os
15import json
16import matplotlib.pyplot as plt
17import pandas as pd
18import numpy as np
19from matplotlib.backends.backend_pdf import PdfPages
20import shutil
21import basf2
22import process_wiregain as pw
23import process_cosgain as pc
24import process_onedcell as oned
25import process_rungain as rg
26
27from prompt import ValidationSettings
28
29settings = ValidationSettings(name="CDC dedx",
30 description=__doc__,
31 download_files=[],
32 expert_config={
33 "GT": "data_prompt_rel09",
34 })
35
36
37def save_to_pdf(pdf, fig):
38 fig.tight_layout()
39 pdf.savefig(fig)
40 plt.close(fig)
41
42
43def read_txt(filepath, columns, sep=r"\s+"):
44 if not os.path.exists(filepath):
45 basf2.B2ERROR(f"File not found: {filepath}")
46 return None
47 return pd.read_csv(filepath, sep=sep, header=None, names=columns)
48
49
50def make_pdf_path(prefix, suffix):
51 pdf_path = os.path.join("plots", "validation", f"{prefix}_{suffix}.pdf")
52 os.makedirs(os.path.dirname(pdf_path), exist_ok=True)
53 return pdf_path
54
55
56def get_positive_minmax(series):
57 positive = series[series > 0]
58 ymin = positive.min() if not positive.empty else series.min()
59 ymax = series.max()
60 return ymin, ymax
61
62
63def rungain_validation(path, suffix):
64 val_path = os.path.join(path, "plots", "run", f"dedx_vs_run_{suffix}.txt")
65 df = read_txt(val_path, ["run", "mean", "mean_err", "reso", "reso_err"])
66 if df is None:
67 return
68
69 df['run'] = df['run'].astype(str)
70
71 pdf_path = make_pdf_path("dedx_vs_run", suffix)
72
73 with PdfPages(pdf_path) as pdf:
74 fig, ax = plt.subplots(1, 2, figsize=(20, 6))
75 n = len(df)
76 space = max(10, min(50, int(200 / max(n, 1))))
77
78 # Mean plot
79 ymin, ymax = get_positive_minmax(df['mean'])
80 pc.hist(y_min=ymin-0.02, y_max=ymax+0.02, xlabel="Run range", ylabel="dE/dx mean", space=space, ax=ax[0])
81 ax[0].errorbar(df['run'], df['mean'], yerr=df['mean_err'], fmt='*', markersize=8, rasterized=True, label='Bhabha mean')
82 ax[0].legend(fontsize=12)
83 ax[0].set_title('dE/dx Mean vs Run', fontsize=14)
84
85 # Reso plot
86 ymin, ymax = get_positive_minmax(df['reso'])
87 pc.hist(y_min=ymin-0.01, y_max=ymax+0.01, xlabel="Run range", ylabel="dE/dx reso", space=space, ax=ax[1])
88 ax[1].errorbar(df['run'], df['reso'], yerr=df['reso_err'], fmt='*', markersize=8, rasterized=True, label='Bhabha reso')
89 ax[1].legend(fontsize=12)
90 ax[1].set_title('dE/dx Resolution vs Run', fontsize=14)
91
92 fig.suptitle("dE/dx vs Run", fontsize=20)
93 save_to_pdf(pdf, fig)
94
95
96def wiregain_validation(path, suffix):
97
98 val_path_gwire = os.path.join(path, "plots", "wire", f"dedx_mean_gwire_{suffix}.txt")
99 val_path_bwire = os.path.join(path, "plots", "wire", f"dedx_mean_badwire_{suffix}.txt")
100 val_path_layer = os.path.join(path, "plots", "wire", f"dedx_mean_layer_{suffix}.txt")
101
102 df_gwire = read_txt(val_path_gwire, ["wire", "mean"])
103 df_bwire = read_txt(val_path_bwire, ["wire", "mean"])
104 df_layer = read_txt(val_path_layer, ["layer", "mean", "gmean"])
105
106 if df_gwire is None or df_bwire is None or df_layer is None:
107 return
108
109 pdf_path = make_pdf_path("dedx_vs_wire_layer", suffix)
110
111 with PdfPages(pdf_path) as pdf:
112 fig, ax = plt.subplots(2, 2, figsize=(20, 12))
113
114 ymin, ymax = get_positive_minmax(df_gwire['mean'])
115
116 pc.hist(y_min=ymin-0.05, y_max=ymax+0.05, xlabel="Wire", ylabel="dE/dx mean", space=1000, ax=ax[0, 0])
117 ax[0, 0].plot(df_gwire['wire'], df_gwire['mean'], '*', markersize=5, rasterized=True)
118 ax[0, 0].set_title('dE/dx Mean vs good Wire', fontsize=14)
119
120 ymin, ymax = get_positive_minmax(df_bwire['mean'])
121
122 pc.hist(y_min=ymin-0.05, y_max=ymax+0.05, xlabel="Wire", ylabel="dE/dx mean", space=1000, ax=ax[1, 0])
123 ax[1, 0].plot(df_bwire['wire'], df_bwire['mean'], '*', markersize=5, rasterized=True)
124 ax[1, 0].set_title('dE/dx Mean vs bad Wire', fontsize=14)
125
126 ymin, ymax = get_positive_minmax(df_layer['mean'])
127
128 pc.hist(x_min=0, x_max=56, y_min=ymin-0.05, y_max=ymax+0.05, xlabel="Layer", ylabel="dE/dx mean", space=3, ax=ax[0, 1])
129 ax[0, 1].plot(df_layer['layer'], df_layer['mean'], '*', markersize=10, rasterized=True)
130 ax[0, 1].set_title('dE/dx Mean vs Layer', fontsize=14)
131
132 ymin, ymax = get_positive_minmax(df_layer['gmean'])
133 pc.hist(x_min=0, x_max=56, y_min=ymin-0.02, y_max=ymax+0.02, xlabel="Layer", ylabel="dE/dx mean", space=3, ax=ax[1, 1])
134 ax[1, 1].plot(df_layer['layer'], df_layer['gmean'], '*', markersize=10, rasterized=True)
135 ax[1, 1].set_title('dE/dx Mean vs Layer (good wires)', fontsize=14)
136
137 fig.suptitle(f"dE/dx vs #wire {suffix}", fontsize=20)
138 save_to_pdf(pdf, fig)
139
140
141def cosgain_validation(path, suffix):
142 val_path_el = os.path.join(path, "plots", "costh", f"dedx_vs_cos_electrons_{suffix}.txt")
143 val_path_po = os.path.join(path, "plots", "costh", f"dedx_vs_cos_positrons_{suffix}.txt")
144
145 df_el = read_txt(val_path_el, ["cos", "mean", "mean_err", "reso", "reso_err"])
146 df_po = read_txt(val_path_po, ["cos", "mean", "mean_err", "reso", "reso_err"])
147
148 if df_el is None or df_po is None:
149 return
150
151 # Ensure both dataframes are sorted by 'cos' so addition is element-wise correct
152 df_el = df_el.sort_values(by='cos').reset_index(drop=True)
153 df_po = df_po.sort_values(by='cos').reset_index(drop=True)
154
155 # New DataFrame with summed means
156 mean_avg = (df_el['mean'] + df_po['mean']) / 2
157 err_avg = 0.5 * np.sqrt(df_el['mean_err']**2 + df_po['mean_err']**2)
158 df_sum = pd.DataFrame({'cos': df_el['cos'], 'mean_sum': mean_avg, 'err_avg': err_avg})
159
160 pdf_path = make_pdf_path("dedx_vs_cosine", suffix)
161
162 with PdfPages(pdf_path) as pdf:
163 fig, ax = plt.subplots(1, 2, figsize=(20, 6)) # Two plots side-by-side
164 # mean
165 pc.hist(x_min=-1.0, x_max=1.0, y_min=0.96, y_max=1.03, xlabel=r"cos#theta", ylabel="dE/dx mean", space=0.1, ax=ax[0])
166 ax[0].errorbar(
167 df_el['cos'],
168 df_el['mean'],
169 yerr=df_el['mean_err'],
170 fmt='*',
171 markersize=10,
172 rasterized=True,
173 label='electron')
174 ax[0].errorbar(
175 df_po['cos'],
176 df_po['mean'],
177 yerr=df_po['mean_err'],
178 fmt='*',
179 markersize=10,
180 rasterized=True,
181 label='positrons')
182 ax[0].errorbar(df_sum['cos'], df_sum['mean_sum'], yerr=df_sum['err_avg'], fmt='*',
183 markersize=10, rasterized=True, label=r'average of e^{+} and e^{-}')
184 ax[0].legend(fontsize=17)
185 ax[0].set_title('dE/dx Mean vs cosine', fontsize=14)
186
187 # reso
188 pc.hist(x_min=-1.0, x_max=1.0, y_min=0.04, y_max=0.13, xlabel=r"cos#theta", ylabel="dE/dx reso", space=0.1, ax=ax[1])
189 ax[1].errorbar(
190 df_el['cos'],
191 df_el['reso'],
192 yerr=df_el['reso_err'],
193 fmt='*',
194 markersize=10,
195 rasterized=True,
196 label='electron')
197 ax[1].errorbar(
198 df_po['cos'],
199 df_po['reso'],
200 yerr=df_po['reso_err'],
201 fmt='*',
202 markersize=10,
203 rasterized=True,
204 label='positrons')
205 ax[1].legend(fontsize=17)
206 ax[1].set_title('dE/dx Resolution vs cosine', fontsize=14)
207
208 fig.suptitle(fr"dE/dx vs cos$\theta$ {suffix}", fontsize=20)
209 save_to_pdf(pdf, fig)
210
211
212def injection_validation(path, suffix):
213
214 cols = ["var", "bin", "mean", "mean_err", "reso", "reso_err"]
215 # corrected files
216 val_path_ler = os.path.join(path, "plots", "injection", f"dedx_vs_inj_ler_{suffix}.txt")
217 val_path_her = os.path.join(path, "plots", "injection", f"dedx_vs_inj_her_{suffix}.txt")
218
219 df_ler = read_txt(val_path_ler, cols)
220 df_her = read_txt(val_path_her, cols)
221
222 # no-correction files
223 val_path_ler_nocor = os.path.join(path, "plots", "injection", f"dedx_vs_inj_nocor_ler_{suffix}.txt")
224 val_path_her_nocor = os.path.join(path, "plots", "injection", f"dedx_vs_inj_nocor_her_{suffix}.txt")
225
226 df_ler_nocor = read_txt(val_path_ler_nocor, cols)
227 df_her_nocor = read_txt(val_path_her_nocor, cols)
228
229 if df_ler is None or df_her is None or df_ler_nocor is None or df_her_nocor is None:
230 return
231
232 for df in [df_ler, df_her, df_ler_nocor, df_her_nocor]:
233 df["bin"] = df["bin"].astype(str)
234
235 pdf_path = make_pdf_path("dedx_mean_inj", suffix)
236
237 with PdfPages(pdf_path) as pdf:
238
239 fig, ax = plt.subplots(2, 1, figsize=(18, 10), sharex=True)
240
241 ymin, ymax = get_positive_minmax(
242 pd.concat([df_ler["mean"], df_her["mean"]])
243 )
244
245 pc.hist(y_min=ymin - 0.01, y_max=ymax + 0.01,
246 xlabel="", ylabel="dE/dx mean",
247 space=3, ax=ax[0])
248
249 ax[0].errorbar(df_ler['bin'], df_ler['mean'], yerr=df_ler['mean_err'],
250 fmt='*', markersize=10, rasterized=True, label='LER')
251 ax[0].errorbar(df_her['bin'], df_her['mean'], yerr=df_her['mean_err'],
252 fmt='*', markersize=10, rasterized=True, label='HER')
253
254 ax[0].legend(fontsize=14)
255 ax[0].set_title("Corrected", fontsize=16)
256
257 all_means = pd.concat([
258 df_ler["mean"], df_her["mean"],
259 df_ler_nocor["mean"], df_her_nocor["mean"]
260 ])
261
262 positive_means = all_means[all_means > 0]
263 ymin2 = positive_means.min() if not positive_means.empty else all_means.min()
264 ymax2 = all_means.max()
265
266 pc.hist(y_min=ymin2 - 0.01, y_max=ymax2 + 0.01,
267 xlabel="injection time", ylabel="dE/dx mean",
268 space=3, ax=ax[1])
269
270 datasets = [
271 ("LER corr", df_ler, "o"),
272 ("HER corr", df_her, "s"),
273 ("LER no corr", df_ler_nocor, "^"),
274 ("HER no corr", df_her_nocor, "D"),
275 ]
276
277 for label, df, marker in datasets:
278 ax[1].errorbar(df['bin'], df['mean'], yerr=df['mean_err'],
279 fmt=marker, markersize=6, rasterized=True, label=label)
280
281 ax[1].legend(fontsize=12)
282 ax[1].set_title("Corrected vs No correction", fontsize=16)
283
284 fig.suptitle(f"dE/dx vs Injection time {suffix}", fontsize=20)
285
286 plt.tight_layout(rect=[0, 0, 1, 0.96]) # important to avoid overlap
287 save_to_pdf(pdf, fig)
288
289
290def mom_validation(path, suffix):
291
292 cos_labels = [
293 "acos",
294 "cos$\\theta > 0.0$",
295 "cos$\\theta < 0.0$",
296 "cos$\\theta \\leq -0.8$",
297 "cos$\\theta > -0.8$ and $\\cos\\theta \\leq -0.6$",
298 "cos$\\theta > -0.6$ and $\\cos\\theta \\leq -0.4$",
299 "cos$\\theta > -0.4$ and $\\cos\\theta \\leq -0.2$",
300 "cos$\\theta > -0.2$ and $\\cos\\theta \\leq 0$",
301 "cos$\\theta > 0$ and $\\cos\\theta \\leq 0.2$",
302 "cos$\\theta > 0.2$ and $\\cos\\theta \\leq 0.4$",
303 "cos$\\theta > 0.4$ and $\\cos\\theta \\leq 0.6$",
304 "cos$\\theta > 0.6$ and $\\cos\\theta \\leq 0.8$",
305 "cos$\\theta > 0.8$"
306 ]
307
308 # Define output PDFs
309 pdf_paths = {
310 "low": make_pdf_path("dedx_vs_mom", suffix),
311 "high": make_pdf_path("dedx_vs_mom", f"{suffix}_cosbins"),
312 }
313
314 with PdfPages(pdf_paths["low"]) as pdf_low, PdfPages(pdf_paths["high"]) as pdf_high:
315 for i in range(13):
316 cols = ["mom", "mean", "mean_err", "reso", "reso_err"]
317 val_path_el = os.path.join(path, "plots", "mom", f"dedx_vs_mom_{i}_elec_{suffix}.txt")
318 val_path_po = os.path.join(path, "plots", "mom", f"dedx_vs_mom_{i}_posi_{suffix}.txt")
319
320 df_el = read_txt(val_path_el, cols)
321 df_po = read_txt(val_path_po, cols)
322
323 if df_el is None or df_po is None:
324 continue
325
326 df_el['mom'] *= -1 # flip electron momentum
327
328 fig, ax = plt.subplots(2, 2, figsize=(20, 12))
329
330 ymin, ymax = get_positive_minmax(df_el['mean'])
331
332 panels = [
333 {"xlim": (-7, 7), "ylim": (ymin-0.01, ymax+0.01),
334 "ylabel": "dE/dx mean", "df_col": "mean", "err_col": "mean_err",
335 "title": "dE/dx Mean vs momentum"},
336 {"xlim": (-7, 7), "ylim": (0.04, 0.1),
337 "ylabel": "dE/dx reso", "df_col": "reso", "err_col": "reso_err",
338 "title": "dE/dx resolution vs momentum"},
339 {"xlim": (-3, 3), "ylim": (ymin-0.01, ymax+0.01),
340 "ylabel": "dE/dx mean", "df_col": "mean", "err_col": "mean_err",
341 "title": "dE/dx Mean vs momentum (zoomed)"},
342 {"xlim": (-3, 3), "ylim": (0.04, 0.1),
343 "ylabel": "dE/dx reso", "df_col": "reso", "err_col": "reso_err",
344 "title": "dE/dx resolution vs momentum (zoomed)"},
345 ]
346
347 for ax_i, panel in zip(ax.flat, panels):
348 pc.hist(x_min=panel["xlim"][0], x_max=panel["xlim"][1],
349 y_min=panel["ylim"][0], y_max=panel["ylim"][1],
350 xlabel="Momentum", ylabel=panel["ylabel"],
351 space=1, ax=ax_i)
352
353 ax_i.errorbar(df_el['mom'], df_el[panel["df_col"]],
354 yerr=df_el[panel["err_col"]],
355 fmt='*', markersize=10, rasterized=True, label='electron')
356 ax_i.errorbar(df_po['mom'], df_po[panel["df_col"]],
357 yerr=df_po[panel["err_col"]],
358 fmt='*', markersize=10, rasterized=True, label='positron')
359 ax_i.legend(fontsize=17)
360 ax_i.set_title(panel["title"], fontsize=14)
361 if i == 3 and panel["df_col"] == "reso":
362 ymin, ymax = ax_i.get_ylim()
363 ax_i.set_ylim(ymin, ymax * 1.5)
364
365 fig.suptitle(f"dE/dx vs Momentum ({cos_labels[i]}) {suffix}", fontsize=20)
366 plt.tight_layout()
367
368 # Save to correct PDF
369 if i <= 2:
370 save_to_pdf(pdf_low, fig)
371 else:
372 save_to_pdf(pdf_high, fig)
373
374
375def oneDcell_validation(path, suffix):
376
377 val_path_il = os.path.join(path, "plots", "oneD", f"dedx_vs_1D_IL_{suffix}.txt")
378 val_path_ol = os.path.join(path, "plots", "oneD", f"dedx_vs_1D_OL_{suffix}.txt")
379
380 df_il = read_txt(val_path_il, ["enta", "mean"])
381 df_ol = read_txt(val_path_ol, ["enta", "mean"])
382
383 if df_il is None or df_ol is None:
384 return
385
386 pdf_path = make_pdf_path("dedx_vs_enta", suffix)
387
388 with PdfPages(pdf_path) as pdf:
389 fig, ax = plt.subplots(2, 2, figsize=(20, 12))
390
391 pc.hist(x_min=-1.5, x_max=1.5, y_min=0.9, y_max=1.07, xlabel=r"entaRS", ylabel="dE/dx mean", space=0.3, ax=ax[0, 0])
392 ax[0, 0].plot(df_il['enta'], df_il['mean'], '-', markersize=10, rasterized=True, label='IL')
393 ax[0, 0].legend(fontsize=17)
394 ax[0, 0].set_title('dE/dx Mean vs entaRS (IL)', fontsize=14)
395
396 pc.hist(x_min=-1.5, x_max=1.5, y_min=0.9, y_max=1.05, xlabel=r"entaRS", ylabel="dE/dx mean", space=0.3, ax=ax[0, 1])
397 ax[0, 1].plot(df_ol['enta'], df_ol['mean'], '-', markersize=10, rasterized=True, label='OL')
398 ax[0, 1].legend(fontsize=17)
399 ax[0, 1].set_title('dE/dx Mean vs entaRS (OL)', fontsize=14)
400
401 pc.hist(x_min=-0.2, x_max=0.2, y_min=0.9, y_max=1.07, xlabel=r"entaRS", ylabel="dE/dx mean", space=0.02, ax=ax[1, 0])
402 ax[1, 0].plot(df_il['enta'], df_il['mean'], '-', markersize=10, rasterized=True, label='IL')
403 ax[1, 0].legend(fontsize=17)
404 ax[1, 0].set_title('dE/dx Mean vs entaRS (IL) zoom', fontsize=14)
405
406 pc.hist(x_min=-0.2, x_max=0.2, y_min=0.9, y_max=1.05, xlabel=r"entaRS", ylabel="dE/dx mean", space=0.02, ax=ax[1, 1])
407 ax[1, 1].plot(df_ol['enta'], df_ol['mean'], '-', markersize=10, rasterized=True, label='OL')
408 ax[1, 1].legend(fontsize=17)
409 ax[1, 1].set_title('dE/dx Mean vs entaRS (OL) zoom', fontsize=14)
410
411 fig.suptitle(f"dE/dx vs entaRS {suffix}", fontsize=20)
412 save_to_pdf(pdf, fig)
413
414
415def run_validation(job_path, input_data_path, requested_iov, expert_config, **kwargs):
416 '''
417 Makes validation plots
418 :job_path: path to cdcdedx calibration output
419 :input_data_path: path to the input files
420 :requested_iov: required argument but not used
421 :expert_config: required argument
422 '''
423 os.makedirs('plots/validation', exist_ok=True)
424 os.makedirs('plots/constant', exist_ok=True)
425
426 expert_config = json.loads(expert_config)
427 GT = expert_config["GT"]
428
429 basf2.B2INFO("Starting validation...")
430
431 basf2.B2INFO("Processing run gain payloads...")
432 gtpath = os.path.join(job_path, 'rungain2', 'outputdb')
433 rg.getRunGain(gtpath, GT)
434
435 basf2.B2INFO("Processing coscorr payloads...")
436 ccpath = os.path.join(job_path, 'coscorr1', 'outputdb')
437 pc.process_cosgain(ccpath, GT)
438
439 basf2.B2INFO("Processing wire gain payloads...")
440 wgpath = os.path.join(job_path, 'wiregain0', 'outputdb')
441 exp_run_dict = pw.process_wiregain(wgpath, GT)
442
443 basf2.B2INFO("Processing 1D gain payloads...")
444 onedpath = os.path.join(job_path, 'onedcell0', 'outputdb')
445 oned.process_onedgain(onedpath, GT)
446
447 basf2.B2INFO("Generating validation plots...")
448 val_path = os.path.join(job_path, 'validation0', '0', 'algorithm_output')
449
450 validators = [
451 ("rungain validation plots", rungain_validation),
452 ("wire gain validation plots", wiregain_validation),
453 ("cosine correction validation plots", cosgain_validation),
454 ("injection time validation plots", injection_validation),
455 ("momentum validation plots", mom_validation),
456 ("1D validation plots", oneDcell_validation),
457 ]
458
459 for exp, run_list in exp_run_dict.items():
460 for run in run_list:
461 suffix = f"e{exp}_r{run}"
462 for msg, func in validators:
463 basf2.B2INFO(f"Processing {msg} for {suffix}...")
464 func(val_path, suffix)
465
466 source_path = os.path.join(job_path, 'validation0', '0', 'algorithm_output', 'plots')
467 shutil.copy(source_path+f"/costh/dedxpeaks_vs_cos_{suffix}.pdf", 'plots/validation/')
468
469 shutil.copy(source_path+f"/mom/dedxpeaks_vs_mom_{suffix}.pdf", 'plots/validation/')
470
471
472if __name__ == "__main__":
473 run_validation(*sys.argv[1:])
Definition plot.py:1