Warning, /detector_benchmarks/benchmarks/backwards_ecal/backwards_ecal.org is written in an unsupported language. File is not indexed.
0001 #+PROPERTY: header-args:jupyter-python :session /jpy:localhost#8888:backwards_ecal :async yes :results drawer :exports both
0002
0003 #+TITLE: ePIC EEEMCal benchmark
0004 #+AUTHOR: Dmitry Kalinkin
0005 #+OPTIONS: d:t
0006
0007 #+LATEX_CLASS_OPTIONS: [9pt,letter]
0008 #+BIND: org-latex-image-default-width ""
0009 #+BIND: org-latex-image-default-option "scale=0.3"
0010 #+BIND: org-latex-images-centered nil
0011 #+BIND: org-latex-minted-options (("breaklines") ("bgcolor" "black!5") ("frame" "single"))
0012 #+LATEX_HEADER: \usepackage[margin=1in]{geometry}
0013 #+LATEX_HEADER: \setlength{\parindent}{0pt}
0014 #+LATEX: \sloppy
0015
0016 #+begin_src jupyter-python :results silent
0017 import os
0018 from pathlib import Path
0019
0020 import awkward as ak
0021 import numpy as np
0022 import vector
0023 import uproot
0024 from sklearn.metrics import roc_curve
0025
0026 vector.register_awkward()
0027 #+end_src
0028
0029 * Plotting setup
0030
0031 #+begin_src jupyter-python :results silent
0032 import matplotlib as mpl
0033 import matplotlib.pyplot as plt
0034
0035 def setup_presentation_style():
0036 mpl.rcParams.update(mpl.rcParamsDefault)
0037 plt.style.use('ggplot')
0038 plt.rcParams.update({
0039 'axes.labelsize': 8,
0040 'axes.titlesize': 9,
0041 'figure.titlesize': 9,
0042 'figure.figsize': (4, 3),
0043 'legend.fontsize': 7,
0044 'xtick.labelsize': 8,
0045 'ytick.labelsize': 8,
0046 'xaxis.labellocation': 'right',
0047 'yaxis.labellocation': 'top',
0048 'pgf.rcfonts': False,
0049 })
0050
0051 setup_presentation_style()
0052 #+end_src
0053
0054 * Parameters
0055
0056 #+begin_src jupyter-python :results silent
0057 DETECTOR_CONFIG=os.environ.get("DETECTOR_CONFIG")
0058 PLOT_TITLE=os.environ.get("PLOT_TITLE")
0059 INPUT_PATH_FORMAT=os.environ.get("INPUT_PATH_FORMAT", "EPIC/RECO/24.04.0/epic_craterlake/SINGLE/{particle}/{energy}/130to177deg/{particle}_{energy}_130to177deg.{ix:04d}.eicrecon.tree.edm4eic.root")
0060 INPUT_PATH_INDEX_RANGE=list(range(20))
0061
0062 output_dir=Path(os.environ.get("OUTPUT_DIR", "./"))
0063 output_dir.mkdir(parents=True, exist_ok=True)
0064 #+end_src
0065
0066 * Analysis
0067
0068 First, we define a requirement on what phase we will consider for our
0069 analysis. The following function filters single-particle events that
0070 are thrown within $-3.5 < \eta < -2.0$:
0071
0072 #+begin_src jupyter-python
0073 def filter_pointing(events):
0074 part_momentum = ak.zip({
0075 "m": events["MCParticles.mass"],
0076 "px": events["MCParticles.momentum.x"],
0077 "py": events["MCParticles.momentum.y"],
0078 "pz": events["MCParticles.momentum.z"],
0079 }, with_name="Momentum4D")
0080 cond = (part_momentum.eta[:,0] > -3.5) & (part_momentum.eta[:,0] < -2.)
0081 return events[cond]
0082 #+end_src
0083
0084 #+begin_src jupyter-python
0085 energies = [
0086 "100MeV",
0087 "200MeV",
0088 "500MeV",
0089 "1GeV",
0090 "2GeV",
0091 "5GeV",
0092 "10GeV",
0093 "20GeV",
0094 ]
0095
0096 filter_name = [
0097 "MCParticles.*",
0098 "*EcalEndcapNRecHits*",
0099 "*EcalEndcapNClusters*",
0100 ]
0101
0102 pi_eval = {}
0103 e_eval = {}
0104
0105 for energy in energies:
0106 pi_eval[energy] = filter_pointing(uproot.concatenate(
0107 {INPUT_PATH_FORMAT.format(particle="pi-", energy=energy, ix=ix): "events" for ix in INPUT_PATH_INDEX_RANGE},
0108 filter_name=filter_name,
0109 ))
0110 e_eval[energy] = filter_pointing(uproot.concatenate(
0111 {INPUT_PATH_FORMAT.format(particle="e-", energy=energy, ix=ix): "events" for ix in INPUT_PATH_INDEX_RANGE},
0112 filter_name=filter_name,
0113 ))
0114 #+end_src
0115
0116 ** Pion rejection
0117
0118 #+begin_src jupyter-python
0119 fig, axs = plt.subplots(2, 4, sharex=True, sharey=True, figsize=(15, 6))
0120 fig_log, axs_log = plt.subplots(2, 4, sharex=True, sharey=True, figsize=(15, 6))
0121 fig_roc, axs_roc = plt.subplots(2, 4, sharex=True, sharey=True, figsize=(15, 6))
0122
0123 fig.suptitle(PLOT_TITLE)
0124 fig_log.suptitle(PLOT_TITLE)
0125 fig_roc.suptitle(PLOT_TITLE)
0126
0127 axs = np.ravel(np.array(axs))
0128 axs_log = np.ravel(np.array(axs_log))
0129 axs_roc = np.ravel(np.array(axs_roc))
0130
0131 rocs = {}
0132
0133 for ix, energy in enumerate(energies):
0134 for use_clusters in [False, True]:
0135 energy_value = float(energy.replace("GeV", "").replace("MeV", "e-3"))
0136 if use_clusters:
0137 clf_label = "leading cluster"
0138 else:
0139 clf_label = "sum all hits"
0140 def clf(events):
0141 if use_clusters:
0142 return ak.drop_none(ak.max(events["EcalEndcapNClusters.energy"], axis=-1)) / energy_value
0143 else:
0144 return ak.sum(events["EcalEndcapNRecHits.energy"], axis=-1) / energy_value
0145 e_pred = clf(e_eval[energy])
0146 pi_pred = clf(pi_eval[energy])
0147
0148 for do_log, ax in [(False, axs[ix]), (True, axs_log[ix])]:
0149 plt.sca(ax)
0150 plt.hist(e_pred, weights=np.full_like(e_pred, 1.0 / ak.num(e_pred, axis=0)), bins=np.linspace(0., 1.01, 101), label=rf"$e^-$ {clf_label}", hatch=None if use_clusters else r"xxx", alpha=0.8 if use_clusters else 1.)
0151 plt.hist(pi_pred, weights=np.full_like(pi_pred, 1.0 / ak.num(pi_pred, axis=0)), bins=np.linspace(0., 1.01, 101), label=rf"$\pi^-$ {clf_label}", histtype="step")
0152 plt.title(f"{energy}")
0153 plt.legend()
0154 plt.xlabel("Classifier output")
0155 plt.ylabel("Event yield")
0156 if do_log:
0157 plt.yscale("log")
0158
0159 plt.sca(axs_roc[ix])
0160 fpr, tpr, _ = roc_curve(
0161 np.concatenate([np.ones_like(e_pred), np.zeros_like(pi_pred)]),
0162 np.concatenate([e_pred, pi_pred]),
0163 )
0164 cond = fpr != 0 # avoid infinite rejection (region of large uncertainty)
0165 cond &= tpr != 1 # avoid linear interpolation (region of large uncertainty)
0166 def mk_interp(tpr, fpr):
0167 def interp(eff):
0168 return np.interp(eff, tpr, fpr)
0169 return interp
0170 rocs.setdefault(clf_label, {})[energy] = mk_interp(tpr, fpr)
0171 plt.plot(tpr[cond] * 100, 1 / fpr[cond], label=f"{clf_label}")
0172 plt.yscale("log")
0173 plt.title(f"{energy}")
0174 plt.legend(loc="lower left")
0175 plt.xlabel("Electron efficiency, %")
0176 plt.ylabel("Pion rejection factor")
0177
0178 fig.savefig(output_dir / f"pred.pdf", bbox_inches="tight")
0179 plt.close(fig)
0180 fig_log.savefig(output_dir / f"pred_log.pdf", bbox_inches="tight")
0181 fig_log.show()
0182 fig_roc.savefig(output_dir / f"roc.pdf", bbox_inches="tight")
0183 fig_roc.show()
0184
0185 plt.figure()
0186 for clf_label, roc in rocs.items():
0187 plt.plot(
0188 [float(energy.replace("GeV", "").replace("MeV", "e-3")) for energy in energies],
0189 [1 / roc[energy](0.95) for energy in energies],
0190 marker=".",
0191 label=f"{clf_label}",
0192 )
0193 plt.yscale("log")
0194 plt.title(INPUT_PATH_FORMAT)
0195 plt.legend()
0196 plt.xlabel("Energy, GeV")
0197 plt.ylabel("Pion rejection at 95%")
0198 plt.savefig(output_dir / f"pion_rej.pdf", bbox_inches="tight")
0199 plt.show()
0200 #+end_src