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